diff --git a/src/cpl_cli/command/generate_service.py b/src/cpl_cli/command/generate_service.py index a615f095..fa511a58 100644 --- a/src/cpl_cli/command/generate_service.py +++ b/src/cpl_cli/command/generate_service.py @@ -44,7 +44,14 @@ class GenerateService(CommandABC): if len(GenerateSchematicABC.__subclasses__()) == 0: Console.error(f'No schematics found in template directory: .cpl') sys.exit() + + known_schematics = [] for schematic in GenerateSchematicABC.__subclasses__(): + if schematic.__name__ in known_schematics: + Console.error(f'Duplicate of schematic {schematic.__name__} found!') + sys.exit() + + known_schematics.append(schematic.__name__) schematic.register() self._schematics = SchematicCollection.get_schematics() diff --git a/src/cpl_cli/command/new_service.py b/src/cpl_cli/command/new_service.py index 32c27def..eef6e8d4 100644 --- a/src/cpl_cli/command/new_service.py +++ b/src/cpl_cli/command/new_service.py @@ -1,3 +1,4 @@ +import importlib import os import sys import textwrap @@ -18,6 +19,7 @@ from cpl_cli.configuration.project_type_enum import ProjectTypeEnum from cpl_cli.configuration.venv_helper_service import VenvHelper from cpl_cli.configuration.version_settings_name_enum import VersionSettingsNameEnum from cpl_cli.configuration.workspace_settings import WorkspaceSettings +from cpl_cli.helper.dependencies import Dependencies from cpl_cli.source_creator.template_builder import TemplateBuilder from cpl_core.configuration.configuration_abc import ConfigurationABC from cpl_core.console.console import Console @@ -103,9 +105,9 @@ class NewService(CommandABC): self._project.from_dict(self._project_dict) - def _create_build_settings(self, project_type: ProjectTypeEnum): + def _create_build_settings(self, project_type: str): self._build_dict = { - BuildSettingsNameEnum.project_type.value: project_type.value, + BuildSettingsNameEnum.project_type.value: project_type, BuildSettingsNameEnum.source_path.value: '', BuildSettingsNameEnum.output_path.value: '../../dist', BuildSettingsNameEnum.main.value: f'{String.convert_to_snake_case(self._project.name)}.main', @@ -209,14 +211,35 @@ class NewService(CommandABC): Console.error(str(e), traceback.format_exc()) sys.exit(-1) - def _create_project(self, project_type: ProjectTypeEnum): - self._read_custom_project_types_from_path(self._env.runtime_directory) + def _create_project(self, project_type: str): + for package_name in Dependencies.get_cpl_packages(): + package = importlib.import_module(String.convert_to_snake_case(package_name[0])) + self._read_custom_project_types_from_path(os.path.dirname(package.__file__)) + self._read_custom_project_types_from_path(self._env.working_directory) if len(ProjectTypeABC.__subclasses__()) == 0: Console.error(f'No project types found in template directory: .cpl') sys.exit() + project_class = None + known_project_types = [] + for p in ProjectTypeABC.__subclasses__(): + if p.__name__ in known_project_types: + Console.error(f'Duplicate of project type {p.__name__} found!') + sys.exit() + + known_project_types.append(p.__name__) + if p.__name__.lower() != project_type and p.__name__.lower()[0] != project_type[0]: + continue + + project_class = p + + if project_class is None: + Console.error(f'Project type {project_type} not found in template directory: .cpl/') + sys.exit() + + project_type = String.convert_to_snake_case(project_class.__name__) self._create_project_settings() self._create_build_settings(project_type) self._create_project_json() @@ -229,17 +252,6 @@ class NewService(CommandABC): if self._rel_path != '': project_name = f'{self._rel_path}/{project_name}' - project_class = None - for p in ProjectTypeABC.__subclasses__(): - if p.__name__.lower() != project_type.value and p.__name__.lower()[0] != project_type.value[0]: - continue - - project_class = p - - if project_class is None: - Console.error(f'Project type {project_type.value} not found in template directory: .cpl/') - sys.exit() - base = 'src/' split_project_name = project_name.split('/') if self._use_base and len(split_project_name) > 0: @@ -336,22 +348,10 @@ class NewService(CommandABC): 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._create_project(ProjectTypeEnum.console) - - elif console is None and library is not None and unittest is None: - self._name = library - self._create_project(ProjectTypeEnum.library) - - elif console is None and library is None and unittest is not None: - self._name = unittest - self._create_project(ProjectTypeEnum.unittest) - - else: + if len(args) <= 1: Console.error(f'Project type not found') Console.write_line(self.help_message) return + + self._name = args[1] + self._create_project(args[0]) diff --git a/src/cpl_cli/startup_argument_extension.py b/src/cpl_cli/startup_argument_extension.py index 835a9b67..07de63dd 100644 --- a/src/cpl_cli/startup_argument_extension.py +++ b/src/cpl_cli/startup_argument_extension.py @@ -38,9 +38,6 @@ class StartupArgumentExtension(StartupExtensionABC): .add_console_argument(ArgumentTypeEnum.Flag, '--', 'cpl-exp', ['ce', 'CE']) \ .add_console_argument(ArgumentTypeEnum.Flag, '--', 'cpl-dev', ['cd', 'CD']) config.create_console_argument(ArgumentTypeEnum.Executable, '', 'new', ['n', 'N'], NewService, True) \ - .add_console_argument(ArgumentTypeEnum.Variable, '', 'console', ['c', 'C'], ' ') \ - .add_console_argument(ArgumentTypeEnum.Variable, '', 'library', ['l', 'L'], ' ') \ - .add_console_argument(ArgumentTypeEnum.Variable, '', 'unittest', ['ut', 'UT'], ' ') \ .add_console_argument(ArgumentTypeEnum.Flag, '--', 'async', ['a', 'A']) \ .add_console_argument(ArgumentTypeEnum.Flag, '--', 'application-base', ['ab', 'AB']) \ .add_console_argument(ArgumentTypeEnum.Flag, '--', 'startup', ['s', 'S']) \ diff --git a/src/cpl_discord/.cpl/project_discord_bot.py b/src/cpl_discord/.cpl/project_discord_bot.py new file mode 100644 index 00000000..e78d5d8d --- /dev/null +++ b/src/cpl_discord/.cpl/project_discord_bot.py @@ -0,0 +1,54 @@ +import os + +from cpl_cli.abc.project_type_abc import ProjectTypeABC +from cpl_cli.configuration import WorkspaceSettings +from cpl_core.utils import String + + +class DiscordBot(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, + project_file_data: dict, + ): + from project_file_discord import DiscordBotProjectFile + from project_file_discord_appsettings import DiscordBotProjectFileAppsettings + from project_file_discord_code_application import DiscordBotProjectFileApplication + from project_file_discord_code_main import DiscordBotProjectFileMain + from project_file_discord_code_startup import DiscordBotProjectFileStartup + from project_file_discord_readme import DiscordBotProjectFileReadme + from project_file_discord_license import DiscordBotProjectFileLicense + from schematic_discord_init import DiscordBotInit + from schematic_discord_event import Event + from schematic_discord_command import Command + + use_application_api, use_startup, use_service_providing, use_async = True, True, True, True + + ProjectTypeABC.__init__(self, base_path, project_name, workspace, use_application_api, use_startup, use_service_providing, use_async, project_file_data) + + project_path = f'{base_path}{String.convert_to_snake_case(project_name.split("/")[-1])}/' + + self.add_template(DiscordBotProjectFile(project_name.split('/')[-1], project_path, project_file_data)) + if workspace is None: + self.add_template(DiscordBotProjectFileLicense('')) + self.add_template(DiscordBotProjectFileReadme('')) + self.add_template(DiscordBotInit('', 'init', f'{base_path}tests/')) + + self.add_template(DiscordBotInit('', 'init', project_path)) + self.add_template(DiscordBotProjectFileAppsettings(project_path)) + + self.add_template(DiscordBotInit('', 'init', f'{project_path}events/')) + self.add_template(Event('OnReady', 'event', f'{project_path}events/')) + self.add_template(DiscordBotInit('', 'init', f'{project_path}commands/')) + self.add_template(Command('Ping', 'command', f'{project_path}commands/')) + + self.add_template(DiscordBotProjectFileApplication(project_path, use_application_api, use_startup, use_service_providing, use_async)) + self.add_template(DiscordBotProjectFileStartup(project_name.split('/')[-1], project_path, use_application_api, use_startup, use_service_providing, use_async)) + self.add_template(DiscordBotProjectFileMain(project_name.split('/')[-1], project_path, use_application_api, use_startup, use_service_providing, use_async)) diff --git a/src/cpl_discord/.cpl/project_file_discord.py b/src/cpl_discord/.cpl/project_file_discord.py new file mode 100644 index 00000000..1027c949 --- /dev/null +++ b/src/cpl_discord/.cpl/project_file_discord.py @@ -0,0 +1,14 @@ +import json + +from cpl_cli.abc.file_template_abc import FileTemplateABC + + +class DiscordBotProjectFile(FileTemplateABC): + + def __init__(self, name: str, path: str, code: dict): + FileTemplateABC.__init__(self, '', path, '{}') + self._name = f'{name}.json' + self._code = code + + def get_code(self) -> str: + return json.dumps(self._code, indent=2) diff --git a/src/cpl_discord/.cpl/project_file_discord_appsettings.py b/src/cpl_discord/.cpl/project_file_discord_appsettings.py new file mode 100644 index 00000000..cf0c29ad --- /dev/null +++ b/src/cpl_discord/.cpl/project_file_discord_appsettings.py @@ -0,0 +1,34 @@ +import textwrap + +from cpl_cli.abc.file_template_abc import FileTemplateABC + + +class DiscordBotProjectFileAppsettings(FileTemplateABC): + + def __init__(self, path: str): + FileTemplateABC.__init__(self, '', path, '{}') + self._name = 'appsettings.json' + + def get_code(self) -> str: + return 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" + }, + + "DiscordBotSettings": { + "Token": "", + "Prefix": "!bot " + } + } + """) diff --git a/src/cpl_discord/.cpl/project_file_discord_code_application.py b/src/cpl_discord/.cpl/project_file_discord_code_application.py new file mode 100644 index 00000000..dbdd4f23 --- /dev/null +++ b/src/cpl_discord/.cpl/project_file_discord_code_application.py @@ -0,0 +1,53 @@ +from cpl_cli.abc.code_file_template_abc import CodeFileTemplateABC + + +class DiscordBotProjectFileApplication(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 + + 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 + from cpl_core.logging import LoggerABC + from cpl_discord.application.discord_bot_application_abc import DiscordBotApplicationABC + from cpl_discord.configuration.discord_bot_settings import DiscordBotSettings + from cpl_discord.service.discord_bot_service import DiscordBotService + from cpl_discord.service.discord_bot_service_abc import DiscordBotServiceABC + + + class Application(DiscordBotApplicationABC): + + def __init__(self, config: ConfigurationABC, services: ServiceProviderABC): + ApplicationABC.__init__(self, config, services) + + self._bot: DiscordBotServiceABC = services.get_service(DiscordBotServiceABC) + self._logger: LoggerABC = services.get_service(LoggerABC) + self._bot_settings: DiscordBotSettings = config.get_configuration(DiscordBotSettings) + + async def configure(self): + pass + + async def main(self): + try: + self._logger.debug(__name__, f'Starting...\\n') + self._logger.trace(__name__, f'Try to start {DiscordBotService.__name__}') + await self._bot.start_async() + except Exception as e: + self._logger.error(__name__, 'Start failed', e) + + async def stop_async(self): + try: + self._logger.trace(__name__, f'Try to stop {DiscordBotService.__name__}') + await self._bot.close() + self._logger.trace(__name__, f'Stopped {DiscordBotService.__name__}') + except Exception as e: + self._logger.error(__name__, 'stop failed', e) + + Console.write_line() + """) diff --git a/src/cpl_discord/.cpl/project_file_discord_code_main.py b/src/cpl_discord/.cpl/project_file_discord_code_main.py new file mode 100644 index 00000000..e99183ce --- /dev/null +++ b/src/cpl_discord/.cpl/project_file_discord_code_main.py @@ -0,0 +1,47 @@ +from cpl_cli.abc.code_file_template_abc import CodeFileTemplateABC +from cpl_core.utils import String + + +class DiscordBotProjectFileMain(CodeFileTemplateABC): + + def __init__(self, name: str, 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'{String.convert_to_snake_case(name)}.' + + self._main_with_application_host_and_startup = textwrap.dedent(f"""\ + import asyncio + from typing import Optional + + from cpl_core.application import ApplicationBuilder, ApplicationABC + from {import_pkg}application import Application + from {import_pkg}startup import Startup + + + class Program: + + def __init__(self): + self._app: Optional[Application] = None + + async def main(self): + app_builder = ApplicationBuilder(Application) + app_builder.use_startup(Startup) + self._app: ApplicationABC = await app_builder.build_async() + await self._app.run_async() + + async def stop(self): + await self._app.stop_async() + + + if __name__ == '__main__': + program = Program() + try: + asyncio.run(program.main()) + except KeyboardInterrupt: + asyncio.run(program.stop()) + """) + + def get_code(self) -> str: + return self._main_with_application_host_and_startup diff --git a/src/cpl_discord/.cpl/project_file_discord_code_startup.py b/src/cpl_discord/.cpl/project_file_discord_code_startup.py new file mode 100644 index 00000000..885aea67 --- /dev/null +++ b/src/cpl_discord/.cpl/project_file_discord_code_startup.py @@ -0,0 +1,47 @@ +from cpl_cli.abc.code_file_template_abc import CodeFileTemplateABC +from cpl_core.utils import String + + +class DiscordBotProjectFileStartup(CodeFileTemplateABC): + + def __init__(self, project_name: str, 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) + self._project_name = project_name + + def get_code(self) -> str: + import textwrap + + import_pkg = f'{String.convert_to_snake_case(self._project_name)}.' + + return textwrap.dedent(f"""\ + 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 + from cpl_discord import get_discord_collection + from cpl_discord.discord_event_types_enum import DiscordEventTypesEnum + from {import_pkg}commands.ping_command import PingCommand + from {import_pkg}events.on_ready_event import OnReadyEvent + + + class Startup(StartupABC): + + def __init__(self): + StartupABC.__init__(self) + + def configure_configuration(self, configuration: ConfigurationABC, environment: ApplicationEnvironment) -> ConfigurationABC: + configuration.add_json_file('appsettings.json', optional=False) + configuration.add_environment_variables('CPL_') + configuration.add_environment_variables('DISCORD_') + + return configuration + + def configure_services(self, services: ServiceCollectionABC, environment: ApplicationEnvironment) -> ServiceProviderABC: + services.add_logging() + services.add_discord() + dc_collection = get_discord_collection(services) + dc_collection.add_event(DiscordEventTypesEnum.on_ready.value, OnReadyEvent) + dc_collection.add_command(PingCommand) + + return services.build_service_provider() + """) diff --git a/src/cpl_discord/.cpl/project_file_discord_license.py b/src/cpl_discord/.cpl/project_file_discord_license.py new file mode 100644 index 00000000..4592aba0 --- /dev/null +++ b/src/cpl_discord/.cpl/project_file_discord_license.py @@ -0,0 +1,11 @@ +from cpl_cli.abc.file_template_abc import FileTemplateABC + + +class DiscordBotProjectFileLicense(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_discord/.cpl/project_file_discord_readme.py b/src/cpl_discord/.cpl/project_file_discord_readme.py new file mode 100644 index 00000000..e3dceea1 --- /dev/null +++ b/src/cpl_discord/.cpl/project_file_discord_readme.py @@ -0,0 +1,11 @@ +from cpl_cli.abc.file_template_abc import FileTemplateABC + + +class DiscordBotProjectFileReadme(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_discord/.cpl/schematic_command.py b/src/cpl_discord/.cpl/schematic_discord_command.py similarity index 100% rename from src/cpl_discord/.cpl/schematic_command.py rename to src/cpl_discord/.cpl/schematic_discord_command.py diff --git a/src/cpl_discord/.cpl/schematic_event.py b/src/cpl_discord/.cpl/schematic_discord_event.py similarity index 73% rename from src/cpl_discord/.cpl/schematic_event.py rename to src/cpl_discord/.cpl/schematic_discord_event.py index 28e2aa85..43f08354 100644 --- a/src/cpl_discord/.cpl/schematic_event.py +++ b/src/cpl_discord/.cpl/schematic_discord_event.py @@ -6,7 +6,7 @@ from cpl_core.console import Console from cpl_core.utils import String -class Command(GenerateSchematicABC): +class Event(GenerateSchematicABC): def __init__(self, name: str, schematic: str, path: str): GenerateSchematicABC.__init__(self, name, schematic, path) @@ -24,11 +24,17 @@ class Command(GenerateSchematicABC): if event is None: Console.error(f'No valid event found in name {name}') + Console.write_line('Available events:') + for event_type in DiscordEventTypesEnum: + Console.write_line(f'\t{event_type.value.__name__.replace("ABC", "")}') sys.exit() self._event_class = f'{event}ABC' - self._name = f'{String.convert_to_snake_case(name)}_{String.convert_to_snake_case(self._event_class.replace("ABC", ""))}_{schematic}.py' - self._class_name = f'{String.first_to_upper(name)}{self._event_class.replace("ABC", "")}{String.first_to_upper(schematic)}' + self._name = f'{String.convert_to_snake_case(self._event_class.replace("ABC", ""))}_{schematic}.py' + self._class_name = f'{self._event_class.replace("ABC", "")}{String.first_to_upper(schematic)}' + if name != '': + self._name = f'{String.convert_to_snake_case(name)}_{self._name}' + self._class_name = f'{String.first_to_upper(name)}{self._class_name}' def get_code(self) -> str: code = """\ diff --git a/src/cpl_discord/.cpl/schematic_discord_init.py b/src/cpl_discord/.cpl/schematic_discord_init.py new file mode 100644 index 00000000..8b7ce010 --- /dev/null +++ b/src/cpl_discord/.cpl/schematic_discord_init.py @@ -0,0 +1,24 @@ +import textwrap + +from cpl_cli.abc.generate_schematic_abc import GenerateSchematicABC + + +class DiscordBotInit(GenerateSchematicABC): + + def __init__(self, *args: str): + GenerateSchematicABC.__init__(self, *args) + self._name = f'__init__.py' + + def get_code(self) -> str: + code = """\ + # imports + """ + return self.build_code_str(code, Name=self._class_name) + + @classmethod + def register(cls): + GenerateSchematicABC.register( + cls, + 'init', + [] + )