diff options
Diffstat (limited to 'tools/cru-py/cru/template.py')
-rw-r--r-- | tools/cru-py/cru/template.py | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/tools/cru-py/cru/template.py b/tools/cru-py/cru/template.py new file mode 100644 index 0000000..8e06418 --- /dev/null +++ b/tools/cru-py/cru/template.py @@ -0,0 +1,142 @@ +from collections.abc import Iterable, Mapping +import os +import os.path +from string import Template + +from ._error import CruException + + +class CruTemplateError(CruException): + pass + + +class TemplateFile: + def __init__(self, source: str, destination_path: str | None): + self._source = source + self._destination = destination_path + self._template: Template | None = None + + @property + def source(self) -> str: + return self._source + + @property + def destination(self) -> str | None: + return self._destination + + @destination.setter + def destination(self, value: str | None) -> None: + self._destination = value + + @property + def template(self) -> Template: + if self._template is None: + return self.reload_template() + return self._template + + def reload_template(self) -> Template: + with open(self._source, "r") as f: + self._template = Template(f.read()) + return self._template + + @property + def variables(self) -> set[str]: + return set(self.template.get_identifiers()) + + def generate(self, variables: Mapping[str, str]) -> str: + return self.template.substitute(variables) + + def generate_to_destination(self, variables: Mapping[str, str]) -> None: + if self._destination is None: + raise CruTemplateError("No destination specified for this template.") + with open(self._destination, "w") as f: + f.write(self.generate(variables)) + + +class TemplateDirectory: + def __init__( + self, + source: str, + destination: str, + exclude: Iterable[str], + file_suffix: str = ".template", + ): + self._files: list[TemplateFile] | None = None + self._source = source + self._destination = destination + self._exclude = [os.path.normpath(p) for p in exclude] + self._file_suffix = file_suffix + + @property + def files(self) -> list[TemplateFile]: + if self._files is None: + return self.reload() + else: + return self._files + + @property + def source(self) -> str: + return self._source + + @property + def destination(self) -> str: + return self._destination + + @property + def exclude(self) -> list[str]: + return self._exclude + + @property + def file_suffix(self) -> str: + return self._file_suffix + + @staticmethod + def _scan_files( + root_path: str, exclude: list[str], suffix: str | None + ) -> Iterable[str]: + for root, _dirs, files in os.walk(root_path): + for file in files: + if suffix is None or file.endswith(suffix): + path = os.path.join(root, file) + path = os.path.relpath(path, root_path) + if suffix is not None: + path = path[: -len(suffix)] + is_exclude = False + for exclude_path in exclude: + if path.startswith(exclude_path): + is_exclude = True + break + if not is_exclude: + yield path + + def reload(self) -> list[TemplateFile]: + if not os.path.isdir(self.source): + raise CruTemplateError( + f"Source directory {self.source} does not exist or is not a directory." + ) + files = self._scan_files(self.source, self.exclude, self.file_suffix) + self._files = [ + TemplateFile( + os.path.join(self._source, file + self.file_suffix), + os.path.join(self._destination, file), + ) + for file in files + ] + return self._files + + @property + def variables(self) -> set[str]: + s = set() + for file in self.files: + s.update(file.variables) + return s + + def generate_to_destination(self, variables: Mapping[str, str]) -> None: + for file in self.files: + file.generate_to_destination(variables) + + def extra_files_in_destination(self) -> Iterable[str]: + source_files = set(os.path.relpath(f.source, self.source) for f in self.files) + for file in self._scan_files(self.destination, self.exclude, None): + if file not in source_files: + yield file |