Skip to content

jinja

trestle.core.jinja ¤

Trestle utilities to customize .

logger ¤

Classes¤

MDCleanInclude (TrestleJinjaExtension) ¤

Inject the parameter of the tag as the resulting content.

Source code in trestle/core/jinja.py
class MDCleanInclude(TrestleJinjaExtension):
    """Inject the parameter of the tag as the resulting content."""

    tags = {'md_clean_include'}

    def __init__(self, environment: Environment) -> None:
        """Ensure enviroment is set and carried into class vars."""
        super().__init__(environment)

    def parse(self, parser):
        """Execute parsing of md token and return nodes."""
        kwargs = None
        expected_heading_level = None
        count = 0
        while parser.stream.current.type != lexer.TOKEN_BLOCK_END:
            count = count + 1
            if count > self.max_tag_parse:
                raise err.TrestleError('Unexpected Jinja tag structure provided, please review docs.')
            token = parser.stream.current
            if token.test('name:md_clean_include'):
                parser.stream.expect(lexer.TOKEN_NAME)
                markdown_source = parser.stream.expect(lexer.TOKEN_STRING)
            elif kwargs is not None:
                arg = token.value
                next(parser.stream)
                parser.stream.expect(lexer.TOKEN_ASSIGN)
                token = parser.stream.current
                exp = self.parse_expression(parser)
                kwargs[arg] = exp.value
            else:
                if parser.stream.look().type == lexer.TOKEN_ASSIGN:
                    kwargs = {}
                continue
        md_content, _, _ = self.environment.loader.get_source(self.environment, markdown_source.value)
        fm = frontmatter.loads(md_content)
        content = fm.content
        content += '\n\n'
        if kwargs is not None:
            expected_heading_level = kwargs.get('heading_level')
        if expected_heading_level is not None:
            content = adjust_heading_level(content, expected_heading_level)

        local_parser = Parser(self.environment, content)
        top_level_output = local_parser.parse()

        return top_level_output.body
identifier: ClassVar[str] ¤
tags: Set[str] ¤
Methods¤
__init__(self, environment) special ¤
Source code in trestle/core/jinja.py
def __init__(self, environment: Environment) -> None:
    """Ensure enviroment is set and carried into class vars."""
    super().__init__(environment)
parse(self, parser) ¤

Execute parsing of md token and return nodes.

Source code in trestle/core/jinja.py
def parse(self, parser):
    """Execute parsing of md token and return nodes."""
    kwargs = None
    expected_heading_level = None
    count = 0
    while parser.stream.current.type != lexer.TOKEN_BLOCK_END:
        count = count + 1
        if count > self.max_tag_parse:
            raise err.TrestleError('Unexpected Jinja tag structure provided, please review docs.')
        token = parser.stream.current
        if token.test('name:md_clean_include'):
            parser.stream.expect(lexer.TOKEN_NAME)
            markdown_source = parser.stream.expect(lexer.TOKEN_STRING)
        elif kwargs is not None:
            arg = token.value
            next(parser.stream)
            parser.stream.expect(lexer.TOKEN_ASSIGN)
            token = parser.stream.current
            exp = self.parse_expression(parser)
            kwargs[arg] = exp.value
        else:
            if parser.stream.look().type == lexer.TOKEN_ASSIGN:
                kwargs = {}
            continue
    md_content, _, _ = self.environment.loader.get_source(self.environment, markdown_source.value)
    fm = frontmatter.loads(md_content)
    content = fm.content
    content += '\n\n'
    if kwargs is not None:
        expected_heading_level = kwargs.get('heading_level')
    if expected_heading_level is not None:
        content = adjust_heading_level(content, expected_heading_level)

    local_parser = Parser(self.environment, content)
    top_level_output = local_parser.parse()

    return top_level_output.body

MDDatestamp (TrestleJinjaExtension) ¤

Inject the parameter of the tag as the resulting content.

Source code in trestle/core/jinja.py
class MDDatestamp(TrestleJinjaExtension):
    """Inject the parameter of the tag as the resulting content."""

    tags = {'md_datestamp'}

    def __init__(self, environment: Environment) -> None:
        """Ensure enviroment is set and carried into class vars."""
        super().__init__(environment)

    def parse(self, parser):
        """Execute parsing of md token and return nodes."""
        kwargs = None
        count = 0
        while parser.stream.current.type != lexer.TOKEN_BLOCK_END:
            count = count + 1
            token = parser.stream.current
            if count > self.max_tag_parse:
                raise err.TrestleError(f'Unexpected Jinja tag structure provided at token {token.value}')
            if token.test('name:md_datestamp'):
                parser.stream.expect(lexer.TOKEN_NAME)
            elif kwargs is not None:
                arg = token.value
                next(parser.stream)
                parser.stream.expect(lexer.TOKEN_ASSIGN)
                token = parser.stream.current
                exp = self.parse_expression(parser)
                kwargs[arg] = exp.value
            else:
                if parser.stream.look().type == lexer.TOKEN_ASSIGN or parser.stream.look().type == lexer.TOKEN_STRING:
                    kwargs = {}
                continue

        if kwargs is not None:
            if 'format' in kwargs and type(kwargs['format'] is str):
                date_string = date.today().strftime(kwargs['format'])
            else:
                date_string = date.today().strftime(markdown_const.JINJA_DATESTAMP_FORMAT)
            if 'newline' in kwargs and kwargs['newline'] is False:
                pass
            else:
                date_string += '\n\n'
        else:
            date_string = date.today().strftime(markdown_const.JINJA_DATESTAMP_FORMAT) + '\n\n'

        local_parser = Parser(self.environment, date_string)
        datestamp_output = local_parser.parse()

        return datestamp_output.body
identifier: ClassVar[str] ¤
tags: Set[str] ¤
Methods¤
__init__(self, environment) special ¤
Source code in trestle/core/jinja.py
def __init__(self, environment: Environment) -> None:
    """Ensure enviroment is set and carried into class vars."""
    super().__init__(environment)
parse(self, parser) ¤

Execute parsing of md token and return nodes.

Source code in trestle/core/jinja.py
def parse(self, parser):
    """Execute parsing of md token and return nodes."""
    kwargs = None
    count = 0
    while parser.stream.current.type != lexer.TOKEN_BLOCK_END:
        count = count + 1
        token = parser.stream.current
        if count > self.max_tag_parse:
            raise err.TrestleError(f'Unexpected Jinja tag structure provided at token {token.value}')
        if token.test('name:md_datestamp'):
            parser.stream.expect(lexer.TOKEN_NAME)
        elif kwargs is not None:
            arg = token.value
            next(parser.stream)
            parser.stream.expect(lexer.TOKEN_ASSIGN)
            token = parser.stream.current
            exp = self.parse_expression(parser)
            kwargs[arg] = exp.value
        else:
            if parser.stream.look().type == lexer.TOKEN_ASSIGN or parser.stream.look().type == lexer.TOKEN_STRING:
                kwargs = {}
            continue

    if kwargs is not None:
        if 'format' in kwargs and type(kwargs['format'] is str):
            date_string = date.today().strftime(kwargs['format'])
        else:
            date_string = date.today().strftime(markdown_const.JINJA_DATESTAMP_FORMAT)
        if 'newline' in kwargs and kwargs['newline'] is False:
            pass
        else:
            date_string += '\n\n'
    else:
        date_string = date.today().strftime(markdown_const.JINJA_DATESTAMP_FORMAT) + '\n\n'

    local_parser = Parser(self.environment, date_string)
    datestamp_output = local_parser.parse()

    return datestamp_output.body

MDSectionInclude (TrestleJinjaExtension) ¤

Inject the parameter of the tag as the resulting content.

Source code in trestle/core/jinja.py
class MDSectionInclude(TrestleJinjaExtension):
    """Inject the parameter of the tag as the resulting content."""

    tags = {'mdsection_include'}

    def __init__(self, environment: Environment) -> None:
        """Ensure enviroment is set and carried into class vars."""
        super().__init__(environment)

    def parse(self, parser):
        """Execute parsing of md token and return nodes."""
        kwargs = None
        expected_heading_level = None
        count = 0
        while parser.stream.current.type != lexer.TOKEN_BLOCK_END:
            count = count + 1
            if count > self.max_tag_parse:
                raise err.TrestleError('Unexpected Jinja tag structure provided, please review docs.')
            token = parser.stream.current
            if token.test('name:mdsection_include'):
                parser.stream.expect(lexer.TOKEN_NAME)
                markdown_source = parser.stream.expect(lexer.TOKEN_STRING)
                section_title = parser.stream.expect(lexer.TOKEN_STRING)
            elif kwargs is not None:
                arg = token.value
                next(parser.stream)
                parser.stream.expect(lexer.TOKEN_ASSIGN)
                token = parser.stream.current
                exp = self.parse_expression(parser)
                kwargs[arg] = exp.value
            else:
                if parser.stream.look().type == lexer.TOKEN_ASSIGN:
                    kwargs = {}
                continue
        # Use the established environment to source the file
        md_content, _, _ = self.environment.loader.get_source(self.environment, markdown_source.value)
        fm = frontmatter.loads(md_content)
        if not fm.metadata == {}:
            logger.warning('Non zero metadata on MD section include - ignoring')
        full_md = markdown_node.MarkdownNode.build_tree_from_markdown(fm.content.split('\n'))
        md_section = full_md.get_node_for_key(section_title.value, strict_matching=True)
        # adjust
        if kwargs is not None:
            expected_heading_level = kwargs.get('heading_level')
        if expected_heading_level is not None:
            level = md_section.get_node_header_lvl()
            delta = int(expected_heading_level) - level
            if not delta == 0:
                md_section.change_header_level_by(delta)
        if not md_section:
            raise err.TrestleError(
                f'Unable to retrieve section "{section_title.value}"" from {markdown_source.value} jinja template.'
            )
        local_parser = Parser(self.environment, md_section.content.raw_text)
        top_level_output = local_parser.parse()

        return top_level_output.body
identifier: ClassVar[str] ¤
tags: Set[str] ¤
Methods¤
__init__(self, environment) special ¤
Source code in trestle/core/jinja.py
def __init__(self, environment: Environment) -> None:
    """Ensure enviroment is set and carried into class vars."""
    super().__init__(environment)
parse(self, parser) ¤

Execute parsing of md token and return nodes.

Source code in trestle/core/jinja.py
def parse(self, parser):
    """Execute parsing of md token and return nodes."""
    kwargs = None
    expected_heading_level = None
    count = 0
    while parser.stream.current.type != lexer.TOKEN_BLOCK_END:
        count = count + 1
        if count > self.max_tag_parse:
            raise err.TrestleError('Unexpected Jinja tag structure provided, please review docs.')
        token = parser.stream.current
        if token.test('name:mdsection_include'):
            parser.stream.expect(lexer.TOKEN_NAME)
            markdown_source = parser.stream.expect(lexer.TOKEN_STRING)
            section_title = parser.stream.expect(lexer.TOKEN_STRING)
        elif kwargs is not None:
            arg = token.value
            next(parser.stream)
            parser.stream.expect(lexer.TOKEN_ASSIGN)
            token = parser.stream.current
            exp = self.parse_expression(parser)
            kwargs[arg] = exp.value
        else:
            if parser.stream.look().type == lexer.TOKEN_ASSIGN:
                kwargs = {}
            continue
    # Use the established environment to source the file
    md_content, _, _ = self.environment.loader.get_source(self.environment, markdown_source.value)
    fm = frontmatter.loads(md_content)
    if not fm.metadata == {}:
        logger.warning('Non zero metadata on MD section include - ignoring')
    full_md = markdown_node.MarkdownNode.build_tree_from_markdown(fm.content.split('\n'))
    md_section = full_md.get_node_for_key(section_title.value, strict_matching=True)
    # adjust
    if kwargs is not None:
        expected_heading_level = kwargs.get('heading_level')
    if expected_heading_level is not None:
        level = md_section.get_node_header_lvl()
        delta = int(expected_heading_level) - level
        if not delta == 0:
            md_section.change_header_level_by(delta)
    if not md_section:
        raise err.TrestleError(
            f'Unable to retrieve section "{section_title.value}"" from {markdown_source.value} jinja template.'
        )
    local_parser = Parser(self.environment, md_section.content.raw_text)
    top_level_output = local_parser.parse()

    return top_level_output.body

TrestleJinjaExtension (Extension) ¤

Class to define common methods to be inherited from for use in trestle.

Source code in trestle/core/jinja.py
class TrestleJinjaExtension(Extension):
    """Class to define common methods to be inherited from for use in trestle."""

    # This
    max_tag_parse = 20

    def __init__(self, environment: Environment) -> None:
        """Ensure enviroment is set and carried into class vars."""
        super().__init__(environment)

    @staticmethod
    def parse_expression(parser):
        """Safely parse jinja expression."""
        # Licensed under MIT from:
        # https://github.com/MoritzS/jinja2-django-tags/blob/master/jdj_tags/extensions.py#L424
        # Due to how the jinja2 parser works, it treats "foo" "bar" as a single
        # string literal as it is the case in python.
        # But the url tag in django supports multiple string arguments, e.g.
        # "{% url 'my_view' 'arg1' 'arg2' %}".
        # That's why we have to check if it's a string literal first.
        token = parser.stream.current
        if token.test(lexer.TOKEN_STRING):
            expr = nodes.Const(token.value, lineno=token.lineno)
            next(parser.stream)
        else:
            expr = parser.parse_expression(False)

        return expr
identifier: ClassVar[str] ¤
max_tag_parse ¤
Methods¤
__init__(self, environment) special ¤

Ensure enviroment is set and carried into class vars.

Source code in trestle/core/jinja.py
def __init__(self, environment: Environment) -> None:
    """Ensure enviroment is set and carried into class vars."""
    super().__init__(environment)
parse_expression(parser) staticmethod ¤

Safely parse jinja expression.

Source code in trestle/core/jinja.py
@staticmethod
def parse_expression(parser):
    """Safely parse jinja expression."""
    # Licensed under MIT from:
    # https://github.com/MoritzS/jinja2-django-tags/blob/master/jdj_tags/extensions.py#L424
    # Due to how the jinja2 parser works, it treats "foo" "bar" as a single
    # string literal as it is the case in python.
    # But the url tag in django supports multiple string arguments, e.g.
    # "{% url 'my_view' 'arg1' 'arg2' %}".
    # That's why we have to check if it's a string literal first.
    token = parser.stream.current
    if token.test(lexer.TOKEN_STRING):
        expr = nodes.Const(token.value, lineno=token.lineno)
        next(parser.stream)
    else:
        expr = parser.parse_expression(False)

    return expr

Functions¤

adjust_heading_level(input_md, expected) ¤

Adjust the header level of a markdown string such that the most significant header matches the expected #'s.

Source code in trestle/core/jinja.py
def adjust_heading_level(input_md: str, expected: int) -> str:
    """Adjust the header level of a markdown string such that the most significant header matches the expected #'s."""
    output_md = input_md
    mdn = markdown_node.MarkdownNode.build_tree_from_markdown(input_md.split('\n'))
    if mdn.subnodes:
        mdn_top_heading = mdn.subnodes[0].get_node_header_lvl()
        delta = int(expected) - mdn_top_heading
        if not delta == 0:
            mdn.change_header_level_by(delta)
            output_md = mdn.content.raw_text
    return output_md

handler: python