예제 #1
0
    def template_validate(self) -> int:
        """Validate the integrity of the template files."""
        logger.info('Checking template file integrity')
        for template_file in self.template_dir.iterdir():
            if (template_file.name
                    not in author_const.REFERENCE_TEMPLATES.values()
                    and template_file.name.lower() != 'readme.md'):
                raise TrestleError(
                    f'Unexpected template file {self.rel_dir(template_file)}')

            if template_file.suffix == '.md':
                try:
                    md_api = MarkdownAPI()
                    md_api.load_validator_with_template(
                        template_file, True, False)
                except Exception as ex:
                    raise TrestleError(
                        f'Template for task {self.task_name} failed to validate due to {ex}'
                    )

            elif template_file.suffix == '.drawio':
                try:
                    _ = DrawIOMetadataValidator(template_file)
                except Exception as ex:
                    raise TrestleError(
                        f'Template for task {self.task_name} failed to validate due to {ex}'
                    )

        logger.info('Templates validated')
        return CmdReturnCodes.SUCCESS.value
def test_bad_unicode_in_parsetree(tmp_path: pathlib.Path):
    """Test error on read of bad unicode in parsetree."""
    bad_file = tmp_path / 'bad_unicode.md'
    with open(bad_file, 'wb') as f:
        f.write(b'\x81')
    with pytest.raises(err.TrestleError):
        md_api = MarkdownAPI()
        md_api.load_validator_with_template(bad_file, False, False)
def test_instance_template_version(template_path: pathlib.Path,
                                   instance_path: pathlib.Path,
                                   status: bool) -> None:
    """Test for x-trestle-template-version header in instances."""
    md_api = MarkdownAPI()
    md_api.load_validator_with_template(template_path, False, True)
    result = md_api.validate_instance(instance_path)
    assert result == status
def test_md_validator_with_md_header(template_path: pathlib.Path,
                                     instance_path: pathlib.Path, status: bool,
                                     yaml_header_validate: bool) -> None:
    """Test with validation of heading."""
    md_api = MarkdownAPI()
    md_api.load_validator_with_template(template_path, yaml_header_validate,
                                        False, 'Governed Document')
    result = md_api.validate_instance(instance_path)
    assert result == status
def test_md_validator_substitutions(template_path: pathlib.Path,
                                    instance_path: pathlib.Path, status: bool,
                                    header_validate: bool,
                                    validate_md_body: bool) -> None:
    """Run markdown validator to expected outcome."""
    md_api = MarkdownAPI()
    md_api.load_validator_with_template(template_path, header_validate,
                                        validate_md_body)
    result = md_api.validate_instance(instance_path)
    assert result == status
def test_validate_for_governed_header(template_path: pathlib.Path,
                                      instance_path: pathlib.Path,
                                      status: bool,
                                      governed_header: str) -> None:
    """Test scenarios for validate w.r.t the governed header."""
    md_api = MarkdownAPI()
    md_api.load_validator_with_template(template_path, False, False,
                                        governed_header)
    result = md_api.validate_instance(instance_path)
    assert result == status
예제 #7
0
    def write_versioned_template(resource_name: str,
                                 task_path: Path,
                                 target_file: Path,
                                 version: Optional[str] = None) -> None:
        """
        Write a template with the header or metadata of a specified version.

        If no version was given the latest version for the task will be used.

        Args:
            resource_name:  Template resource name
            task_path: Task path
            target_file: File path where template will be written
            version: return a resource of a specific version

        Returns:
            A dotted path of a versioned template, list of all available versions
        """
        TemplateVersioning._check_if_exists_and_dir(task_path)
        try:
            templates_resource_path = TRESTLE_RESOURCES + '.templates'

            generic_template = Path(
                resource_filename(templates_resource_path,
                                  resource_name)).resolve()
            if version is None:
                _, version = TemplateVersioning.get_latest_version_for_task(
                    task_path)

            # modify header/metadata in the template
            if generic_template.suffix == '.md':
                md_api = MarkdownAPI()
                header, md_body = md_api.processor.read_markdown_wo_processing(
                    generic_template)
                header[TEMPLATE_VERSION_HEADER] = version
                md_api.write_markdown_with_header(target_file, header, md_body)
                logger.debug(
                    f'Successfully written template markdown to {target_file}')
            elif generic_template.suffix == '.drawio':
                drawio = DrawIO(generic_template)
                metadata = drawio.get_metadata()[0]
                metadata[TEMPLATE_VERSION_HEADER] = version

                drawio.write_drawio_with_metadata(generic_template, metadata,
                                                  0, target_file)
                logger.debug(
                    f'Successfully written template drawio to {target_file}')
            else:
                raise TrestleError(
                    f'Unsupported template file extension {generic_template.suffix}'
                )
        except OSError as e:
            raise TrestleError(f'Error while updating template folder: {e}')
def test_md_by_hand() -> None:
    """Simpler test to enable debugging."""
    template_path = pathlib.Path(
        'tests/data/author/0.0.1/test_3_md_hand_edited/template.md')
    instance_path = pathlib.Path(
        'tests/data/author/0.0.1/test_3_md_hand_edited/decisions_000.md')
    header_validate = False
    status = True
    md_api = MarkdownAPI()
    md_api.load_validator_with_template(template_path, header_validate, False,
                                        'Governed Document')
    result = md_api.validate_instance(instance_path)
    assert result == status
예제 #9
0
def test_ssp_generate(import_cat, specify_sections,
                      tmp_trestle_dir: pathlib.Path) -> None:
    """Test the ssp generator."""
    args, sections, yaml_path = setup_for_ssp(True, False, tmp_trestle_dir,
                                              prof_name, ssp_name, import_cat)
    if specify_sections:
        args.allowed_sections = 'ImplGuidance,ExpectedEvidence'

    ssp_cmd = SSPGenerate()
    # run the command for happy path
    assert ssp_cmd._run(args) == 0
    ac_dir = tmp_trestle_dir / (ssp_name + '/ac')
    ac_1 = ac_dir / 'ac-1.md'
    ac_2 = ac_dir / 'ac-2.md'
    assert ac_1.exists()
    assert ac_2.exists()
    assert ac_1.stat().st_size > 1000
    assert ac_2.stat().st_size > 2000

    with open(yaml_path, 'r', encoding=const.FILE_ENCODING) as f:
        yaml = YAML()
        expected_header = yaml.load(f)
    sections_dict = sections_to_dict(sections)
    expected_header[const.SECTIONS_TAG] = sections_dict
    assert test_utils.confirm_text_in_file(
        ac_1, '## Control', '## Control Guidance') != specify_sections
    md_api = MarkdownAPI()
    header, tree = md_api.processor.process_markdown(ac_1)
    assert tree is not None
    assert expected_header == header
    header, tree = md_api.processor.process_markdown(ac_2)
    assert tree is not None
    assert expected_header == header
예제 #10
0
def test_modify_md_node_header_lvl(testdata_dir: pathlib.Path,
                                   tmp_trestle_dir: pathlib.Path) -> None:
    """Test that header modification works."""
    markdown_file = testdata_dir / 'markdown/valid_levels_no_text.md'
    expected_file = testdata_dir / 'markdown/valid_levels_no_text_increased_level.md'
    md_api = MarkdownAPI()
    _, tree = md_api.processor.process_markdown(markdown_file)

    tree.change_header_level_by(1)

    _, tree_expected = md_api.processor.process_markdown(expected_file)
    assert tree_expected.content.raw_text == tree.content.raw_text
    assert tree_expected.content.text == tree.content.text
    assert len(list(tree.get_all_headers_for_level(1))) == len(
        list(tree_expected.get_all_headers_for_level(1)))
    assert len(list(tree.get_all_headers_for_level(2))) == len(
        list(tree_expected.get_all_headers_for_level(2)))
    assert len(list(tree.get_all_headers_for_level(3))) == len(
        list(tree_expected.get_all_headers_for_level(3)))
    assert tree.get_node_for_key(
        '####### Header 3.2.1.1.1.1'
    ).content.raw_text == tree_expected.get_node_for_key(
        '####### Header 3.2.1.1.1.1').content.raw_text
    assert len(
        tree.get_node_for_key('## Header 3 a deeper tree').subnodes) == len(
            tree_expected.get_node_for_key(
                '## Header 3 a deeper tree').subnodes)

    for key in tree_expected.content.subnodes_keys:
        node = tree.get_node_for_key(key)
        assert node is not None
        expected_node = tree_expected.get_node_for_key(key)
        assert node.content.raw_text == expected_node.content.raw_text
        assert len(node.subnodes) == len(expected_node.subnodes)

    tree.change_header_level_by(-1)

    _, tree_expected = md_api.processor.process_markdown(markdown_file)

    assert tree_expected.content.raw_text == tree.content.raw_text
    assert tree_expected.content.text == tree.content.text
    assert len(list(tree.get_all_headers_for_level(1))) == len(
        list(tree_expected.get_all_headers_for_level(1)))
    assert len(list(tree.get_all_headers_for_level(2))) == len(
        list(tree_expected.get_all_headers_for_level(2)))
    assert len(list(tree.get_all_headers_for_level(3))) == len(
        list(tree_expected.get_all_headers_for_level(3)))
    assert len(
        tree.get_node_for_key('# Header 3 a deeper tree').subnodes) == len(
            tree_expected.get_node_for_key(
                '# Header 3 a deeper tree').subnodes)

    for key in tree_expected.content.subnodes_keys:
        node = tree.get_node_for_key(key)
        assert node is not None
        expected_node = tree_expected.get_node_for_key(key)
        assert node.content.raw_text == expected_node.content.raw_text
        assert len(node.subnodes) == len(expected_node.subnodes)
예제 #11
0
    def template_validate(self, heading: str, validate_header: bool,
                          validate_only_header: bool) -> int:
        """Validate that the template is acceptable markdown."""
        template_file = self.template_dir / self.template_name
        if not self._validate_template_dir():
            raise TrestleError('Aborting setup')
        if not template_file.is_file():
            raise TrestleError(
                f'Required template file: {self.rel_dir(template_file)} does not exist. Exiting.'
            )
        try:
            md_api = MarkdownAPI()
            md_api.load_validator_with_template(template_file, validate_header,
                                                validate_only_header, heading)
        except Exception as ex:
            raise TrestleError(
                f'Template for task {self.task_name} failed to validate due to {ex}'
            )

        logger.info(f'TEMPLATES VALID: {self.task_name}')
        return CmdReturnCodes.SUCCESS.value
예제 #12
0
    def template_validate(self, validate_header: bool,
                          validate_only_header: bool, heading: str,
                          readme_validate: bool) -> int:
        """Validate that the template is acceptable markdown."""
        if not self.template_dir.is_dir():
            raise TrestleError(
                f'Template directory {self.rel_dir(self.template_dir)} for task {self.task_name} does not exist.'
            )
        # get list of files:
        template_files = self.template_dir.rglob('*')

        for template_file in template_files:
            try:
                if not file_utils.is_local_and_visible(template_file):
                    continue
                elif template_file.is_dir():
                    continue
                elif template_file.suffix.lower() == '.md':
                    if not readme_validate and template_file.name == 'readme.md':
                        raise TrestleError(
                            'Template directory contains a readme.md file and readme validation is off.'
                        )

                    md_api = MarkdownAPI()
                    md_api.load_validator_with_template(
                        template_file, validate_header,
                        not validate_only_header, heading)
                elif template_file.suffix.lower().lstrip('.') == 'drawio':
                    _ = draw_io.DrawIOMetadataValidator(template_file)
                else:
                    logger.info(
                        f'File: {self.rel_dir(template_file)} within the template directory was ignored'
                        + 'as it is not markdown.')
            except Exception as ex:
                raise TrestleError(
                    f'Template file {self.rel_dir(template_file)} for task {self.task_name}'
                    + f' failed to validate due to {ex}')
        logger.info(f'TEMPLATES VALID: {self.task_name}.')
        return CmdReturnCodes.SUCCESS.value
예제 #13
0
def test_write_versioned_template(tmp_path: pathlib.Path) -> None:
    """Test writing a template to the folder."""
    task_path = tmp_path.joinpath('trestle/author/sample_task/')

    with pytest.raises(TrestleError):
        TemplateVersioning.get_versioned_template_dir(task_path)

    task_path.mkdir(parents=True)
    tmp_path_01 = task_path.joinpath('0.0.1')
    tmp_path_02 = task_path.joinpath('0.0.2')
    tmp_path_01.mkdir(parents=True)
    tmp_path_02.mkdir(parents=True)
    template = tmp_path_01.joinpath('template.md')

    TemplateVersioning.write_versioned_template('template.md', tmp_path_01,
                                                template, None)

    assert task_path.joinpath(START_TEMPLATE_VERSION).joinpath(
        'template.md').exists()
    assert template.exists()

    template2 = tmp_path_01.joinpath('template2.md')
    template3 = tmp_path_02.joinpath('template3.md')
    template4 = tmp_path_02.joinpath('template4.md')
    TemplateVersioning.write_versioned_template('template.md', tmp_path_01,
                                                template2, '0.0.1')
    TemplateVersioning.write_versioned_template('template.md', tmp_path_02,
                                                template3, '0.0.2')
    TemplateVersioning.write_versioned_template('template.md', tmp_path_02,
                                                template4, None)

    assert tmp_path_01.joinpath('template.md').exists()
    assert tmp_path_01.joinpath('template2.md').exists()
    assert tmp_path_02.joinpath('template3.md').exists()
    assert tmp_path_02.joinpath('template4.md').exists()
    assert template.exists()

    md_api = MarkdownAPI()
    header, _ = md_api.processor.read_markdown_wo_processing(
        tmp_path_02.joinpath('template3.md'))

    assert header[TEMPLATE_VERSION_HEADER] == '0.0.2'

    template_drawio = tmp_path_02.joinpath('template.drawio')
    TemplateVersioning.write_versioned_template('template.drawio', tmp_path_02,
                                                template_drawio, '0.0.2')

    drawio = DrawIO(template_drawio)
    metadata = drawio.get_metadata()[0]

    assert metadata[TEMPLATE_VERSION_HEADER] == '0.0.2'
예제 #14
0
def test_ssp_generate_header_edit(yaml_header: bool,
                                  tmp_trestle_dir: pathlib.Path) -> None:
    """Test ssp generate does not overwrite header edits."""
    # always start by creating the markdown with the yaml header
    args, sections, yaml_path = setup_for_ssp(True, False, tmp_trestle_dir,
                                              prof_name, ssp_name)
    ssp_cmd = SSPGenerate()
    assert ssp_cmd._run(args) == 0

    ac_dir = tmp_trestle_dir / (ssp_name + '/ac')
    ac_1 = ac_dir / 'ac-1.md'

    with open(yaml_path, 'r', encoding=const.FILE_ENCODING) as f:
        yaml = YAML()
        yaml_header = yaml.load(f)

    md_api = MarkdownAPI()
    header, tree = md_api.processor.process_markdown(ac_1)
    assert tree is not None
    # remove the sections that were added to original header so we can check other changes in header
    header.pop(const.SECTIONS_TAG)
    assert yaml_header == header

    # edit the header by adding a list item and removing a value
    assert test_utils.insert_text_in_file(ac_1, 'System Specific',
                                          '  - My new edits\n')
    assert test_utils.delete_line_in_file(ac_1, 'Corporate')

    # if the yaml header is not written out, the new header should be the one currently in the control
    # if the yaml header is written out, it is merged with the current header giving priority to current header
    # so if not written out, the header should have one item added and another deleted due to edits in this test
    # if written out, it should just have the one added item because the deleted one will be put back in

    # tell it not to add the yaml header
    if not yaml_header:
        args.yaml_header = None

    assert ssp_cmd._run(args) == 0
    header, tree = md_api.processor.process_markdown(ac_1)
    assert tree is not None

    assert len(header['control-origination']) == 2
    if not yaml_header:
        assert 'new' in header['control-origination'][0]
    else:
        assert 'new' not in header['control-origination'][0]
예제 #15
0
def test_modify_subtree(testdata_dir: pathlib.Path,
                        tmp_trestle_dir: pathlib.Path) -> None:
    """Test modification of the subtree."""
    markdown_file = testdata_dir / 'markdown/valid_no_lvl1_headers.md'
    md_api = MarkdownAPI()
    _, tree = md_api.processor.process_markdown(markdown_file)

    assert tree.get_node_header_lvl() is None

    subtree = tree.get_node_for_key('## Header root child 2')

    subtree.change_header_level_by(-1)

    assert len(subtree.content.subnodes_keys) == 10
    assert subtree.key == '# Header root child 2'
    assert len(list(subtree.get_all_headers_for_level(2))) == 2
    assert len(list(subtree.get_all_headers_for_level(3))) == 4
    assert len(list(subtree.get_all_headers_for_level(4))) == 4
    assert subtree.get_node_header_lvl() == 1
예제 #16
0
def test_ssp_generate_no_header(tmp_trestle_dir: pathlib.Path) -> None:
    """Test the ssp generator with no yaml header."""
    args, _, _ = setup_for_ssp(False, False, tmp_trestle_dir, prof_name,
                               ssp_name)
    ssp_cmd = SSPGenerate()
    args.sections = None
    # run the command for happy path
    assert ssp_cmd._run(args) == 0
    ac_dir = tmp_trestle_dir / (ssp_name + '/ac')
    ac_1 = ac_dir / 'ac-1.md'
    ac_2 = ac_dir / 'ac-2.md'
    assert ac_1.exists()
    assert ac_2.exists()
    assert ac_1.stat().st_size > 1000
    assert ac_2.stat().st_size > 2000

    md_api = MarkdownAPI()
    header, tree = md_api.processor.process_markdown(ac_1)
    assert tree is not None
    assert not header
    header, tree = md_api.processor.process_markdown(ac_2)
    assert tree is not None
    assert not header
예제 #17
0
def test_modify_md_node_remove_restore_headers(
        testdata_dir: pathlib.Path, tmp_trestle_dir: pathlib.Path) -> None:
    """Test that header modification works."""
    markdown_file = testdata_dir / 'markdown/valid_no_lvl1_headers.md'
    expected_file = testdata_dir / 'markdown/valid_no_headers.md'
    md_api = MarkdownAPI()
    _, tree = md_api.processor.process_markdown(markdown_file)

    tree.change_header_level_by(-99)

    _, tree_expected = md_api.processor.process_markdown(expected_file)

    assert tree_expected.content.raw_text == tree.content.raw_text
    assert len(list(tree.get_all_headers_for_level(1))) == len(
        list(tree_expected.get_all_headers_for_level(1)))
    assert len(list(tree.get_all_headers_for_level(2))) == len(
        list(tree_expected.get_all_headers_for_level(2)))
    assert len(list(tree.get_all_headers_for_level(3))) == len(
        list(tree_expected.get_all_headers_for_level(3)))

    tree.change_header_level_by(4)

    assert len(list(tree.get_all_headers_for_level(4))) == 14
예제 #18
0
    def _validate_dir(self,
                      governed_heading: str,
                      md_dir: pathlib.Path,
                      validate_header: bool,
                      validate_only_header: bool,
                      recurse: bool,
                      readme_validate: bool,
                      template_version: Optional[str] = None,
                      ignore: Optional[str] = None) -> int:
        """
        Validate md files in a directory with option to recurse.

        Template version will be fetched from the instance header.
        """
        # status is a linux returncode
        status = 0
        for item_path in md_dir.iterdir():
            if file_utils.is_local_and_visible(item_path):
                if item_path.is_file():
                    if not item_path.suffix == '.md':
                        logger.info(
                            f'Unexpected file {self.rel_dir(item_path)} in folder {self.rel_dir(md_dir)}, skipping.'
                        )
                        continue
                    if not readme_validate and item_path.name.lower(
                    ) == 'readme.md':
                        continue

                    if ignore:
                        p = re.compile(ignore)
                        matched = p.match(item_path.parts[-1])
                        if matched is not None:
                            logger.info(
                                f'Ignoring file {item_path} from validation.')
                            continue

                    md_api = MarkdownAPI()
                    if template_version != '':
                        template_file = self.template_dir / self.template_name
                    else:
                        instance_version = md_api.processor.fetch_value_from_header(
                            item_path, author_const.TEMPLATE_VERSION_HEADER)
                        if instance_version is None:
                            instance_version = '0.0.1'
                        versione_template_dir = TemplateVersioning.get_versioned_template_dir(
                            self.template_dir, instance_version)
                        template_file = versione_template_dir / self.template_name
                    if not template_file.is_file():
                        raise TrestleError(
                            f'Required template file: {self.rel_dir(template_file)} does not exist. Exiting.'
                        )
                    md_api.load_validator_with_template(
                        template_file, validate_header,
                        not validate_only_header, governed_heading)
                    if not md_api.validate_instance(item_path):
                        logger.info(f'INVALID: {self.rel_dir(item_path)}')
                        status = 1
                    else:
                        logger.info(f'VALID: {self.rel_dir(item_path)}')
                elif recurse:
                    if ignore:
                        p = re.compile(ignore)
                        if len(
                                list(
                                    filter(
                                        p.match,
                                        str(item_path.relative_to(
                                            md_dir)).split('/')))) > 0:
                            logger.info(
                                f'Ignoring directory {item_path} from validation.'
                            )
                            continue
                    rc = self._validate_dir(governed_heading, item_path,
                                            validate_header,
                                            validate_only_header, recurse,
                                            readme_validate, template_version,
                                            ignore)
                    if rc != 0:
                        status = rc

        return status
def test_bad_file_path(tmp_path: pathlib.Path):
    """Check errors are thrown with bad files."""
    no_file = tmp_path / 'non_existent.md'
    with pytest.raises(err.TrestleError):
        md_api = MarkdownAPI()
        md_api.load_validator_with_template(no_file, False, False)
def test_broken_yaml_header(testdata_dir: pathlib.Path):
    """Test for a bad markdown header."""
    bad_file = testdata_dir / 'author' / 'bad_md_header.md'
    with pytest.raises(err.TrestleError):
        md_api = MarkdownAPI()
        md_api.load_validator_with_template(bad_file, True, False)
def test_template_path_mismatch(template_path: pathlib.Path) -> None:
    """Test template path and version mismatch."""
    with pytest.raises(err.TrestleError):
        md_api = MarkdownAPI()
        md_api.load_validator_with_template(template_path, False, True)
예제 #22
0
    def _validate_dir(self, candidate_dir: pathlib.Path, recurse: bool,
                      readme_validate: bool,
                      relative_exclusions: List[pathlib.Path],
                      template_version: str, ignore: str) -> bool:
        """Validate a directory within the trestle project."""
        all_versioned_templates = {}
        instance_version = template_version
        instance_file_names: List[pathlib.Path] = []
        # Fetch all instances versions and build dictionary of required template files
        instances = list(candidate_dir.iterdir())
        if recurse:
            instances = candidate_dir.rglob('*')
            if ignore:
                p = re.compile(ignore)
                instances = list(
                    filter(
                        lambda f: len(
                            list(
                                filter(
                                    p.match,
                                    str(f.relative_to(candidate_dir)).split(
                                        '/')))) == 0, instances))
        for instance_file in instances:
            if not file_utils.is_local_and_visible(instance_file):
                continue
            if instance_file.name.lower(
            ) == 'readme.md' and not readme_validate:
                continue
            if instance_file.is_dir() and not recurse:
                continue
            if any(
                    str(ex) in str(instance_file)
                    for ex in relative_exclusions):
                continue
            if ignore:
                p = re.compile(ignore)
                matched = p.match(instance_file.parts[-1])
                if matched is not None:
                    logger.info(
                        f'Ignoring file {instance_file} from validation.')
                    continue
            instance_file_name = instance_file.relative_to(candidate_dir)
            instance_file_names.append(instance_file_name)
            if instance_file.suffix == '.md':
                md_api = MarkdownAPI()
                versioned_template_dir = None
                if template_version != '':
                    versioned_template_dir = self.template_dir
                else:
                    instance_version = md_api.processor.fetch_value_from_header(
                        instance_file, author_const.TEMPLATE_VERSION_HEADER)
                    if instance_version is None:
                        instance_version = '0.0.1'  # backward compatibility
                    versioned_template_dir = TemplateVersioning.get_versioned_template_dir(
                        self.template_dir, instance_version)

                if instance_version not in all_versioned_templates.keys():
                    templates = list(
                        filter(lambda p: file_utils.is_local_and_visible(p),
                               versioned_template_dir.iterdir()))
                    if not readme_validate:
                        templates = list(
                            filter(lambda p: p.name.lower() != 'readme.md',
                                   templates))
                    all_versioned_templates[instance_version] = {}
                    all_versioned_templates[instance_version]['drawio'] = list(
                        filter(lambda p: p.suffix == '.drawio', templates))[0]
                    all_versioned_templates[instance_version]['md'] = list(
                        filter(lambda p: p.suffix == '.md', templates))[0]

                # validate
                md_api.load_validator_with_template(
                    all_versioned_templates[instance_version]['md'], True,
                    False)
                status = md_api.validate_instance(instance_file)
                if not status:
                    logger.info(f'INVALID: {self.rel_dir(instance_file)}')
                    return False
                else:
                    logger.info(f'VALID: {self.rel_dir(instance_file)}')

            elif instance_file.suffix == '.drawio':
                drawio = DrawIO(instance_file)
                metadata = drawio.get_metadata()[0]

                versioned_template_dir = None
                if template_version != '':
                    versioned_template_dir = self.template_dir
                else:
                    if author_const.TEMPLATE_VERSION_HEADER in metadata.keys():
                        instance_version = metadata[
                            author_const.TEMPLATE_VERSION_HEADER]
                    else:
                        instance_version = '0.0.1'  # backward compatibility

                    versioned_template_dir = TemplateVersioning.get_versioned_template_dir(
                        self.template_dir, instance_version)

                if instance_version not in all_versioned_templates.keys():
                    templates = list(
                        filter(lambda p: file_utils.is_local_and_visible(p),
                               versioned_template_dir.iterdir()))
                    if not readme_validate:
                        templates = list(
                            filter(lambda p: p.name.lower() != 'readme.md',
                                   templates))
                    all_versioned_templates[instance_version] = {}
                    all_versioned_templates[instance_version]['drawio'] = list(
                        filter(lambda p: p.suffix == '.drawio', templates))[0]
                    all_versioned_templates[instance_version]['md'] = list(
                        filter(lambda p: p.suffix == '.md', templates))[0]

                # validate
                drawio_validator = DrawIOMetadataValidator(
                    all_versioned_templates[instance_version]['drawio'])
                status = drawio_validator.validate(instance_file)
                if not status:
                    logger.info(f'INVALID: {self.rel_dir(instance_file)}')
                    return False
                else:
                    logger.info(f'VALID: {self.rel_dir(instance_file)}')

            else:
                logger.debug(
                    f'Unsupported extension of the instance file: {instance_file}, will not be validated.'
                )

        return True
예제 #23
0
    def _measure_template_folder(self, instance_dir: pathlib.Path,
                                 validate_header: bool,
                                 validate_only_header: bool,
                                 governed_heading: str, readme_validate: bool,
                                 template_version: str, ignore: str) -> bool:
        """
        Validate instances against templates.

        Validation will succeed iff:
            1. All template files from the specified version are present in the task
            2. All of the instances are valid
        """
        all_versioned_templates = {}
        instance_version = template_version
        instance_file_names: List[pathlib.Path] = []
        # Fetch all instances versions and build dictionary of required template files
        for instance_file in instance_dir.iterdir():
            if not file_utils.is_local_and_visible(instance_file):
                continue
            if not instance_file.is_file():
                continue
            if instance_file.name.lower(
            ) == 'readme.md' and not readme_validate:
                continue
            if ignore:
                p = re.compile(ignore)
                matched = p.match(instance_file.parts[-1])
                if matched is not None:
                    logger.info(
                        f'Ignoring file {instance_file} from validation.')
                    continue
            instance_file_name = instance_file.relative_to(instance_dir)
            instance_file_names.append(instance_file_name)
            if instance_file.suffix == '.md':
                md_api = MarkdownAPI()
                versioned_template_dir = None
                if template_version != '':
                    template_file = self.template_dir / instance_file_name
                    versioned_template_dir = self.template_dir
                else:
                    instance_version = md_api.processor.fetch_value_from_header(
                        instance_file, author_const.TEMPLATE_VERSION_HEADER)
                    if instance_version is None:
                        instance_version = '0.0.1'  # backward compatibility
                    versioned_template_dir = TemplateVersioning.get_versioned_template_dir(
                        self.template_dir, instance_version)
                    template_file = versioned_template_dir / instance_file_name

                if instance_version not in all_versioned_templates.keys():
                    templates = list(
                        filter(lambda p: file_utils.is_local_and_visible(p),
                               versioned_template_dir.iterdir()))
                    if not readme_validate:
                        templates = list(
                            filter(lambda p: p.name.lower() != 'readme.md',
                                   templates))

                    all_versioned_templates[instance_version] = dict.fromkeys([
                        t.relative_to(versioned_template_dir)
                        for t in templates
                    ], False)

                if instance_file_name in all_versioned_templates[
                        instance_version]:
                    # validate
                    md_api.load_validator_with_template(
                        template_file, validate_header,
                        not validate_only_header, governed_heading)
                    status = md_api.validate_instance(instance_file)
                    if not status:
                        logger.warning(
                            f'INVALID: Markdown file {instance_file} failed validation against'
                            + f' {template_file}')
                        return False
                    else:
                        logger.info(f'VALID: {instance_file}')
                    # mark template as present
                    all_versioned_templates[instance_version][
                        instance_file_name] = True

            elif instance_file.suffix == '.drawio':
                drawio = draw_io.DrawIO(instance_file)
                metadata = drawio.get_metadata()[0]
                versioned_template_dir = None
                if template_version != '':
                    template_file = self.template_dir / instance_file_name
                    versioned_template_dir = self.template_dir
                else:
                    if author_const.TEMPLATE_VERSION_HEADER in metadata.keys():
                        instance_version = metadata[
                            author_const.TEMPLATE_VERSION_HEADER]
                    else:
                        instance_version = '0.0.1'  # backward compatibility

                    versioned_template_dir = TemplateVersioning.get_versioned_template_dir(
                        self.template_dir, instance_version)
                    template_file = versioned_template_dir / instance_file_name

                if instance_version not in all_versioned_templates.keys():
                    templates = list(
                        filter(lambda p: file_utils.is_local_and_visible(p),
                               versioned_template_dir.iterdir()))
                    if not readme_validate:
                        templates = list(
                            filter(lambda p: p.name.lower() != 'readme.md',
                                   templates))

                    all_versioned_templates[instance_version] = dict.fromkeys([
                        t.relative_to(versioned_template_dir)
                        for t in templates
                    ], False)

                if instance_file_name in all_versioned_templates[
                        instance_version]:
                    # validate
                    drawio_validator = draw_io.DrawIOMetadataValidator(
                        template_file)
                    status = drawio_validator.validate(instance_file)
                    if not status:
                        logger.warning(
                            f'INVALID: Drawio file {instance_file} failed validation against'
                            + f' {template_file}')
                        return False
                    else:
                        logger.info(f'VALID: {instance_file}')
                    # mark template as present
                    all_versioned_templates[instance_version][
                        instance_file_name] = True

            else:
                logger.debug(
                    f'Unsupported extension of the instance file: {instance_file}, will not be validated.'
                )

        # Check that all template files are present
        for version in all_versioned_templates.keys():
            for template in all_versioned_templates[version]:
                if not all_versioned_templates[version][template]:
                    logger.warning(
                        f'Required template file {template} does not exist in measured instance'
                        + f'{instance_dir}')
                    return False

        return True