Source code for elasticai.creator.file_generation.template
import re
from collections.abc import Iterable, Iterator, Mapping
from dataclasses import dataclass
from itertools import repeat
from string import Template as StringTemplate
from typing import Protocol, cast
from .resource_utils import read_text
[docs]
def module_to_package(module: str) -> str:
return ".".join(module.split(".")[:-1])
[docs]
class Template(Protocol):
parameters: dict[str, str | list[str]]
content: list[str]
[docs]
@dataclass
class InProjectTemplate(Template):
package: str
file_name: str
parameters: dict[str, str | list[str]]
[docs]
def __post_init__(self) -> None:
self.content = list(read_text(self.package, self.file_name))
[docs]
class TemplateExpander:
def __init__(self, template: Template) -> None:
super().__init__()
self._template = template
[docs]
def lines(self) -> list[str]:
self._assert_all_variables_to_fill_exists()
single_line_params, multi_line_params = _split_single_and_multiline_parameters(
self._template.parameters
)
lines = _expand_template(self._template.content, **single_line_params)
lines = _expand_multiline_template(lines, **multi_line_params)
return list(lines)
[docs]
def unfilled_variables(self) -> set[str]:
template_variables = _extract_template_variables(self._template.content)
variables_to_fill = set(self._template.parameters.keys())
return template_variables - variables_to_fill
def _assert_all_variables_to_fill_exists(self) -> None:
template_variables = _extract_template_variables(self._template.content)
variables_to_fill = set(self._template.parameters.keys())
not_existing_variables = variables_to_fill - template_variables
if len(not_existing_variables) > 0:
raise KeyError(
"The template has no variables named"
f" {', '.join(not_existing_variables)} to fill."
)
def _split_single_and_multiline_parameters(
parameters: Mapping[str, str | Iterable[str]],
) -> tuple[dict[str, str], dict[str, Iterator[str]]]:
single_line_parameters = dict(
cast(
Iterator[tuple[str, str]],
filter(lambda i: isinstance(i[1], str), parameters.items()),
)
)
multiline_parameters = dict(
cast(
Iterator[tuple[str, Iterator[str]]],
filter(lambda i: not isinstance(i[1], str), parameters.items()),
)
)
return single_line_parameters, multiline_parameters
def _expand_multiline_template(
template: str | Iterable[str], **kwargs: Iterable[str]
) -> Iterator[str]:
"""Expand a template field to multiple lines, while keeping indentation.
Example:
>>> template = "\\t$my_key"
>>> values = ["hello,", "world", "!"]
>>> "\\n".join(_expand_multiline_template(template, my_key=values))
'\\thello,\\n\\tworld\\n\\t!'
"""
lines = _unify_template_datatype(template)
for line in lines:
contains_no_key = True
for key in kwargs:
if f"${key}" in line or f"${{{key}}}" in line:
contains_no_key = False
for placeholder_line, value in zip(repeat(line), kwargs[key]):
t = StringTemplate(placeholder_line)
yield t.safe_substitute({key: value})
break
if contains_no_key:
yield line
def _unify_template_datatype(template: str | Iterable[str]) -> Iterator[str]:
if isinstance(template, str):
lines = template.splitlines()
else:
lines = list(template)
yield from lines
def _expand_template(template: str | Iterable[str], **kwargs: str) -> Iterator[str]:
for line in _unify_template_datatype(template):
yield StringTemplate(line).safe_substitute(kwargs)
def _extract_template_variables(template: list[str]) -> set[str]:
template_text = "\n".join(template)
id_pattern = r"\$([_a-z][_a-z0-9]*)"
bid_pattern = r"\${([_a-z][_a-z0-9]*)}"
identifiers = set(
re.findall(id_pattern, template_text) + re.findall(bid_pattern, template_text)
)
return identifiers