aboutsummaryrefslogtreecommitdiff
path: root/tools/cru-py/cru
diff options
context:
space:
mode:
Diffstat (limited to 'tools/cru-py/cru')
-rw-r--r--tools/cru-py/cru/service/_base.py1
-rw-r--r--tools/cru-py/cru/service/_config.py4
-rw-r--r--tools/cru-py/cru/service/_template.py54
-rw-r--r--tools/cru-py/cru/template.py135
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))