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 = docs_markdown_node.DocsMarkdownNode.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 = docs_markdown_node.DocsMarkdownNode.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 = docs_markdown_node.DocsMarkdownNode.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