From b46083adfb1ac6d2c829002aa9da11ee7abb979b Mon Sep 17 00:00:00 2001 From: edraft Date: Thu, 16 Oct 2025 12:46:11 +0200 Subject: [PATCH] Added cpl generate --- src/cli/cpl.project.json | 2 +- .../cpl/cli/.cpl/generate/abc.py.schematic | 9 ++ .../cpl/cli/.cpl/generate/app.py.schematic | 16 +++ .../cpl/cli/.cpl/generate/config.py.schematic | 10 ++ .../generate/data_access_object.py.schematic | 9 ++ .../cli/.cpl/generate/db_model.py.schematic | 23 ++++ .../.cpl/generate/db_model_join.py.schematic | 29 +++++ .../cpl/cli/.cpl/generate/enum.py.schematic | 5 + .../cpl/cli/.cpl/generate/logger.py.schematic | 7 + .../cpl/cli/.cpl/generate/module.py.schematic | 17 +++ .../.cpl/generate/multiprocess.py.schematic | 9 ++ .../cpl/cli/.cpl/generate/pipe.py.schematic | 11 ++ .../cpl/cli/.cpl/generate/thread.py.schematic | 9 ++ .../cli/.cpl/generate/web_app.py.schematic | 18 +++ src/cli/cpl/cli/command/structure/generate.py | 122 ++++++++++++++++++ src/cli/cpl/cli/main.py | 2 + src/cli/cpl/cli/model/workspace.py | 10 ++ src/cli/cpl/cli/utils/NameUtils.py | 13 ++ src/cli/cpl/cli/utils/template_collector.py | 42 ++++++ .../cpl/dependency/module/__init__.py | 1 + 20 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 src/cli/cpl/cli/.cpl/generate/abc.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/app.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/config.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/data_access_object.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/db_model.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/db_model_join.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/enum.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/logger.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/module.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/multiprocess.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/pipe.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/thread.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/web_app.py.schematic create mode 100644 src/cli/cpl/cli/command/structure/generate.py create mode 100644 src/cli/cpl/cli/utils/NameUtils.py create mode 100644 src/cli/cpl/cli/utils/template_collector.py diff --git a/src/cli/cpl.project.json b/src/cli/cpl.project.json index 76a94d70..852a8a28 100644 --- a/src/cli/cpl.project.json +++ b/src/cli/cpl.project.json @@ -15,7 +15,7 @@ }, "references": [], "main": "cpl/cli/main.py", - "directory": "cpl", + "directory": "cpl/cli", "build": { "include": [ "_templates/" diff --git a/src/cli/cpl/cli/.cpl/generate/abc.py.schematic b/src/cli/cpl/cli/.cpl/generate/abc.py.schematic new file mode 100644 index 00000000..4443bc8f --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/abc.py.schematic @@ -0,0 +1,9 @@ +from abc import ABC + + +class ABC(ABC): + + def __init__(self): + ABC.__init__(self) + + print(" initialized") diff --git a/src/cli/cpl/cli/.cpl/generate/app.py.schematic b/src/cli/cpl/cli/.cpl/generate/app.py.schematic new file mode 100644 index 00000000..6d332b1e --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/app.py.schematic @@ -0,0 +1,16 @@ +from cpl.application.abc import ApplicationABC +from cpl.core.environment import Environment +from cpl.core.log import LoggerABC +from cpl.dependency import ServiceProvider +from cpl.dependency.typing import Modules + + +class (ApplicationABC): + def __init__(self, services: ServiceProvider, modules: Modules): + ApplicationABC.__init__(self, services, modules) + + self._logger = services.get_service(LoggerABC) + + async def main(self): + self._logger.debug(f"Host: {Environment.get_host_name()}") + self._logger.debug(f"Environment: {Environment.get_environment()}") diff --git a/src/cli/cpl/cli/.cpl/generate/config.py.schematic b/src/cli/cpl/cli/.cpl/generate/config.py.schematic new file mode 100644 index 00000000..a3420af4 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/config.py.schematic @@ -0,0 +1,10 @@ +from cpl.core.configuration import ConfigurationModelABC + + +class Config(ConfigurationModelABC): + + def __init__( + self, + src: dict = None, + ): + ConfigurationModelABC.__init__(self, src) diff --git a/src/cli/cpl/cli/.cpl/generate/data_access_object.py.schematic b/src/cli/cpl/cli/.cpl/generate/data_access_object.py.schematic new file mode 100644 index 00000000..4dd329d4 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/data_access_object.py.schematic @@ -0,0 +1,9 @@ +from cpl.database.abc import DbModelDaoABC + + +class Dao(DbModelDaoABC[]): + + def __init__(self): + DbModelDaoABC.__init__(self, , "") + + self.attribute(.name, str) diff --git a/src/cli/cpl/cli/.cpl/generate/db_model.py.schematic b/src/cli/cpl/cli/.cpl/generate/db_model.py.schematic new file mode 100644 index 00000000..1d4ae23b --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/db_model.py.schematic @@ -0,0 +1,23 @@ +from datetime import datetime +from typing import Self + +from cpl.core.typing import SerialId +from cpl.database.abc import DbModelABC + + +class (DbModelABC[Self]): + def __init__( + self, + id: SerialId, + name: str, + deleted: bool = False, + editor_id: SerialId | None = None, + created: datetime | None = None, + updated: datetime | None = None, + ): + DbModelABC.__init__(self, id, deleted, editor_id, created, updated) + self._name = name + + @property + def name(self) -> str: + return self._name diff --git a/src/cli/cpl/cli/.cpl/generate/db_model_join.py.schematic b/src/cli/cpl/cli/.cpl/generate/db_model_join.py.schematic new file mode 100644 index 00000000..d800a952 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/db_model_join.py.schematic @@ -0,0 +1,29 @@ +from datetime import datetime +from typing import Self + +from cpl.core.typing import SerialId +from cpl.database.abc import DbJoinModelABC + + +class Join(DbJoinModelABC[Self]): + def __init__( + self, + id: SerialId, + source_id: SerialId, + reference_id: SerialId, + deleted: bool = False, + editor_id: SerialId | None = None, + created: datetime | None = None, + updated: datetime | None = None, + ): + DbJoinModelABC.__init__(self, source_id, reference_id, id, deleted, editor_id, created, updated) + self._source_id = source_id + self._reference_id = reference_id + + @property + def source_id(self) -> int: + return self._source_id + + @property + def reference(self) -> int: + return self._reference_id diff --git a/src/cli/cpl/cli/.cpl/generate/enum.py.schematic b/src/cli/cpl/cli/.cpl/generate/enum.py.schematic new file mode 100644 index 00000000..ed2e4e94 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/enum.py.schematic @@ -0,0 +1,5 @@ +from enum import Enum + + +class Enum(Enum): + KEY = "value" diff --git a/src/cli/cpl/cli/.cpl/generate/logger.py.schematic b/src/cli/cpl/cli/.cpl/generate/logger.py.schematic new file mode 100644 index 00000000..aa9b144f --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/logger.py.schematic @@ -0,0 +1,7 @@ +from cpl.core.log.wrapped_logger import WrappedLogger + + +class Logger(WrappedLogger): + + def __init__(self): + WrappedLogger.__init__(self, "") diff --git a/src/cli/cpl/cli/.cpl/generate/module.py.schematic b/src/cli/cpl/cli/.cpl/generate/module.py.schematic new file mode 100644 index 00000000..a4901060 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/module.py.schematic @@ -0,0 +1,17 @@ +from cpl.dependency import ServiceCollection, ServiceProvider +from cpl.dependency.module import Module + + +class Module(Module): + dependencies = [] + configuration = [] + singleton = [] + scoped = [] + transient = [] + hosted = [] + + @staticmethod + def register(collection: ServiceCollection): ... + + @staticmethod + def configure(provider: ServiceProvider): ... diff --git a/src/cli/cpl/cli/.cpl/generate/multiprocess.py.schematic b/src/cli/cpl/cli/.cpl/generate/multiprocess.py.schematic new file mode 100644 index 00000000..be7fa608 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/multiprocess.py.schematic @@ -0,0 +1,9 @@ +import multiprocessing + + +class (multiprocessing.Process): + + def __init__(self): + multiprocessing.Process.__init__(self) + + def run(self): ... \ No newline at end of file diff --git a/src/cli/cpl/cli/.cpl/generate/pipe.py.schematic b/src/cli/cpl/cli/.cpl/generate/pipe.py.schematic new file mode 100644 index 00000000..65a30bd0 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/pipe.py.schematic @@ -0,0 +1,11 @@ +from cpl.core.pipes import PipeABC +from cpl.core.typing import T + + +class Pipe(PipeABC): + + @staticmethod + def to_str(value: T, *args) -> str: ... + + @staticmethod + def from_str(value: str, *args) -> T: ... diff --git a/src/cli/cpl/cli/.cpl/generate/thread.py.schematic b/src/cli/cpl/cli/.cpl/generate/thread.py.schematic new file mode 100644 index 00000000..438681e6 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/thread.py.schematic @@ -0,0 +1,9 @@ +import threading + + +class (threading.Thread): + + def __init__(self): + threading.Thread.__init__(self) + + def run(self): ... \ No newline at end of file diff --git a/src/cli/cpl/cli/.cpl/generate/web_app.py.schematic b/src/cli/cpl/cli/.cpl/generate/web_app.py.schematic new file mode 100644 index 00000000..5f63cc0b --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/web_app.py.schematic @@ -0,0 +1,18 @@ +from cpl.api.application import WebApp +from cpl.core.environment import Environment +from cpl.core.log import LoggerABC +from cpl.dependency import ServiceProvider +from cpl.dependency.typing import Modules + + +class (WebApp): + def __init__(self, services: ServiceProvider, modules: Modules): + WebApp.__init__(self, services, modules) + + self._logger = services.get_service(LoggerABC) + + async def main(self): + self._logger.debug(f"Host: {Environment.get_host_name()}") + self._logger.debug(f"Environment: {Environment.get_environment()}") + + await super().main() diff --git a/src/cli/cpl/cli/command/structure/generate.py b/src/cli/cpl/cli/command/structure/generate.py new file mode 100644 index 00000000..7a113000 --- /dev/null +++ b/src/cli/cpl/cli/command/structure/generate.py @@ -0,0 +1,122 @@ +import os +import re +from pathlib import Path + +import click + +from cpl.cli import cli as clim +from cpl.cli.cli import cli +from cpl.cli.model.project import Project +from cpl.cli.model.workspace import Workspace +from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.cli.utils.template_collector import TemplateCollector +from cpl.core.configuration import Configuration +from cpl.core.console import Console +from cpl.core.utils import String + + +@cli.command("generate", aliases=["g"]) +@click.argument("schematic", type=click.STRING, required=True) +@click.argument("name", type=click.STRING, required=True) +@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") +def generate(schematic: str, name: str, verbose: bool) -> None: + TemplateCollector.collect_templates(Path(clim.__file__).parent, verbose) + + workspace: Workspace = Configuration.get("workspace") + if workspace is not None: + if verbose: + Console.write_line("Workspace found, collecting templates...") + TemplateCollector.collect_templates(Path(workspace.path).parent, verbose) + + project = None + try: + project = get_project_by_name_or_path("./") + if verbose: + Console.write_line("project found, collecting templates...") + + TemplateCollector.collect_templates(Path(project.path).parent, verbose) + except ValueError: + if verbose: + Console.write_line("Local project not found") + + templates = TemplateCollector.get_templates() + schematics = {} + for template_name, template_content in templates.items(): + t_name = template_name.split(".")[0] + + if t_name in schematics: + raise ValueError(f"Duplicate schematic name found: {t_name}") + + schematics[t_name] = template_name + + for i in range(len(t_name)): + char = t_name[i] + if char in schematics: + continue + + schematics[char] = template_name + break + + if schematic not in schematics: + raise ValueError( + f"Schematic '{schematic}' not found. Available schematics: {', '.join([x.split(".")[0] for x in templates.keys()])}" + ) + + path, name = _get_name_and_path_from_name(name, project) + + os.makedirs(path, exist_ok=True) + + Console.write_line(f"Generating {str(path / name)} ...") + with open(path / f"{name}.py", "w") as f: + f.write( + _render_template(schematics[schematic].split(".")[0], templates[schematics[schematic]], name, str(path)) + ) + + +def _get_name_and_path_from_name(in_name: str, project: Project = None) -> tuple[Path, str]: + path = "" + name = "" + + in_name_parts = in_name.split("/") + if len(in_name_parts) == 1: + name = in_name_parts[0] + else: + path = "/".join(in_name_parts[:-1]) + name = in_name_parts[-1] + + workspace: Workspace = Configuration.get("workspace") + if workspace is None and project is not None: + return (Path(project.path).parent / project.directory / path).resolve().absolute(), name + elif workspace is None and project is None: + return Path(path).resolve().absolute(), name + + selected_project = project + project_name = path.split("/")[0] + project_by_name = workspace.get_project_by_name(project_name) + if project_by_name is not None: + selected_project = project_by_name + path = "/".join(path.split("/")[1:]) + + if selected_project is None: + selected_project = workspace.get_project_by_name(workspace.default_project) + + return (Path(selected_project.path).parent / selected_project.directory / path).resolve().absolute(), name + + +def _render_template(schematic, template_str: str, name: str, path: str) -> str: + context = { + "schematic": schematic, + "Name": String.to_pascal_case(name), + "name": String.to_snake_case(name), + "NAME": String.to_snake_case(name).upper(), + "camelName": String.to_camel_case(name), + "multi_Name": f"{String.to_pascal_case(name)}s", + "multi_name": f"{String.to_snake_case(name)}s", + "multi_NAME": f"{String.to_snake_case(name).upper()}s", + "multi_camelName": f"{String.to_camel_case(name)}s", + "path": path.replace("\\", "/"), + } + + for key, value in context.items(): + template_str = template_str.replace(f"<{key}>", value) + return template_str diff --git a/src/cli/cpl/cli/main.py b/src/cli/cpl/cli/main.py index 959c7bd0..e85d6a5d 100644 --- a/src/cli/cpl/cli/main.py +++ b/src/cli/cpl/cli/main.py @@ -10,6 +10,7 @@ from cpl.cli.command.package.update import update from cpl.cli.command.project.build import build from cpl.cli.command.project.run import run from cpl.cli.command.project.start import start +from cpl.cli.command.structure.generate import generate from cpl.cli.command.structure.init import init from cpl.cli.command.version import version from cpl.cli.model.workspace import Workspace @@ -54,6 +55,7 @@ def configure(): # structure cli.add_command(init) # cli.add_command(new) + cli.add_command(generate) # packaging cli.add_command(install) diff --git a/src/cli/cpl/cli/model/workspace.py b/src/cli/cpl/cli/model/workspace.py index 77d1880f..b79c6a2e 100644 --- a/src/cli/cpl/cli/model/workspace.py +++ b/src/cli/cpl/cli/model/workspace.py @@ -50,6 +50,10 @@ class Workspace(CPLStructureModel): def actual_projects(self) -> List[Project]: return [Project.from_file(p) for p in self._projects] + @property + def project_names(self) -> List[str]: + return [Project.from_file(p).name for p in self._projects if "name" in p] + @property def default_project(self) -> Optional[str]: return self._default_project @@ -65,3 +69,9 @@ class Workspace(CPLStructureModel): @scripts.setter def scripts(self, value: Dict[str, str]): self._scripts = self._require_dict_str_str(value, "scripts") + + def get_project_by_name(self, name: str) -> Optional[Project]: + for project in self.actual_projects: + if project.name == name: + return project + return None diff --git a/src/cli/cpl/cli/utils/NameUtils.py b/src/cli/cpl/cli/utils/NameUtils.py new file mode 100644 index 00000000..b1ccb66d --- /dev/null +++ b/src/cli/cpl/cli/utils/NameUtils.py @@ -0,0 +1,13 @@ +class NameUtils: + @staticmethod + def classify(name: str) -> str: # UserService + return ''.join(w.capitalize() for w in name.replace('-', '_').split('_')) + + @staticmethod + def dasherize(name: str) -> str: # user-service + return name.replace('_', '-').lower() + + @staticmethod + def camelize(name: str) -> str: # userService + parts = name.split('-') + return parts[0] + ''.join(w.capitalize() for w in parts[1:]) \ No newline at end of file diff --git a/src/cli/cpl/cli/utils/template_collector.py b/src/cli/cpl/cli/utils/template_collector.py new file mode 100644 index 00000000..10728e61 --- /dev/null +++ b/src/cli/cpl/cli/utils/template_collector.py @@ -0,0 +1,42 @@ +import os +from pathlib import Path + +from cpl.core.console import Console + + +class TemplateCollector: + _templates = {} + _collected_paths = [] + + + @classmethod + def get_templates(cls) -> dict[str, str]: + return cls._templates + + @classmethod + def collect_templates(cls, directory: Path, verbose=False): + if not directory.exists() or not directory.is_dir(): + raise FileNotFoundError(f"Directory '{directory}' does not exist") + + if not str(directory).endswith(".cpl/generate"): + directory = directory / ".cpl" / "generate" + + directory = directory.resolve().absolute() + + if directory in cls._collected_paths: + return + + cls._collected_paths.append(directory) + if not directory.exists() or not directory.is_dir(): + if verbose: + Console.write_line(f"No templates found in {directory}") + return + + templates = {} + for root, _, files in os.walk(directory): + for file in files: + if file.endswith(".schematic"): + with open(os.path.join(root, file), "r") as f: + templates[os.path.relpath(os.path.join(root, file), directory)] = f.read() + + cls._templates.update(templates) diff --git a/src/dependency/cpl/dependency/module/__init__.py b/src/dependency/cpl/dependency/module/__init__.py index e69de29b..8f210ac9 100644 --- a/src/dependency/cpl/dependency/module/__init__.py +++ b/src/dependency/cpl/dependency/module/__init__.py @@ -0,0 +1 @@ +from .module import Module