Esempio n. 1
0
    def test_required_flat(self):
        required = ['req1', 'req2']
        set1 = {'key': 'val', 'req1': 1, 'req2': False}

        options = Options(set1, required=required)

        set2 = {'key': 'val', 'req2': False}
        with self.assertRaises(RequiredParamsMissingError):
            options = Options(set2, required=required)
Esempio n. 2
0
    def test_required_combinations(self):
        required = [['req1', 'req2'], ['req21', 'req22'], ['req']]
        set1 = {'key': 'val', 'req1': 1, 'req2': False}

        options = Options(set1, required=required)

        set2 = {'key': 'val', 'req2': False}
        with self.assertRaises(RequiredParamsMissingError):
            options = Options(set2, required=required)

        set3 = {'key': 'val', 'req21': False, 'req22': ''}
        options = Options(set3, required=required)

        set4 = {'key': 'val', 'req': '1'}
        options = Options(set4, required=required)
Esempio n. 3
0
        def _sub(block) -> str:
            custom_id = block.group('custom_id').strip()
            if custom_id in self.applied_anchors:
                self._warning(
                    f"Can't apply dublicate custom ID \"{custom_id}\", skipping.",
                    context=self.get_tag_context(block))
                return ''
            if custom_id in header_anchors:
                self._warning(
                    f'Custom ID "{custom_id}" may conflict with header "{header_anchors[custom_id]}".',
                    context=self.get_tag_context(block))
            options = Options(self.options)

            illegal_char = contains_illegal_chars(custom_id)
            if illegal_char:
                self._warning(
                    f"Can't apply custom ID \"{custom_id}\", because it contains illegal symbol: {illegal_char}.",
                    context=self.get_tag_context(block))
                return ''

            self.applied_anchors.append(custom_id)

            if self.context['target'] == 'pdf' and options['tex']:
                element = get_tex_anchor(custom_id)
            else:
                element = get_anchor(custom_id, options,
                                     self.context['target'])

            return f'{element}\n\n{block.group("heading")}\n'
Esempio n. 4
0
    def test_convert(self):
        mock_convertor = Mock(return_value='converted')
        original = {'key': 'val', 'int': 12, 'bool': True}
        options = Options(original, convertors={'int': mock_convertor})

        self.assertEqual(options['int'], 'converted')
        mock_convertor.assert_called_once_with(12)
    def process_swaggerdoc_blocks(self, block) -> str:
        tag_options = Options(
            self.get_options(block.group('options')),
            convertors={
                'json_path':
                rel_path_convertor(self.current_filepath.parent),
                'spec_path':
                rel_path_convertor(self.current_filepath.parent),
                'additional_json_path':
                rel_path_convertor(self.current_filepath.parent)
            })
        options = CombinedOptions(
            options={
                'config': self.options,
                'tag': tag_options
            },
            priority='tag',
            required=[('json_url', ), ('json_path', ), ('spec_url', ),
                      ('spec_path', )],
            validators={'mode': validate_in(self._modes)},
            defaults=self.defaults)
        self.logger.debug(
            f'Processing swaggerdoc tag in {self.current_filepath}')
        spec_url = options['spec_url'] or options['json_url']
        if spec_url and isinstance(spec_url, str):
            spec_url = [spec_url]
        spec_path = options['spec_path'] or options['json_path']
        spec = self._gather_specs(spec_url, spec_path)
        if not spec:
            raise RuntimeError("No valid swagger spec file specified")

        return self._modes[options['mode']](spec, options)
    def _get_options(self, *configs, fallback_title=None) -> Options:
        '''
        Get a list of dictionaries, all of which will be merged in one and
        transfered to an Options object with necessary checks.

        Returns the resulting Options object.
        '''
        options = {}
        if fallback_title:
            options['title'] = fallback_title
        for config in configs:
            options.update(config)
        options = Options(options,
                          validators={'host': val_type(str),
                                      'login': val_type(str),
                                      'password': val_type(str),
                                      'id': val_type([str, int]),
                                      'parent_id': val_type([str, int]),
                                      'title': val_type(str),
                                      'space_key': val_type(str),
                                      'pandoc_path': val_type(str),
                                      },
                          required=[('id',),
                                    ('space_key', 'title')])
        return options
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.logger = self.logger.getChild('swaggerdoc')

        self.logger.debug(f'Preprocessor inited: {self.__dict__}')

        # '/' for abspaths
        self._env = \
            Environment(loader=FileSystemLoader([str(self.project_path), '/']),
                        extensions=["jinja2.ext.do"])

        self._modes = {
            'jinja': self._process_jinja,
            'widdershins': self._process_widdershins
        }

        self._swagger_tmp = self.project_path / '.swaggercache/'
        if self._swagger_tmp.exists():
            remove_tree(self._swagger_tmp)
        os.makedirs(self._swagger_tmp)

        self._counter = 0
        self.options = Options(self.options,
                               validators={
                                   'json_path': validate_exists,
                                   'spec_path': validate_exists
                               })
    def process_template_tag(self, block) -> str:
        """
        Function for processing tag. Send the contents to the corresponging
        template engine along with parameters from tag and config, and
        <content_file> path. Replace the tag with output from the engine.
        """
        tag_options = Options(
            self.get_options(block.group('options')),
            validators={'engine': validate_in(self.engines.keys())})
        options = CombinedOptions({
            'config': self.options,
            'tag': tag_options
        },
                                  priority='tag')

        tag = block.group('tag')
        if tag == 'template':  # if "template" tag is used — engine must be specified
            if 'engine' not in options:
                self._warning(
                    'Engine must be specified in the <template> tag. Skipping.',
                    self.get_tag_context(block))
                return block.group(0)
            engine = self.engines[options['engine']]
        else:
            engine = self.engines[tag]

        current_pos = block.start()
        chapter = get_meta_for_chapter(self.current_filepath)
        section = chapter.get_section_by_offset(current_pos)
        _foliant_vars = {
            'meta': section.data,
            'meta_object': self.meta,
            'config': self.config,
            'target': self.context['target'],
            'backend': self.context['backend'],
            'project_path': self.context['project_path']
        }

        context = {}

        # external context is loaded first, it has lowest priority
        if 'ext_context' in options:
            context.update(
                self.load_external_context(options['ext_context'], options))

        # all unrecognized params are redirected to template engine params
        context.update(
            {p: options[p]
             for p in options if p not in self.tag_params})

        # add options from "context" param
        context.update(options.get('context', {}))

        template = engine(block.group('body'), context,
                          options.get('engine_params', {}),
                          self.current_filepath, _foliant_vars)
        return template.build()
Esempio n. 9
0
    def test_is_default(self):
        original = {'key': 'val', 'def1': 12, 'def3': 'overridden'}
        defaults = {'def1': 12, 'def2': 'Default', 'def3': 'Default'}

        options = Options(original, defaults=defaults)
        self.assertTrue(options.is_default('def1'))
        self.assertTrue(options.is_default('def2'))
        self.assertFalse(options.is_default('def3'))
        self.assertFalse(options.is_default('key'))
Esempio n. 10
0
        def _sub_raw(diagram) -> str:
            '''
            Sub function for raw diagrams replacement (without ``<plantuml>``
            tags). Handles alternation and returns spaces which were used to
            filter out inline mentions of ``@startuml``.
            '''

            spaces = diagram.group('spaces')
            body = diagram.group('body')
            return spaces + self._process_diagram(Options(self.options), body)
Esempio n. 11
0
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.config = Options(self.options,
                              defaults=self.defaults)

        self._cache_path = self.project_path / self.config['cache_dir']
        self._puppeteer_config_file = self._cache_path / 'puppeteer-config.json'

        self.logger = self.logger.getChild('mermaid')

        self.logger.debug(f'Preprocessor inited: {self.__dict__}')
Esempio n. 12
0
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.config = Options(
            self.options,
            defaults=self.defaults,
            validators={'engine': validate_in(self.supported_engines)})

        self._cache_path = self.project_path / self.config['cache_dir']

        self.logger = self.logger.getChild('graphviz')

        self.logger.debug(f'Preprocessor inited: {self.__dict__}')
Esempio n. 13
0
    def test_defaults(self):
        original = {'key': 'val', 'int': 12, 'bool': True, 'overridden': 42}
        defaults = {'defaultkey': 'defaultvalue', 'overridden': 0}

        expected = {
            'key': 'val',
            'int': 12,
            'bool': True,
            'overridden': 42,
            'defaultkey': 'defaultvalue'
        }
        options = Options(original, defaults=defaults)
        self.assertEqual(options.defaults, defaults)
        self.assertEqual(options.options, expected)
Esempio n. 14
0
    def test_validate(self):
        mock_validator1 = Mock(return_value=None)
        mock_validator2 = Mock(return_value=None)
        original = {'key': 'val', 'int': 12, 'bool': True}
        options = Options(original,
                          validators={
                              'key': mock_validator1,
                              'bool': mock_validator2
                          })

        self.assertEqual(options['key'], 'val')
        self.assertEqual(options['int'], 12)
        self.assertEqual(options['bool'], True)

        mock_validator1.assert_called_once_with('val')
        mock_validator2.assert_called_once_with(True)
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.logger = self.logger.getChild('dbmldoc')

        self.logger.debug(f'Preprocessor inited: {self.__dict__}')

        # '/' for abspaths
        self._env = \
            Environment(loader=FileSystemLoader([str(self.project_path), '/']),
                        extensions=["jinja2.ext.do"])

        self._dbml_tmp = self.project_path / '.dbmlcache/'
        if self._dbml_tmp.exists():
            remove_tree(self._dbml_tmp)
        os.makedirs(self._dbml_tmp)

        self.options = Options(self.options,
                               validators={'spec_path': validate_exists})
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self._cachedir = (self.project_path / CACHEDIR_NAME).resolve()
        self._cachedir.mkdir(exist_ok=True)

        self._debug_dir = self._cachedir / DEBUG_DIR_NAME
        shutil.rmtree(self._debug_dir, ignore_errors=True)
        self._debug_dir.mkdir(exist_ok=True)

        self._flat_src_file_path = self._cachedir / self._flat_src_file_name
        self._attachments_dir = self._cachedir / ATTACHMENTS_DIR_NAME
        config = self.config.get('backend_config', {}).get('confluence', {})
        self.options = {**self.defaults, **config}
        self.options = Options(self.options, required=['host'])

        self.logger = self.logger.getChild('confluence')

        self.logger.debug(f'Backend inited: {self.__dict__}')
    def process_dbmldoc_blocks(self, block) -> str:
        tag_options = Options(self.get_options(block.group('options')),
                              convertors={
                                  'spec_path':
                                  rel_path_convertor(
                                      self.current_filepath.parent)
                              })
        options = CombinedOptions(options={
            'config': self.options,
            'tag': tag_options
        },
                                  priority='tag',
                                  required=[('spec_url', ), ('spec_path', )],
                                  defaults=self.defaults)
        self.logger.debug(f'Processing dbmldoc tag in {self.current_filepath}')
        spec_urls = options['spec_url']
        if spec_urls and isinstance(spec_urls, str):
            spec_urls = [spec_urls]
        spec_path = options['spec_path']
        spec = self._gather_specs(spec_urls, spec_path)
        if not spec:
            raise RuntimeError("No valid dbml spec file specified")

        return self._process_jinja(spec, options)
Esempio n. 18
0
    def test_no_processing(self):
        original = {'key': 'val', 'int': 12, 'bool': True}
        options = Options(original)

        self.assertEqual(options.options, original)
Esempio n. 19
0
    def _process_diagrams(self, block) -> str:
        '''
        Process mermaid tag.
        Save Mermaid diagram body to .mmd file, generate an image from it,
        and return the image ref.

        If the image for this diagram has already been generated, the existing version
        is used.

        :returns: Image ref
        '''
        tag_options = Options(self.get_options(block.group('options')))
        options = CombinedOptions({'config': self.options,
                                   'tag': tag_options},
                                  priority='tag')
        body = block.group('body')

        self.logger.debug(f'Processing Mermaid diagram, options: {options}, body: {body}')

        body_hash = md5(f'{body}'.encode())
        body_hash.update(str(options.options).encode())

        diagram_src_path = self._cache_path / 'mermaid' / f'{body_hash.hexdigest()}.mmd'

        self.logger.debug(f'Diagram definition file path: {diagram_src_path}')

        diagram_path = diagram_src_path.with_suffix(f'.{options["format"]}')

        self.logger.debug(f'Diagram image path: {diagram_path}')

        if diagram_path.exists():
            self.logger.debug('Diagram image found in cache')

            return f'![{options.get("caption", "")}]({diagram_path.absolute().as_posix()})'

        diagram_src_path.parent.mkdir(parents=True, exist_ok=True)

        with open(diagram_src_path, 'w', encoding='utf8') as diagram_src_file:
            diagram_src_file.write(body)

            self.logger.debug(f'Diagram definition written into the file')

        command = self._get_command(options, diagram_src_path, diagram_path)
        self.logger.debug(f'Constructed command: {command}')

        # when Mermaid encounters errors in diagram code, it throws error text
        # into stderr but doesn't terminate the process, so we have to do it
        # manually
        p = subprocess.Popen(command, shell=True, stderr=subprocess.PIPE)
        error_text = b''
        for line in p.stderr:
            error_text += line
            # I know this is horrible and some day I will find a better solution
            if b"DeprecationWarning" in line:  # this is usually the list line
                p.terminate()
                p.kill()
                raise RuntimeError(f'Failed to render diagram:\n\n{error_text.decode()}'
                                   '\n\nSkipping')

        self.logger.debug(f'Diagram image saved')

        return f'![{options.get("caption", "")}]({diagram_path.absolute().as_posix()})'
Esempio n. 20
0
 def test_validation_error(self):
     mock_validator = Mock(side_effect=[ValidationError])
     original = {'key': 'val', 'int': 12, 'bool': True}
     with self.assertRaises(ValidationError):
         options = Options(original, validators={'int': mock_validator})
Esempio n. 21
0
    def _process_diagrams(self, block) -> str:
        '''
        Process graphviz tag.
        Save GraphViz diagram body to .gv file, generate an image from it,
        and return the image ref.

        If the image for this diagram has already been generated, the existing version
        is used.

        :returns: Image ref
        '''
        tag_options = Options(
            self.get_options(block.group('options')),
            validators={'engine': validate_in(self.supported_engines)},
            convertors={
                'as_image': boolean_convertor,
                'fix_svg_size': boolean_convertor
            })
        options = CombinedOptions({
            'config': self.options,
            'tag': tag_options
        },
                                  priority='tag')
        body = block.group('body')

        self.logger.debug(
            f'Processing GraphViz diagram, options: {options}, body: {body}')

        body_hash = md5(f'{body}'.encode())
        body_hash.update(str(options.options).encode())

        diagram_src_path = self._cache_path / 'graphviz' / f'{body_hash.hexdigest()}.gv'

        self.logger.debug(f'Diagram definition file path: {diagram_src_path}')

        diagram_path = diagram_src_path.with_suffix(f'.{options["format"]}')

        self.logger.debug(f'Diagram image path: {diagram_path}')

        if diagram_path.exists():
            self.logger.debug('Diagram image found in cache')

            return self._get_result(diagram_path, options)

        diagram_src_path.parent.mkdir(parents=True, exist_ok=True)

        with open(diagram_src_path, 'w', encoding='utf8') as diagram_src_file:
            diagram_src_file.write(body)

            self.logger.debug(f'Diagram definition written into the file')

        command = self._get_command(options, diagram_src_path, diagram_path)
        self.logger.debug(f'Constructed command: {command}')
        result = run(command, shell=True, stdout=PIPE, stderr=PIPE)
        if result.returncode != 0:
            self._warning(
                f'Processing of GraphViz diagram failed:\n{result.stderr.decode()}',
                context=self.get_tag_context(block))
            return block.group(0)

        if options['format'] == 'svg' and options['fix_svg_size']:
            self._fix_svg_size(diagram_path)

        self.logger.debug(f'Diagram image saved')

        return self._get_result(diagram_path, options)