diff options
Diffstat (limited to 'tools/cru-py/cru')
-rw-r--r-- | tools/cru-py/cru/service/_base.py | 1 | ||||
-rw-r--r-- | tools/cru-py/cru/service/_config.py | 4 | ||||
-rw-r--r-- | tools/cru-py/cru/service/_template.py | 54 | ||||
-rw-r--r-- | tools/cru-py/cru/template.py | 135 |
4 files changed, 103 insertions, 91 deletions
diff --git a/tools/cru-py/cru/service/_base.py b/tools/cru-py/cru/service/_base.py index f91eadd..98eed89 100644 --- a/tools/cru-py/cru/service/_base.py +++ b/tools/cru-py/cru/service/_base.py @@ -10,6 +10,7 @@ from cru import CruException, CruInternalError, CruPath _F = TypeVar("_F") +OWNER_NAME = "crupest" class InternalAppException(CruInternalError): pass diff --git a/tools/cru-py/cru/service/_config.py b/tools/cru-py/cru/service/_config.py index a387ef7..63b73b3 100644 --- a/tools/cru-py/cru/service/_config.py +++ b/tools/cru-py/cru/service/_config.py @@ -12,3 +12,7 @@ class ConfigManager(AppFeatureProvider): @property def config_path(self) -> AppFeaturePath: return self._config_path + + @property + def config_map(self) -> dict[str, str]: + raise NotImplementedError() diff --git a/tools/cru-py/cru/service/_template.py b/tools/cru-py/cru/service/_template.py index fcc5658..5f0252a 100644 --- a/tools/cru-py/cru/service/_template.py +++ b/tools/cru-py/cru/service/_template.py @@ -1,18 +1,23 @@ from argparse import ArgumentParser, Namespace +from cru import CruIterator from cru.template import TemplateTree -from ._base import AppCommandFeatureProvider, AppFeaturePath +from ._base import AppCommandFeatureProvider, AppFeaturePath, OWNER_NAME +from ._config import ConfigManager class TemplateManager(AppCommandFeatureProvider): - def __init__(self, prefix: str = "CRUPEST"): + def __init__(self, prefix: str = OWNER_NAME.upper()): super().__init__("template-manager") + self._prefix = prefix self._templates_dir = self.app.add_path("templates", True) self._generated_dir = self.app.add_path("generated", True) - self._template_tree = TemplateTree( - prefix, self._templates_dir.full_path_str, self._generated_dir.full_path_str - ) + self._template_tree: TemplateTree | None = None + + @property + def prefix(self) -> str: + return self._prefix @property def templates_dir(self) -> AppFeaturePath: @@ -22,9 +27,42 @@ class TemplateManager(AppCommandFeatureProvider): def generated_dir(self) -> AppFeaturePath: return self._generated_dir + @property + def template_tree(self) -> TemplateTree: + if self._template_tree is None: + return self.reload() + return self._template_tree + + def reload(self) -> TemplateTree: + self._template_tree = TemplateTree( + self.prefix, self.templates_dir.full_path_str + ) + return self._template_tree + + def list_files(self) -> list[str]: + return ( + CruIterator(self.template_tree.templates) + .transform(lambda t: t[0]) + .to_list() + ) + + def print_file_lists(self) -> None: + for file in self.list_files(): + print(file) + + def generate_files(self) -> None: + config_manager = self.app.get_feature(ConfigManager) + self.template_tree.generate_to( + self.generated_dir.full_path_str, config_manager.config_map + ) + def add_arg_parser(self, arg_parser: ArgumentParser) -> None: subparsers = arg_parser.add_subparsers(dest="template_command") - list_parser = subparsers.add_parser("list", help="List templates.") - generate_parser = subparsers.add_parser("generate", help="Generate template.") + _list_parser = subparsers.add_parser("list", help="List templates.") + _generate_parser = subparsers.add_parser("generate", help="Generate template.") - def run_command(self, args: Namespace) -> None: ... + def run_command(self, args: Namespace) -> None: + if args.template_command == "list": + self.print_file_lists() + elif args.template_command == "generate": + self.generate_files() diff --git a/tools/cru-py/cru/template.py b/tools/cru-py/cru/template.py index ccb3ad8..a02ea0e 100644 --- a/tools/cru-py/cru/template.py +++ b/tools/cru-py/cru/template.py @@ -1,4 +1,4 @@ -from collections.abc import Iterable, Mapping +from collections.abc import Mapping import os import os.path from string import Template @@ -27,7 +27,7 @@ class CruTemplate: return self._prefix @property - def real_template(self) -> Template: + def py_template(self) -> Template: return self._template @property @@ -39,8 +39,16 @@ class CruTemplate: return self._all_variables @property - def has_no_variables(self) -> bool: - return len(self._variables) == 0 + def has_variables(self) -> bool: + """ + If the template does not has any variables that starts with the given prefix, + it returns False. This usually indicates that the template is not a real + template and should be copied as is. Otherwise, it returns True. + + This can be used as a guard to prevent invalid templates created accidentally + without notice. + """ + return len(self.variables) > 0 def generate(self, mapping: Mapping[str, str], allow_extra: bool = True) -> str: values = dict(mapping) @@ -51,118 +59,79 @@ class CruTemplate: return self._template.safe_substitute(values) -class CruTemplateFile(CruTemplate): - def __init__(self, prefix: str, source: str, destination_path: str): - self._source = source - self._destination = destination_path - with open(source, "r") as f: - super().__init__(prefix, f.read()) - - @property - def source(self) -> str: - return self._source - - @property - def destination(self) -> str | None: - return self._destination - - def generate_to_destination( - self, mapping: Mapping[str, str], allow_extra: bool = True - ) -> None: - with open(self._destination, "w") as f: - f.write(self.generate(mapping, allow_extra)) - - class TemplateTree: def __init__( self, prefix: str, source: str, - destination: str, - exclude: Iterable[str] | None = None, - template_file_suffix: str = ".template", + template_file_suffix: str | None = ".template", ): + """ + If template_file_suffix is not None, the files will be checked according to the + suffix of the file name. If the suffix matches, the file will be regarded as a + template file. Otherwise, it will be regarded as a non-template file. + Content of template file must contain variables that need to be replaced, while + content of non-template file may not contain any variables. + If either case is false, it generally means whether the file is a template is + wrongly handled. + """ self._prefix = prefix - self._files: list[CruTemplateFile] | None = None + self._files: list[tuple[str, CruTemplate]] = [] self._source = source - self._destination = destination - self._exclude = [os.path.normpath(p) for p in exclude or []] self._template_file_suffix = template_file_suffix + self._load() @property def prefix(self) -> str: return self._prefix @property - def files(self) -> list[CruTemplateFile]: - if self._files is None: - self.reload() - return self._files # type: ignore - - @property - def template_files(self) -> list[CruTemplateFile]: - return ( - CruIterator(self.files).filter(lambda f: not f.has_no_variables).to_list() - ) - - @property - def non_template_files(self) -> list[CruTemplateFile]: - return CruIterator(self.files).filter(lambda f: f.has_no_variables).to_list() + def templates(self) -> list[tuple[str, CruTemplate]]: + 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 template_file_suffix(self) -> str: + def template_file_suffix(self) -> str | None: return self._template_file_suffix @staticmethod - def _scan_files(root_path: str, exclude: list[str]) -> Iterable[str]: + def _scan_files(root_path: str) -> list[str]: + files: list[str] = [] for root, _dirs, files in os.walk(root_path): for file in files: path = os.path.join(root, file) path = os.path.relpath(path, root_path) - 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, strict=True) -> None: - self._files = [] - file_names = self._scan_files(self.source, self.exclude) - for file_name in file_names: - source = os.path.join(self.source, file_name) - destination = os.path.join(self.destination, file_name) - file = CruTemplateFile(self._prefix, source, destination) - if file_name.endswith(self.template_file_suffix): - if strict and file.has_no_variables: + files.append(path) + return files + + def _load(self) -> None: + files = self._scan_files(self.source) + for file_path in files: + template_file = os.path.join(self.source, file_path) + with open(template_file, "r") as f: + content = f.read() + template = CruTemplate(self.prefix, content) + if self.template_file_suffix is not None: + should_be_template = file_path.endswith(self.template_file_suffix) + if should_be_template and not template.has_variables: raise CruTemplateError( - f"Template file {file_name} has no variables." + f"Template file {file_path} has no variables." ) - else: - if strict and not file.has_no_variables: - raise CruTemplateError(f"Non-template {file_name} has variables.") - self._files.append(file) + elif not should_be_template and template.has_variables: + raise CruTemplateError(f"Non-template {file_path} has variables.") + self._files.append((file_path, template)) @property def variables(self) -> set[str]: s = set() - for file in self.files: - s.update(file.variables) + for _, template in self.templates: + s.update(template.variables) return s - def generate_to_destination(self, variables: Mapping[str, str]) -> None: - for file in self.files: - file.generate_to_destination(variables) + def generate_to(self, destination: str, variables: Mapping[str, str]) -> None: + for file, template in self.templates: + with open(os.path.join(destination, file), "w") as f: + f.write(template.generate(variables)) |