Source code for pyechonext.template_engine.builtin
import os
import re
from pyechonext.logging import logger
from pyechonext.request import Request
from pyechonext.utils.exceptions import TemplateNotFileError
FOR_BLOCK_PATTERN = re.compile(
r"{% for (?P<variable>[a-zA-Z]+) in (?P<seq>[a-zA-Z]+) %}(?P<content>[\S\s]+)(?={%"
r" endfor %}){% endfor %}"
)
VARIABLE_PATTERN = re.compile(r"{{ (?P<variable>[a-zA-Z_]+) }}")
[docs]
class TemplateEngine:
"""
This class describes a built-in template engine.
"""
[docs]
def __init__(self, base_dir: str, templates_dir: str):
"""
Constructs a new instance.
:param base_dir: The base dir
:type base_dir: str
:param templates_dir: The templates dir
:type templates_dir: str
"""
self.templates_dir = os.path.join(base_dir, templates_dir)
def _get_template_as_string(self, template_name: str) -> str:
"""
Gets the template as string.
:param template_name: The template name
:type template_name: str
:returns: The template as string.
:rtype: str
:raises TemplateNotFileError: Template is not a file
"""
template_name = os.path.join(self.templates_dir, template_name)
if not os.path.isfile(template_name):
raise TemplateNotFileError(
f'Template "{template_name}" is not a file')
with open(template_name, "r") as file:
content = file.read()
return content
def _build_block_of_template(self, context: dict, raw_template_block: str) -> str:
"""
Builds a block of template.
:param context: The context
:type context: dict
:param raw_template_block: The raw template block
:type raw_template_block: str
:returns: The block of template.
:rtype: str
"""
used_vars = VARIABLE_PATTERN.findall(raw_template_block)
if used_vars is None:
return raw_template_block
for var in used_vars:
var_in_template = "{{ %s }}" % (var)
processed_template_block = re.sub(
var_in_template, str(context.get(var, "")), raw_template_block
)
return processed_template_block
def _build_statement_for_block(self, context: dict, raw_template_block: str) -> str:
"""
Build statement `for` block
:param context: The context
:type context: dict
:param raw_template_block: The raw template block
:type raw_template_block: str
:returns: The statement for block.
:rtype: str
"""
statement_for_block = FOR_BLOCK_PATTERN.search(raw_template_block)
if statement_for_block is None:
return raw_template_block
builded_statement_block_for = ""
for variable in context.get(statement_for_block.group("seq"), []):
builded_statement_block_for += self._build_block_of_template(
{**context, statement_for_block.group("variable"): variable},
statement_for_block.group("content"),
)
processed_template_block = FOR_BLOCK_PATTERN.sub(
builded_statement_block_for, raw_template_block
)
return processed_template_block
[docs]
def build(self, context: dict, template_name: str) -> str:
"""
Build template
:param context: The context
:type context: dict
:param template_name: The template name
:type template_name: str
:returns: raw template string
:rtype: str
"""
raw_template = self._get_template_as_string(template_name)
processed_template = self._build_statement_for_block(
context, raw_template)
return self._build_block_of_template(context, processed_template)
[docs]
def render_template(request: Request, template_name: str, **kwargs) -> str:
"""
Render template
:param request: The request
:type request: Request
:param template_name: The template name
:type template_name: str
:param kwargs: The keywords arguments
:type kwargs: dictionary
:returns: raw template string
:rtype: str
:raises AssertionError: BASE_DIR and TEMPLATES_DIR is empty
"""
logger.warn(
"Built-in template engine is under development and may be unstable or contain"
" bugs"
)
assert request.settings.BASE_DIR
assert request.settings.TEMPLATES_DIR
engine = TemplateEngine(request.settings.BASE_DIR,
request.settings.TEMPLATES_DIR)
context = kwargs
logger.debug(
f"Built-in template engine: render {template_name} ({request.path})")
return engine.build(context, template_name)