From 5f10603fe5c36482e2dd544fe4d77c121f7c2b17 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Mon, 5 Dec 2022 23:08:52 +0100 Subject: [PATCH] [WIP] Improved cpl new templating #139 --- src/cpl_cli/.cpl/project_console.py | 42 +++ src/cpl_cli/.cpl/project_file_appsettings.py | 29 ++ .../.cpl/project_file_code_application.py | 49 +++ src/cpl_cli/.cpl/project_file_code_main.py | 93 +++++ src/cpl_cli/.cpl/project_file_code_startup.py | 29 ++ src/cpl_cli/.cpl/project_file_license.py | 11 + src/cpl_cli/.cpl/project_file_readme.py | 11 + .../{abc_schematic.py => schematic_abc.py} | 0 ...{class_schematic.py => schematic_class.py} | 0 ..._schematic.py => schematic_configmodel.py} | 0 .../{enum_schematic.py => schematic_enum.py} | 0 .../{init_schematic.py => schematic_init.py} | 0 .../{pipe_schematic.py => schematic_pipe.py} | 0 ...vice_schematic.py => schematic_service.py} | 0 ...se_schematic.py => schematic_test_case.py} | 0 ...hread_schematic.py => schematic_thread.py} | 0 ...or_schematic.py => schematic_validator.py} | 0 src/cpl_cli/abc/code_file_template_abc.py | 24 ++ src/cpl_cli/abc/file_template_abc.py | 10 +- src/cpl_cli/abc/generate_schematic_abc.py | 8 +- src/cpl_cli/abc/project_type_abc.py | 35 ++ src/cpl_cli/command/generate_service.py | 2 +- src/cpl_cli/command/new_old_service.py | 356 ++++++++++++++++++ src/cpl_cli/command/new_service.py | 94 ++++- .../source_creator/template_builder.py | 45 ++- ...ustom_schematic.py => schematic_custom.py} | 0 26 files changed, 822 insertions(+), 16 deletions(-) create mode 100644 src/cpl_cli/.cpl/project_console.py create mode 100644 src/cpl_cli/.cpl/project_file_appsettings.py create mode 100644 src/cpl_cli/.cpl/project_file_code_application.py create mode 100644 src/cpl_cli/.cpl/project_file_code_main.py create mode 100644 src/cpl_cli/.cpl/project_file_code_startup.py create mode 100644 src/cpl_cli/.cpl/project_file_license.py create mode 100644 src/cpl_cli/.cpl/project_file_readme.py rename src/cpl_cli/.cpl/{abc_schematic.py => schematic_abc.py} (100%) rename src/cpl_cli/.cpl/{class_schematic.py => schematic_class.py} (100%) rename src/cpl_cli/.cpl/{configmodel_schematic.py => schematic_configmodel.py} (100%) rename src/cpl_cli/.cpl/{enum_schematic.py => schematic_enum.py} (100%) rename src/cpl_cli/.cpl/{init_schematic.py => schematic_init.py} (100%) rename src/cpl_cli/.cpl/{pipe_schematic.py => schematic_pipe.py} (100%) rename src/cpl_cli/.cpl/{service_schematic.py => schematic_service.py} (100%) rename src/cpl_cli/.cpl/{test_case_schematic.py => schematic_test_case.py} (100%) rename src/cpl_cli/.cpl/{thread_schematic.py => schematic_thread.py} (100%) rename src/cpl_cli/.cpl/{validator_schematic.py => schematic_validator.py} (100%) create mode 100644 src/cpl_cli/abc/code_file_template_abc.py create mode 100644 src/cpl_cli/abc/project_type_abc.py create mode 100644 src/cpl_cli/command/new_old_service.py rename tests/custom/general/.cpl/{custom_schematic.py => schematic_custom.py} (100%) diff --git a/src/cpl_cli/.cpl/project_console.py b/src/cpl_cli/.cpl/project_console.py new file mode 100644 index 00000000..7028172d --- /dev/null +++ b/src/cpl_cli/.cpl/project_console.py @@ -0,0 +1,42 @@ +import os + +from cpl_cli.abc.project_type_abc import ProjectTypeABC +from cpl_cli.configuration import WorkspaceSettings +from cpl_core.utils import String + + +class Console(ProjectTypeABC): + + def __init__( + self, + base_path: str, + project_name: str, + workspace: WorkspaceSettings, + use_application_api: bool, + use_startup: bool, + use_service_providing: bool, + use_async: bool, + ): + from project_file_license import ProjectFileLicense + from project_file_readme import ProjectFileReadme + from schematic_init import Init + from project_file_code_application import ProjectFileApplication + from project_file_code_main import ProjectFileMain + from project_file_code_startup import ProjectFileStartup + + ProjectTypeABC.__init__(self, base_path, project_name, workspace, use_application_api, use_startup, use_service_providing, use_async) + + project_path = f'{base_path}{String.convert_to_snake_case(project_name.split("/")[-1])}/' + + self.add_template(ProjectFileLicense('')) + self.add_template(ProjectFileReadme('')) + self.add_template(Init('', 'init', f'{base_path}tests/')) + self.add_template(Init('', 'init', project_path)) + + if use_application_api: + self.add_template(ProjectFileApplication(project_path, use_application_api, use_startup, use_service_providing, use_async)) + + if use_startup: + self.add_template(ProjectFileStartup(project_path, use_application_api, use_startup, use_service_providing, use_async)) + + self.add_template(ProjectFileMain(project_path, use_application_api, use_startup, use_service_providing, use_async)) diff --git a/src/cpl_cli/.cpl/project_file_appsettings.py b/src/cpl_cli/.cpl/project_file_appsettings.py new file mode 100644 index 00000000..e7b0e3ec --- /dev/null +++ b/src/cpl_cli/.cpl/project_file_appsettings.py @@ -0,0 +1,29 @@ +import textwrap + +from cpl_cli.abc.file_template_abc import FileTemplateABC + + +class ProjectFileAppsettings(FileTemplateABC): + + def __init__(self, path: str): + code = textwrap.dedent("""\ + { + "TimeFormatSettings": { + "DateFormat": "%Y-%m-%d", + "TimeFormat": "%H:%M:%S", + "DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f", + "DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S" + }, + + "LoggingSettings": { + "Path": "logs/", + "Filename": "log_$start_time.log", + "ConsoleLogLevel": "ERROR", + "FileLogLevel": "WARN" + } + } + """) + FileTemplateABC.__init__(self, 'appsettings.json', path, code) + + def get_code(self) -> str: + return self._code diff --git a/src/cpl_cli/.cpl/project_file_code_application.py b/src/cpl_cli/.cpl/project_file_code_application.py new file mode 100644 index 00000000..59bc447a --- /dev/null +++ b/src/cpl_cli/.cpl/project_file_code_application.py @@ -0,0 +1,49 @@ +from cpl_cli.abc.code_file_template_abc import CodeFileTemplateABC + + +class ProjectFileApplication(CodeFileTemplateABC): + + def __init__(self, path: str, use_application_api: bool, use_startup: bool, use_service_providing: bool, use_async: bool): + CodeFileTemplateABC.__init__(self, 'application', path, '', use_application_api, use_startup, use_service_providing, use_async) + + def get_code(self) -> str: + import textwrap + + if self._use_async: + return textwrap.dedent("""\ + from cpl_core.application import ApplicationABC + from cpl_core.configuration import ConfigurationABC + from cpl_core.console import Console + from cpl_core.dependency_injection import ServiceProviderABC + + + class Application(ApplicationABC): + + def __init__(self, config: ConfigurationABC, services: ServiceProviderABC): + ApplicationABC.__init__(self, config, services) + + async def configure(self): + pass + + async def main(self): + Console.write_line('Hello World') + """) + + return textwrap.dedent("""\ + from cpl_core.application import ApplicationABC + from cpl_core.configuration import ConfigurationABC + from cpl_core.console import Console + from cpl_core.dependency_injection import ServiceProviderABC + + + class Application(ApplicationABC): + + def __init__(self, config: ConfigurationABC, services: ServiceProviderABC): + ApplicationABC.__init__(self, config, services) + + def configure(self): + pass + + def main(self): + Console.write_line('Hello World') + """) diff --git a/src/cpl_cli/.cpl/project_file_code_main.py b/src/cpl_cli/.cpl/project_file_code_main.py new file mode 100644 index 00000000..43b5988f --- /dev/null +++ b/src/cpl_cli/.cpl/project_file_code_main.py @@ -0,0 +1,93 @@ +from cpl_cli.abc.code_file_template_abc import CodeFileTemplateABC + + +class ProjectFileMain(CodeFileTemplateABC): + + def __init__(self, path: str, use_application_api: bool, use_startup: bool, use_service_providing: bool, use_async: bool): + CodeFileTemplateABC.__init__(self, 'main', path, '', use_application_api, use_startup, use_service_providing, use_async) + + import textwrap + + import_pkg = f'{self._name}.' + + self._main_with_application_host_and_startup = textwrap.dedent(f"""\ + {"import asyncio" if self._use_async else ''} + + from cpl_core.application import ApplicationBuilder + + from {import_pkg}application import Application + from {import_pkg}startup import Startup + + + {self._async()}def main(): + app_builder = ApplicationBuilder(Application) + app_builder.use_startup(Startup) + {"app: Application = await app_builder.build_async()" if self._use_async else ""} + {"await app.run_async()" if self._use_async else "app_builder.build().run()"} + + + if __name__ == '__main__': + {"asyncio.run(main())" if self._use_async else "main()"} + """) + self._main_with_application_base = textwrap.dedent(f"""\ + {"import asyncio" if self._use_async else ''} + + from cpl_core.application import ApplicationBuilder + + from {import_pkg}application import Application + + + {self._async()}def main(): + app_builder = ApplicationBuilder(Application) + {"app: Application = await app_builder.build_async()" if self._use_async else ""} + {"await app.run_async()" if self._use_async else "app_builder.build().run()"} + + + if __name__ == '__main__': + {"asyncio.run(main())" if self._use_async else "main()"} + """) + + self._main_with_dependency_injection = textwrap.dedent(f"""\ + {"import asyncio" if self._use_async else ''} + + from cpl_core.application import ApplicationBuilder + + from {import_pkg}application import Application + + + {self._async()}def configure_configuration() -> ConfigurationABC: + config = Configuration() + return config + + + {self._async()}def configure_services(config: ConfigurationABC) -> ServiceProviderABC: + services = ServiceCollection(config) + return services.build_service_provider() + + + {self._async()}def main(): + config = {self._async()}configure_configuration() + provider = {self._async()}configure_services(config) + Console.write_line('Hello World') + + + if __name__ == '__main__': + {"asyncio.run(main())" if self._use_async else "main()"} + """) + + def _async(self) -> str: + if self._use_async: + return 'async ' + return '' + + def get_code(self) -> str: + if self._use_application_api and self._use_startup: + return self._main_with_application_host_and_startup + + if self._use_application_api: + return self._main_with_application_base + + if self._use_service_providing: + return self._main_with_dependency_injection + + return self._main_with_application_base diff --git a/src/cpl_cli/.cpl/project_file_code_startup.py b/src/cpl_cli/.cpl/project_file_code_startup.py new file mode 100644 index 00000000..51ce07c1 --- /dev/null +++ b/src/cpl_cli/.cpl/project_file_code_startup.py @@ -0,0 +1,29 @@ +from cpl_cli.abc.code_file_template_abc import CodeFileTemplateABC + + +class ProjectFileStartup(CodeFileTemplateABC): + + def __init__(self, path: str, use_application_api: bool, use_startup: bool, use_service_providing: bool, use_async: bool): + CodeFileTemplateABC.__init__(self, 'startup', path, '', use_application_api, use_startup, use_service_providing, use_async) + + def get_code(self) -> str: + import textwrap + + return textwrap.dedent("""\ + from cpl_core.application import StartupABC + from cpl_core.configuration import ConfigurationABC + from cpl_core.dependency_injection import ServiceProviderABC, ServiceCollectionABC + from cpl_core.environment import ApplicationEnvironment + + + class Startup(StartupABC): + + def __init__(self): + StartupABC.__init__(self) + + def configure_configuration(self, configuration: ConfigurationABC, environment: ApplicationEnvironment) -> ConfigurationABC: + return configuration + + def configure_services(self, services: ServiceCollectionABC, environment: ApplicationEnvironment) -> ServiceProviderABC: + return services.build_service_provider() + """) diff --git a/src/cpl_cli/.cpl/project_file_license.py b/src/cpl_cli/.cpl/project_file_license.py new file mode 100644 index 00000000..49030517 --- /dev/null +++ b/src/cpl_cli/.cpl/project_file_license.py @@ -0,0 +1,11 @@ +from cpl_cli.abc.file_template_abc import FileTemplateABC + + +class ProjectFileLicense(FileTemplateABC): + + def __init__(self, path: str): + FileTemplateABC.__init__(self, '', path, '') + self._name = 'LICENSE' + + def get_code(self) -> str: + return self._code diff --git a/src/cpl_cli/.cpl/project_file_readme.py b/src/cpl_cli/.cpl/project_file_readme.py new file mode 100644 index 00000000..ff45ab52 --- /dev/null +++ b/src/cpl_cli/.cpl/project_file_readme.py @@ -0,0 +1,11 @@ +from cpl_cli.abc.file_template_abc import FileTemplateABC + + +class ProjectFileReadme(FileTemplateABC): + + def __init__(self, path: str): + FileTemplateABC.__init__(self, '', path, '') + self._name = 'README.md' + + def get_code(self) -> str: + return self._code diff --git a/src/cpl_cli/.cpl/abc_schematic.py b/src/cpl_cli/.cpl/schematic_abc.py similarity index 100% rename from src/cpl_cli/.cpl/abc_schematic.py rename to src/cpl_cli/.cpl/schematic_abc.py diff --git a/src/cpl_cli/.cpl/class_schematic.py b/src/cpl_cli/.cpl/schematic_class.py similarity index 100% rename from src/cpl_cli/.cpl/class_schematic.py rename to src/cpl_cli/.cpl/schematic_class.py diff --git a/src/cpl_cli/.cpl/configmodel_schematic.py b/src/cpl_cli/.cpl/schematic_configmodel.py similarity index 100% rename from src/cpl_cli/.cpl/configmodel_schematic.py rename to src/cpl_cli/.cpl/schematic_configmodel.py diff --git a/src/cpl_cli/.cpl/enum_schematic.py b/src/cpl_cli/.cpl/schematic_enum.py similarity index 100% rename from src/cpl_cli/.cpl/enum_schematic.py rename to src/cpl_cli/.cpl/schematic_enum.py diff --git a/src/cpl_cli/.cpl/init_schematic.py b/src/cpl_cli/.cpl/schematic_init.py similarity index 100% rename from src/cpl_cli/.cpl/init_schematic.py rename to src/cpl_cli/.cpl/schematic_init.py diff --git a/src/cpl_cli/.cpl/pipe_schematic.py b/src/cpl_cli/.cpl/schematic_pipe.py similarity index 100% rename from src/cpl_cli/.cpl/pipe_schematic.py rename to src/cpl_cli/.cpl/schematic_pipe.py diff --git a/src/cpl_cli/.cpl/service_schematic.py b/src/cpl_cli/.cpl/schematic_service.py similarity index 100% rename from src/cpl_cli/.cpl/service_schematic.py rename to src/cpl_cli/.cpl/schematic_service.py diff --git a/src/cpl_cli/.cpl/test_case_schematic.py b/src/cpl_cli/.cpl/schematic_test_case.py similarity index 100% rename from src/cpl_cli/.cpl/test_case_schematic.py rename to src/cpl_cli/.cpl/schematic_test_case.py diff --git a/src/cpl_cli/.cpl/thread_schematic.py b/src/cpl_cli/.cpl/schematic_thread.py similarity index 100% rename from src/cpl_cli/.cpl/thread_schematic.py rename to src/cpl_cli/.cpl/schematic_thread.py diff --git a/src/cpl_cli/.cpl/validator_schematic.py b/src/cpl_cli/.cpl/schematic_validator.py similarity index 100% rename from src/cpl_cli/.cpl/validator_schematic.py rename to src/cpl_cli/.cpl/schematic_validator.py diff --git a/src/cpl_cli/abc/code_file_template_abc.py b/src/cpl_cli/abc/code_file_template_abc.py new file mode 100644 index 00000000..439d8e4f --- /dev/null +++ b/src/cpl_cli/abc/code_file_template_abc.py @@ -0,0 +1,24 @@ +from abc import ABC, abstractmethod + +from cpl_cli.abc.file_template_abc import FileTemplateABC +from cpl_core.utils import String + + +class CodeFileTemplateABC(FileTemplateABC): + + @abstractmethod + def __init__( + self, + name: str, + path: str, + code: str, + use_application_api: bool, + use_startup: bool, + use_service_providing: bool, + use_async: bool, + ): + FileTemplateABC.__init__(self, name, path, code) + self._use_application_api = use_application_api + self._use_startup = use_startup + self._use_service_providing = use_service_providing + self._use_async = use_async diff --git a/src/cpl_cli/abc/file_template_abc.py b/src/cpl_cli/abc/file_template_abc.py index 09e39f23..e86cd436 100644 --- a/src/cpl_cli/abc/file_template_abc.py +++ b/src/cpl_cli/abc/file_template_abc.py @@ -11,6 +11,9 @@ class FileTemplateABC(ABC): self._path = path self._code = code + def __repr__(self): + return f'<{type(self).__name__} {self._path}{self._name}>' + @property def name(self) -> str: return self._name @@ -18,11 +21,14 @@ class FileTemplateABC(ABC): @property def path(self) -> str: return self._path + + @path.setter + def path(self, value: str): + self._path = value @property def value(self) -> str: return self.get_code() @abstractmethod - def get_code(self) -> str: - return self._code + def get_code(self) -> str: pass diff --git a/src/cpl_cli/abc/generate_schematic_abc.py b/src/cpl_cli/abc/generate_schematic_abc.py index 7254e176..5a0b958d 100644 --- a/src/cpl_cli/abc/generate_schematic_abc.py +++ b/src/cpl_cli/abc/generate_schematic_abc.py @@ -15,7 +15,10 @@ class GenerateSchematicABC(FileTemplateABC): if schematic in name.lower(): self._name = f'{String.convert_to_snake_case(name)}.py' - self._class_name = f'{String.first_to_upper(name)}{String.first_to_upper(schematic)}' + self._class_name = name + if name != '': + self._class_name = f'{String.first_to_upper(name)}{String.first_to_upper(schematic)}' + if schematic in name.lower(): self._class_name = f'{String.first_to_upper(name)}' @@ -24,7 +27,8 @@ class GenerateSchematicABC(FileTemplateABC): return self._class_name @abstractmethod - def get_code(self) -> str: pass + def get_code(self) -> str: + pass @classmethod def build_code_str(cls, code: str, **kwargs) -> str: diff --git a/src/cpl_cli/abc/project_type_abc.py b/src/cpl_cli/abc/project_type_abc.py new file mode 100644 index 00000000..7a6d8532 --- /dev/null +++ b/src/cpl_cli/abc/project_type_abc.py @@ -0,0 +1,35 @@ +from abc import ABC, abstractmethod +from typing import Optional + +from cpl_cli.abc.file_template_abc import FileTemplateABC +from cpl_cli.configuration import WorkspaceSettings + + +class ProjectTypeABC(ABC): + + @abstractmethod + def __init__( + self, + base_path: str, + project_name: str, + workspace: Optional[WorkspaceSettings], + use_application_api: bool, + use_startup: bool, + use_service_providing: bool, + use_async: bool, + ): + self._templates: list[FileTemplateABC] = [] + self._base_path = base_path + self._project_name = project_name + self._workspace = workspace + self._use_application_api = use_application_api + self._use_startup = use_startup + self._use_service_providing = use_service_providing + self._use_async = use_async + + @property + def templates(self) -> list[FileTemplateABC]: + return self._templates + + def add_template(self, t: FileTemplateABC): + self._templates.append(t) diff --git a/src/cpl_cli/command/generate_service.py b/src/cpl_cli/command/generate_service.py index 975770d7..32f2f061 100644 --- a/src/cpl_cli/command/generate_service.py +++ b/src/cpl_cli/command/generate_service.py @@ -144,7 +144,7 @@ class GenerateService(CommandABC): for r, d, f in os.walk(os.path.join(path, '.cpl')): for file in f: - if not file.endswith('_schematic.py'): + if not file.startswith('schematic_') and not file.endswith('.py'): continue code = '' diff --git a/src/cpl_cli/command/new_old_service.py b/src/cpl_cli/command/new_old_service.py new file mode 100644 index 00000000..5715f5cd --- /dev/null +++ b/src/cpl_cli/command/new_old_service.py @@ -0,0 +1,356 @@ +import os +import sys +import textwrap +from typing import Optional + +from packaging import version + +import cpl_cli +import cpl_core +from cpl_cli.configuration.venv_helper_service import VenvHelper +from cpl_cli.source_creator.unittest_builder import UnittestBuilder + +from cpl_core.configuration.configuration_abc import ConfigurationABC +from cpl_core.console.foreground_color_enum import ForegroundColorEnum +from cpl_core.console.console import Console +from cpl_core.utils.string import String +from cpl_cli.command_abc import CommandABC +from cpl_cli.configuration.build_settings import BuildSettings +from cpl_cli.configuration.build_settings_name_enum import BuildSettingsNameEnum +from cpl_cli.configuration.project_settings import ProjectSettings +from cpl_cli.configuration.project_settings_name_enum import ProjectSettingsNameEnum +from cpl_cli.configuration.project_type_enum import ProjectTypeEnum +from cpl_cli.configuration.version_settings_name_enum import VersionSettingsNameEnum +from cpl_cli.configuration.workspace_settings import WorkspaceSettings +from cpl_cli.source_creator.console_builder import ConsoleBuilder +from cpl_cli.source_creator.library_builder import LibraryBuilder + + +class NewService(CommandABC): + + def __init__(self, configuration: ConfigurationABC): + """ + Service for the CLI command new + :param configuration: + """ + CommandABC.__init__(self) + + self._config = configuration + self._env = self._config.environment + + self._workspace: WorkspaceSettings = self._config.get_configuration(WorkspaceSettings) + self._project: ProjectSettings = ProjectSettings() + self._project_dict = {} + self._build: BuildSettings = BuildSettings() + self._build_dict = {} + self._project_json = {} + + self._name: str = '' + self._rel_path: str = '' + self._schematic: ProjectTypeEnum = ProjectTypeEnum.console + self._use_nothing: bool = False + self._use_application_api: bool = False + self._use_startup: bool = False + self._use_service_providing: bool = False + self._use_async: bool = False + self._use_venv: bool = False + self._use_base: bool = False + + @property + def help_message(self) -> str: + return textwrap.dedent("""\ + Generates a workspace and initial project or add a project to workspace. + Usage: cpl new + + Arguments: + type The project type of the initial project + name Name of the workspace or the project + + Types: + console (c|C) + library (l|L) + unittest (ut|UT) + """) + + def _create_project_settings(self): + self._rel_path = os.path.dirname(self._name) + self._project_dict = { + ProjectSettingsNameEnum.name.value: os.path.basename(self._name), + ProjectSettingsNameEnum.version.value: { + VersionSettingsNameEnum.major.value: '0', + VersionSettingsNameEnum.minor.value: '0', + VersionSettingsNameEnum.micro.value: '0' + }, + ProjectSettingsNameEnum.author.value: '', + ProjectSettingsNameEnum.author_email.value: '', + ProjectSettingsNameEnum.description.value: '', + ProjectSettingsNameEnum.long_description.value: '', + ProjectSettingsNameEnum.url.value: '', + ProjectSettingsNameEnum.copyright_date.value: '', + ProjectSettingsNameEnum.copyright_name.value: '', + ProjectSettingsNameEnum.license_name.value: '', + ProjectSettingsNameEnum.license_description.value: '', + ProjectSettingsNameEnum.dependencies.value: [ + f'cpl-core>={version.parse(cpl_core.__version__)}' + ], + ProjectSettingsNameEnum.dev_dependencies.value: [ + f'cpl-cli>={version.parse(cpl_cli.__version__)}' + ], + ProjectSettingsNameEnum.python_version.value: f'>={sys.version.split(" ")[0]}', + ProjectSettingsNameEnum.python_path.value: { + sys.platform: '../../venv/bin/python' if self._use_venv else '' + }, + ProjectSettingsNameEnum.classifiers.value: [] + } + + self._project.from_dict(self._project_dict) + + def _create_build_settings(self): + self._build_dict = { + BuildSettingsNameEnum.project_type.value: self._schematic, + BuildSettingsNameEnum.source_path.value: '', + BuildSettingsNameEnum.output_path.value: '../../dist', + BuildSettingsNameEnum.main.value: f'{String.convert_to_snake_case(self._project.name)}.main', + BuildSettingsNameEnum.entry_point.value: self._project.name, + BuildSettingsNameEnum.include_package_data.value: False, + BuildSettingsNameEnum.included.value: [], + BuildSettingsNameEnum.excluded.value: [ + '*/__pycache__', + '*/logs', + '*/tests' + ], + BuildSettingsNameEnum.package_data.value: {}, + BuildSettingsNameEnum.project_references.value: [] + } + self._build.from_dict(self._build_dict) + + def _create_project_json(self): + """ + Creates cpl.json content + :return: + """ + self._project_json = { + ProjectSettings.__name__: self._project_dict, + BuildSettings.__name__: self._build_dict + } + + def _get_project_path(self) -> Optional[str]: + """ + Gets project path + :return: + """ + if self._workspace is None: + project_path = os.path.join(self._env.working_directory, self._rel_path, self._project.name) + else: + base = '' if self._use_base else 'src' + project_path = os.path.join(self._env.working_directory, base, self._rel_path, String.convert_to_snake_case(self._project.name)) + + if os.path.isdir(project_path) and len(os.listdir(project_path)) > 0: + Console.write_line(project_path) + Console.error('Project path is not empty\n') + return None + + return project_path + + def _get_project_information(self, is_unittest=False): + """ + Gets project information's from user + :return: + """ + if self._use_application_api or self._use_startup or self._use_service_providing or self._use_async or self._use_nothing: + Console.set_foreground_color(ForegroundColorEnum.default) + Console.write_line('Skipping question due to given flags') + return + + if not is_unittest: + self._use_application_api = Console.read('Do you want to use application base? (y/n) ').lower() == 'y' + + if not is_unittest and self._use_application_api: + self._use_startup = Console.read('Do you want to use startup? (y/n) ').lower() == 'y' + + if not is_unittest and not self._use_application_api: + self._use_service_providing = Console.read('Do you want to use service providing? (y/n) ').lower() == 'y' + + if not self._use_async: + self._use_async = Console.read('Do you want to use async? (y/n) ').lower() == 'y' + + Console.set_foreground_color(ForegroundColorEnum.default) + + def _console(self, args: list[str]): + """ + Generates new console project + :param args: + :return: + """ + self._create_project_settings() + self._create_build_settings() + self._create_project_json() + path = self._get_project_path() + if path is None: + return + + self._get_project_information() + project_name = self._project.name + if self._rel_path != '': + project_name = f'{self._rel_path}/{project_name}' + try: + ConsoleBuilder.build( + path, + self._use_application_api, + self._use_startup, + self._use_service_providing, + self._use_async, + project_name, + self._project_json, + self._workspace + ) + except Exception as e: + Console.error('Could not create project', str(e)) + + def _unittest(self, args: list[str]): + """ + Generates new unittest project + :param args: + :return: + """ + self._create_project_settings() + self._create_build_settings() + self._create_project_json() + path = self._get_project_path() + if path is None: + return + + self._get_project_information(is_unittest=True) + project_name = self._project.name + if self._rel_path != '': + project_name = f'{self._rel_path}/{project_name}' + try: + UnittestBuilder.build( + path, + self._use_application_api, + self._use_async, + project_name, + self._project_json, + self._workspace + ) + except Exception as e: + Console.error('Could not create project', str(e)) + + def _library(self, args: list[str]): + """ + Generates new library project + :param args: + :return: + """ + self._create_project_settings() + self._create_build_settings() + self._create_project_json() + path = self._get_project_path() + if path is None: + return + + self._get_project_information() + project_name = self._project.name + if self._rel_path != '': + project_name = f'{self._rel_path}/{project_name}' + try: + LibraryBuilder.build( + path, + self._use_application_api, + self._use_startup, + self._use_service_providing, + self._use_async, + project_name, + self._project_json, + self._workspace + ) + except Exception as e: + Console.error('Could not create project', str(e)) + + def _create_venv(self): + + project = self._project.name + if self._workspace is not None: + project = self._workspace.default_project + + if self._env.working_directory.endswith(project): + project = '' + + if self._workspace is None and self._use_base: + project = f'{self._rel_path}/{project}' + + VenvHelper.init_venv( + False, + self._env, + self._project, + explicit_path=os.path.join(self._env.working_directory, project, self._project.python_executable.replace('../', '')) + ) + + def execute(self, args: list[str]): + """ + Entry point of command + :param args: + :return: + """ + if 'nothing' in args: + self._use_nothing = True + self._use_async = False + self._use_application_api = False + self._use_startup = False + self._use_service_providing = False + if 'async' in args: + args.remove('async') + if 'application-base' in args: + args.remove('application-base') + if 'startup' in args: + args.remove('startup') + if 'service-providing' in args: + args.remove('service-providing') + + if 'async' in args: + self._use_async = True + args.remove('async') + if 'application-base' in args: + self._use_application_api = True + args.remove('application-base') + if 'startup' in args: + self._use_startup = True + args.remove('startup') + if 'service-providing' in args: + self._use_service_providing = True + args.remove('service-providing') + if 'venv' in args: + self._use_venv = True + args.remove('venv') + if 'base' in args: + self._use_base = True + args.remove('base') + + console = self._config.get_configuration(ProjectTypeEnum.console.value) + library = self._config.get_configuration(ProjectTypeEnum.library.value) + unittest = self._config.get_configuration(ProjectTypeEnum.unittest.value) + if console is not None and library is None and unittest is None: + self._name = console + self._schematic = ProjectTypeEnum.console.value + self._console(args) + if self._use_venv: + self._create_venv() + + elif console is None and library is not None and unittest is None: + self._name = library + self._schematic = ProjectTypeEnum.library.value + self._library(args) + if self._use_venv: + self._create_venv() + + elif console is None and library is None and unittest is not None: + self._name = unittest + self._schematic = ProjectTypeEnum.unittest.value + self._unittest(args) + if self._use_venv: + self._create_venv() + + else: + Console.error(f'Project type not found') + Console.write_line(self.help_message) + return diff --git a/src/cpl_cli/command/new_service.py b/src/cpl_cli/command/new_service.py index 5715f5cd..8b399d58 100644 --- a/src/cpl_cli/command/new_service.py +++ b/src/cpl_cli/command/new_service.py @@ -7,7 +7,9 @@ from packaging import version import cpl_cli import cpl_core +from cpl_cli.abc.project_type_abc import ProjectTypeABC from cpl_cli.configuration.venv_helper_service import VenvHelper +from cpl_cli.source_creator.template_builder import TemplateBuilder from cpl_cli.source_creator.unittest_builder import UnittestBuilder from cpl_core.configuration.configuration_abc import ConfigurationABC @@ -47,7 +49,7 @@ class NewService(CommandABC): self._name: str = '' self._rel_path: str = '' - self._schematic: ProjectTypeEnum = ProjectTypeEnum.console + self._project_type: ProjectTypeEnum = ProjectTypeEnum.console self._use_nothing: bool = False self._use_application_api: bool = False self._use_startup: bool = False @@ -107,7 +109,7 @@ class NewService(CommandABC): def _create_build_settings(self): self._build_dict = { - BuildSettingsNameEnum.project_type.value: self._schematic, + BuildSettingsNameEnum.project_type.value: self._project_type, BuildSettingsNameEnum.source_path.value: '', BuildSettingsNameEnum.output_path.value: '../../dist', BuildSettingsNameEnum.main.value: f'{String.convert_to_snake_case(self._project.name)}.main', @@ -286,6 +288,85 @@ class NewService(CommandABC): explicit_path=os.path.join(self._env.working_directory, project, self._project.python_executable.replace('../', '')) ) + @staticmethod + def _read_custom_project_types_from_path(path: str): + if not os.path.exists(os.path.join(path, '.cpl')): + return + + sys.path.insert(0, os.path.join(path, '.cpl')) + for r, d, f in os.walk(os.path.join(path, '.cpl')): + for file in f: + if not file.startswith('project_') or not file.endswith('.py'): + continue + + code = '' + with open(os.path.join(r, file), 'r') as py_file: + code = py_file.read() + py_file.close() + + exec(code) + + def _create_project(self): + self._read_custom_project_types_from_path(self._env.runtime_directory) + self._read_custom_project_types_from_path(self._env.working_directory) + + self._create_project_settings() + self._create_build_settings() + self._create_project_json() + path = self._get_project_path() + if path is None: + return + + self._get_project_information() + project_name = self._project.name + if self._rel_path != '': + project_name = f'{self._rel_path}/{project_name}' + + project_type = None + for p in ProjectTypeABC.__subclasses__(): + if p.__name__.lower() != self._project_type: + continue + + project_type = p + + base = 'src/' + split_project_name = project_name.split('/') + if self._use_base and len(split_project_name) > 0: + base = f'{split_project_name[0]}/' + + project = project_type( + base if self._workspace is not None else 'src/', + project_name, + self._workspace, + self._use_application_api, + self._use_startup, + self._use_service_providing, + self._use_async, + ) + + if self._workspace is None: + TemplateBuilder.create_workspace( + f'{project_name}/cpl-workspace.json', + project_name.split('/')[-1], + { + project_name: project_name + }, + {} + ) + else: + self._workspace.projects[project_name] = f'{base}{String.convert_to_snake_case(project_name.split("/")[-1])}' + TemplateBuilder.create_workspace('cpl-workspace.json', self._workspace.default_project, self._workspace.projects, self._workspace.scripts) + + for template in project.templates: + Console.spinner( + f'Creating {os.path.join(project_name, template.path, template.name)}', + TemplateBuilder.build, + project_name, + template, + text_foreground_color=ForegroundColorEnum.green, + spinner_foreground_color=ForegroundColorEnum.cyan + ) + def execute(self, args: list[str]): """ Entry point of command @@ -331,21 +412,22 @@ class NewService(CommandABC): unittest = self._config.get_configuration(ProjectTypeEnum.unittest.value) if console is not None and library is None and unittest is None: self._name = console - self._schematic = ProjectTypeEnum.console.value - self._console(args) + self._project_type = ProjectTypeEnum.console.value + self._create_project() + # self._console(args) if self._use_venv: self._create_venv() elif console is None and library is not None and unittest is None: self._name = library - self._schematic = ProjectTypeEnum.library.value + self._project_type = ProjectTypeEnum.library.value self._library(args) if self._use_venv: self._create_venv() elif console is None and library is None and unittest is not None: self._name = unittest - self._schematic = ProjectTypeEnum.unittest.value + self._project_type = ProjectTypeEnum.unittest.value self._unittest(args) if self._use_venv: self._create_venv() diff --git a/src/cpl_cli/source_creator/template_builder.py b/src/cpl_cli/source_creator/template_builder.py index 20939a64..c661cd7a 100644 --- a/src/cpl_cli/source_creator/template_builder.py +++ b/src/cpl_cli/source_creator/template_builder.py @@ -1,12 +1,49 @@ +import json import os +from typing import Union from cpl_cli._templates.template_file_abc import TemplateFileABC +from cpl_cli.abc.file_template_abc import FileTemplateABC +from cpl_cli.configuration import WorkspaceSettings, WorkspaceSettingsNameEnum +from cpl_core.console import Console, ForegroundColorEnum class TemplateBuilder: @staticmethod - def build(project_path: str, template: TemplateFileABC): + def _create_file(file_name: str, content: dict): + if not os.path.isabs(file_name): + file_name = os.path.abspath(file_name) + + path = os.path.dirname(file_name) + if not os.path.isdir(path): + os.makedirs(path) + + with open(file_name, 'w') as project_json: + project_json.write(json.dumps(content, indent=2)) + project_json.close() + + @classmethod + def create_workspace(cls, path: str, project_name, projects: dict, scripts: dict): + ws_dict = { + WorkspaceSettings.__name__: { + WorkspaceSettingsNameEnum.default_project.value: project_name, + WorkspaceSettingsNameEnum.projects.value: projects, + WorkspaceSettingsNameEnum.scripts.value: scripts + } + } + + Console.spinner( + f'Creating {path}', + cls._create_file, + path, + ws_dict, + text_foreground_color=ForegroundColorEnum.green, + spinner_foreground_color=ForegroundColorEnum.cyan + ) + + @staticmethod + def build(project_path: str, template: Union[TemplateFileABC, FileTemplateABC]): """ Creates template :param project_path: @@ -14,10 +51,8 @@ class TemplateBuilder: :return: """ file_path = os.path.join(project_path, template.path, template.name) - file_rel_path = os.path.join(project_path, template.path) - - if not os.path.isdir(file_rel_path): - os.makedirs(file_rel_path) + if not os.path.isdir(os.path.dirname(file_path)): + os.makedirs(os.path.dirname(file_path)) with open(file_path, 'w') as file: file.write(template.value) diff --git a/tests/custom/general/.cpl/custom_schematic.py b/tests/custom/general/.cpl/schematic_custom.py similarity index 100% rename from tests/custom/general/.cpl/custom_schematic.py rename to tests/custom/general/.cpl/schematic_custom.py