sh_cpl/src/cpl_cli/command/new_service.py

363 lines
13 KiB
Python

import importlib
import os
import sys
import textwrap
import traceback
from typing import Optional
from packaging import version
import cpl_cli
import cpl_core
from cpl_cli.abc.project_type_abc import ProjectTypeABC
from cpl_cli.command_abc import CommandABC
from cpl_cli.configuration import VersionSettings
from cpl_cli.configuration.build_settings import BuildSettings
from cpl_cli.configuration.project_settings import ProjectSettings
from cpl_cli.configuration.project_type_enum import ProjectTypeEnum
from cpl_cli.configuration.settings_helper import SettingsHelper
from cpl_cli.configuration.venv_helper_service import VenvHelper
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
from cpl_core.console.foreground_color_enum import ForegroundColorEnum
from cpl_core.utils.string import String
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_dict = {}
self._build_dict = {}
self._project_name = ""
self._python_executable = ""
self._project_type_classes = set()
self._name: str = ""
self._rel_path: str = ""
self._project_type: 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 <type> <name>
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._project_name = os.path.basename(self._name)
self._python_executable = ProjectSettings(
python_path={sys.platform: "../../venv/" if self._use_venv else ""}
).python_executable
self._rel_path = os.path.dirname(self._name)
self._project_dict = SettingsHelper.get_project_settings_dict(
ProjectSettings(
os.path.basename(self._name),
VersionSettings("0", "0", "0"),
"",
"",
"",
"",
"",
"",
"",
"",
"",
[f"cpl-core>={version.parse(cpl_core.__version__)}"],
[f"cpl-cli>={version.parse(cpl_cli.__version__)}"],
f'>={sys.version.split(" ")[0]}',
{sys.platform: "../../venv/" if self._use_venv else ""},
None,
[],
)
)
def _create_build_settings(self, project_type: str):
self._build_dict = SettingsHelper.get_build_settings_dict(
BuildSettings(
ProjectTypeEnum[project_type],
"",
"../../dist",
f"{String.convert_to_snake_case(self._project_name)}.main",
self._project_name,
False,
[],
["*/__pycache__", "*/logs", "*/tests"],
{},
[],
)
)
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, project_type: str):
"""
Gets project information's from user
:return:
"""
is_unittest = project_type == "unittest"
is_library = project_type == "library"
if is_library:
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 and not is_library:
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 _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._python_executable,
explicit_path=os.path.join(
self._env.working_directory, project, self._python_executable.replace("../", "")
),
)
def _read_custom_project_types_from_path(self, 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 file.startswith("project_file_") or not file.startswith("project_") or not file.endswith(".py"):
continue
try:
exec(open(os.path.join(r, file), "r").read())
self._project_type_classes.update(ProjectTypeABC.__subclasses__())
except Exception as e:
Console.error(str(e), traceback.format_exc())
sys.exit(-1)
def _create_project(self, project_type: str):
for package_name in Dependencies.get_cpl_packages():
if package_name == "cpl-cli":
continue
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)
self._read_custom_project_types_from_path(self._env.runtime_directory)
if len(self._project_type_classes) == 0:
Console.error(f"No project types found in template directory: .cpl")
sys.exit()
project_class = None
known_project_types = []
for p in self._project_type_classes:
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()
path = self._get_project_path()
if path is None:
return
self._get_project_information(project_type)
project_name = self._project_name
if self._rel_path != "":
project_name = f"{self._rel_path}/{project_name}"
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_class(
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,
self._project_json,
)
if self._workspace is None:
TemplateBuilder.create_workspace(
f"{project_name}/cpl-workspace.json",
project_name.split("/")[-1],
{
project_name: f'{base if self._workspace is not None else "src/"}{String.convert_to_snake_case(project_name)}/{project_name}.json'
},
{},
)
else:
self._workspace.projects[
project_name
] = f'{base if self._workspace is not None else "src/"}{String.convert_to_snake_case(project_name)}/{project_name}.json'
TemplateBuilder.create_workspace(
"cpl-workspace.json", self._workspace.default_project, self._workspace.projects, self._workspace.scripts
)
for template in project.templates:
rel_base = "/".join(project_name.split("/")[:-1])
template_path_base = template.path.split("/")[0]
if not self._use_base and rel_base != "" and template_path_base != "" and template_path_base != rel_base:
template.path = template.path.replace(f"{template_path_base}/", f"{template_path_base}/{rel_base}/")
if template.name.endswith(f'{project_name.split("/")[-1]}.json'):
pass
file_path = os.path.join(project_name if self._workspace is None else "", template.path, template.name)
Console.spinner(
f"Creating {file_path}",
TemplateBuilder.build,
file_path,
template,
text_foreground_color=ForegroundColorEnum.green,
spinner_foreground_color=ForegroundColorEnum.cyan,
)
if self._use_venv:
self._create_venv()
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")
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])