diff --git a/cpl.workspace.json b/cpl.workspace.json index b0e9f4da..c45ad1ab 100644 --- a/cpl.workspace.json +++ b/cpl.workspace.json @@ -2,7 +2,8 @@ "name": "cpl", "projects": [ "src/cli/cpl.project.json", - "src/core/cpl.project.json" + "src/core/cpl.project.json", + "test/cpl.project.json" ], "defaultProject": "cpl-cli", "scripts": { diff --git a/src/application/cpl/application/application_builder.py b/src/application/cpl/application/application_builder.py index 3d3d1529..97b58154 100644 --- a/src/application/cpl/application/application_builder.py +++ b/src/application/cpl/application/application_builder.py @@ -14,7 +14,7 @@ TApp = TypeVar("TApp", bound=ApplicationABC) class ApplicationBuilder(Generic[TApp]): - def __init__(self, app: Type[ApplicationABC]): + def __init__(self, app: Type[TApp]): assert app is not None, "app must not be None" assert issubclass(app, ApplicationABC), "app must be an subclass of ApplicationABC or its subclass" diff --git a/src/application/cpl/application/host.py b/src/application/cpl/application/host.py index f1309beb..0aeac147 100644 --- a/src/application/cpl/application/host.py +++ b/src/application/cpl/application/host.py @@ -1,7 +1,9 @@ import asyncio from typing import Callable -from cpl.dependency import get_provider +from cpl.core.property import classproperty +from cpl.dependency.context import get_provider, use_root_provider +from cpl.dependency.service_collection import ServiceCollection from cpl.dependency.hosted.startup_task import StartupTask @@ -9,6 +11,24 @@ class Host: _loop: asyncio.AbstractEventLoop | None = None _tasks: dict = {} + _service_collection: ServiceCollection | None = None + + @classproperty + def services(cls) -> ServiceCollection: + if cls._service_collection is None: + cls._service_collection = ServiceCollection() + + return cls._service_collection + + @classmethod + def get_provider(cls): + provider = get_provider() + if provider is None: + provider = cls.services.build() + use_root_provider(provider) + + return provider + @classmethod def get_loop(cls) -> asyncio.AbstractEventLoop: if cls._loop is None: @@ -18,7 +38,7 @@ class Host: @classmethod def run_start_tasks(cls): - provider = get_provider() + provider = cls.get_provider() tasks = provider.get_services(StartupTask) loop = cls.get_loop() @@ -30,7 +50,7 @@ class Host: @classmethod def run_hosted_services(cls): - provider = get_provider() + provider = cls.get_provider() services = provider.get_hosted_services() loop = cls.get_loop() @@ -49,6 +69,10 @@ class Host: cls._tasks.clear() + @classmethod + async def wait_for_all(cls): + await asyncio.gather(*cls._tasks.values()) + @classmethod def run_app(cls, func: Callable, *args, **kwargs): cls.run_start_tasks() diff --git a/src/cli/cpl/cli/command/package/add.py b/src/cli/cpl/cli/command/package/add.py index ee4afc5d..c5c21e63 100644 --- a/src/cli/cpl/cli/command/package/add.py +++ b/src/cli/cpl/cli/command/package/add.py @@ -3,7 +3,7 @@ from pathlib import Path import click from cpl.cli.cli import cli -from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.cli.utils.structure import Structure from cpl.core.console import Console @@ -12,8 +12,8 @@ from cpl.core.console import Console @click.argument("target", type=click.STRING, required=True) @click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") def add(reference: str, target: str, verbose: bool): - reference_project = get_project_by_name_or_path(reference) - target_project = get_project_by_name_or_path(target) + reference_project = Structure.get_project_by_name_or_path(reference) + target_project = Structure.get_project_by_name_or_path(target) if reference_project.name == target_project.name: raise ValueError("Cannot add a project as a dependency to itself!") diff --git a/src/cli/cpl/cli/command/package/install.py b/src/cli/cpl/cli/command/package/install.py index fd1bb93f..b06d68f8 100644 --- a/src/cli/cpl/cli/command/package/install.py +++ b/src/cli/cpl/cli/command/package/install.py @@ -4,7 +4,7 @@ import click from cpl.cli.cli import cli from cpl.cli.utils.pip import Pip -from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.cli.utils.structure import Structure from cpl.core.console import Console @@ -14,7 +14,7 @@ from cpl.core.console import Console @click.option("--dev", is_flag=True, help="Include dev dependencies") @click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") def install(package: str, project: str, dev: bool, verbose: bool): - project = get_project_by_name_or_path(project or "./") + project = Structure.get_project_by_name_or_path(project or "./") if package is not None: Console.write_line(f"Installing {package} to '{project.name}':") diff --git a/src/cli/cpl/cli/command/package/remove.py b/src/cli/cpl/cli/command/package/remove.py index 230580fd..c1e8e3d6 100644 --- a/src/cli/cpl/cli/command/package/remove.py +++ b/src/cli/cpl/cli/command/package/remove.py @@ -3,7 +3,7 @@ from pathlib import Path import click from cpl.cli.cli import cli -from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.cli.utils.structure import Structure from cpl.core.console import Console @@ -11,8 +11,8 @@ from cpl.core.console import Console @click.argument("reference", type=click.STRING, required=True) @click.argument("target", type=click.STRING, required=True) def remove(reference: str, target: str): - reference_project = get_project_by_name_or_path(reference) - target_project = get_project_by_name_or_path(target) + reference_project = Structure.get_project_by_name_or_path(reference) + target_project = Structure.get_project_by_name_or_path(target) if reference_project.name == target_project.name: raise ValueError("Cannot add a project as a dependency to itself!") diff --git a/src/cli/cpl/cli/command/package/uninstall.py b/src/cli/cpl/cli/command/package/uninstall.py index f856b41a..9718c608 100644 --- a/src/cli/cpl/cli/command/package/uninstall.py +++ b/src/cli/cpl/cli/command/package/uninstall.py @@ -4,7 +4,7 @@ import click from cpl.cli.cli import cli from cpl.cli.utils.pip import Pip -from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.cli.utils.structure import Structure from cpl.core.console import Console @@ -17,7 +17,7 @@ def uninstall(package: str, project: str, dev: bool, verbose: bool): if package is None: package = Console.read("Package name to uninstall: ").strip() - project = get_project_by_name_or_path(project or "./") + project = Structure.get_project_by_name_or_path(project or "./") deps = project.dependencies if not dev else project.dev_dependencies diff --git a/src/cli/cpl/cli/command/package/update.py b/src/cli/cpl/cli/command/package/update.py index ea602dc1..e37ed395 100644 --- a/src/cli/cpl/cli/command/package/update.py +++ b/src/cli/cpl/cli/command/package/update.py @@ -4,7 +4,7 @@ import click from cpl.cli.cli import cli from cpl.cli.utils.pip import Pip -from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.cli.utils.structure import Structure from cpl.core.console import Console @@ -14,7 +14,7 @@ from cpl.core.console import Console @click.option("--dev", is_flag=True, help="Include dev dependencies") @click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") def update(package: str, project: str, dev: bool, verbose: bool): - project = get_project_by_name_or_path(project or "./") + project = Structure.get_project_by_name_or_path(project or "./") deps: dict = project.dependencies if dev: diff --git a/src/cli/cpl/cli/command/project/build.py b/src/cli/cpl/cli/command/project/build.py index 6a37c2fc..931e7266 100644 --- a/src/cli/cpl/cli/command/project/build.py +++ b/src/cli/cpl/cli/command/project/build.py @@ -5,8 +5,6 @@ from pathlib import Path import click from cpl.cli.cli import cli -from cpl.cli.model.project import Project -from cpl.cli.utils.structure import get_project_by_name_or_path from cpl.cli.utils.venv import ensure_venv, get_venv_python from cpl.core.configuration import Configuration from cpl.core.console import Console @@ -18,7 +16,9 @@ from cpl.core.console import Console @click.option("--skip-py-build", "-spb", is_flag=True, help="Skip toml generation and python build") @click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") def build(project: str, dist: str = None, skip_py_build: bool = None, verbose: bool = None): - project = get_project_by_name_or_path(project or "./") + from cpl.cli.utils.structure import Structure + + project = Structure.get_project_by_name_or_path(project or "./") venv = ensure_venv().absolute() dist_path = dist or Path(project.path).parent / "dist" @@ -38,9 +38,7 @@ def build(project: str, dist: str = None, skip_py_build: bool = None, verbose: b Console.write_line("\nDone!") return - from cpl.cli.utils.structure import create_pyproject_toml - - create_pyproject_toml(project, dist_path / project.name) + Structure.create_pyproject_toml(project, dist_path / project.name) python = str(get_venv_python(venv)) result = Console.spinner( diff --git a/src/cli/cpl/cli/command/project/run.py b/src/cli/cpl/cli/command/project/run.py index 8b724a81..fb5a93ec 100644 --- a/src/cli/cpl/cli/command/project/run.py +++ b/src/cli/cpl/cli/command/project/run.py @@ -5,19 +5,20 @@ from pathlib import Path import click from cpl.cli.cli import cli -from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.cli.utils.structure import Structure from cpl.cli.utils.venv import get_venv_python, ensure_venv from cpl.core.configuration import Configuration from cpl.core.console import Console @cli.command("run", aliases=["r"]) +@click.argument("project", type=str) @click.argument("args", nargs=-1) -@click.option("--project", "-p", type=str) @click.option("--dev", "-d", is_flag=True, help="Use sources instead of build output") @click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") def run(project: str, args: list[str], dev: bool, verbose: bool): - project = get_project_by_name_or_path(project or "./") + Console.error(project) + project = Structure.get_project_by_name_or_path(project or "./") if project.main is None: Console.error(f"Project {project.name} has no executable") return diff --git a/src/cli/cpl/cli/command/project/start.py b/src/cli/cpl/cli/command/project/start.py index a380dcba..41f3ce1e 100644 --- a/src/cli/cpl/cli/command/project/start.py +++ b/src/cli/cpl/cli/command/project/start.py @@ -2,17 +2,17 @@ import click from cpl.cli.cli import cli from cpl.cli.utils.live_server.live_server import LiveServer -from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.cli.utils.structure import Structure from cpl.core.console import Console @cli.command("start", aliases=["s"]) +@click.argument("project", type=str) @click.argument("args", nargs=-1) -@click.option("--project", "-p", type=str) @click.option("--dev", "-d", is_flag=True, help="Use sources instead of build output") @click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") def start(project: str, args: list[str], dev: bool, verbose: bool): - project = get_project_by_name_or_path(project or "./") + project = Structure.get_project_by_name_or_path(project or "./") if project.main is None: Console.error(f"Project {project.name} has no executable") return diff --git a/src/cli/cpl/cli/command/structure/generate.py b/src/cli/cpl/cli/command/structure/generate.py index 150c75f1..67e74096 100644 --- a/src/cli/cpl/cli/command/structure/generate.py +++ b/src/cli/cpl/cli/command/structure/generate.py @@ -7,7 +7,7 @@ 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.structure import Structure from cpl.cli.utils.template_collector import TemplateCollector from cpl.cli.utils.template_renderer import TemplateRenderer from cpl.core.configuration import Configuration @@ -29,7 +29,7 @@ def generate(schematic: str, name: str, verbose: bool) -> None: project = None try: - project = get_project_by_name_or_path("./") + project = Structure.get_project_by_name_or_path("./") if verbose: Console.write_line("project found, collecting templates...") diff --git a/src/cli/cpl/cli/command/structure/init.py b/src/cli/cpl/cli/command/structure/init.py index ff4bc7cd..a8323fa4 100644 --- a/src/cli/cpl/cli/command/structure/init.py +++ b/src/cli/cpl/cli/command/structure/init.py @@ -3,9 +3,8 @@ from pathlib import Path import click from cpl.cli.const import PROJECT_TYPES, PROJECT_TYPES_SHORT -from cpl.cli.model.project import Project -from cpl.cli.model.workspace import Workspace from cpl.cli.utils.prompt import SmartChoice +from cpl.cli.utils.structure import Structure from cpl.core.console import Console @@ -25,62 +24,10 @@ def init(target: str, name: str): target = [pt for pt in PROJECT_TYPES if pt.startswith(target)][0] if target in ["workspace", "ws"]: - _init_workspace(name or click.prompt("Workspace name", default="my-workspace")) + Structure.init_workspace("./", name or click.prompt("Workspace name", default="my-workspace")) elif target in PROJECT_TYPES: - _init_project(name or click.prompt("Project name", default=f"my-{target}"), target) + workspace = Structure.find_workspace_in_path(Path(name).parent) + Structure.init_project("./", name or click.prompt("Project name", default=f"my-{target}"), target, workspace) else: Console.error(f"Unknown target '{target}'") raise SystemExit(1) - - -def _init_workspace(name: str): - path = Path("cpl.workspace.json") - - if path.exists(): - Console.write_line("workspace.json already exists.") - return - - data = { - "name": name, - "projects": [], - "defaultProject": None, - "scripts": {}, - } - workspace = Workspace.new("./", name) - workspace.save() - - Console.write_line(f"Created workspace '{name}'") - - -def _init_project(name: str, project_type: str): - project = Project.new("./", name, project_type) - - path = Path("cpl.project.json") - if path.exists(): - Console.write_line("cpl.project.json already exists.") - return - - if not Path("src").exists(): - project.directory = click.prompt( - "Project directory", type=click.Path(exists=True, file_okay=False), default="src" - ) - - if project_type in ["console", "web", "service"]: - project.main = click.prompt( - "Main executable", type=click.Path(exists=True, dir_okay=False), default="src/main.py" - ) - - project.save() - - workspace_file = Path("../cpl.workspace.json") - if workspace_file.exists(): - workspace = Workspace.from_file(workspace_file) - - rel_path = str(Path.cwd().relative_to(workspace_file.parent)) - if rel_path not in workspace.projects: - workspace.projects.append(rel_path) - workspace.save() - - Console.write_line(f"Registered '{name}' in workspace.json") - - Console.write_line(f"Created {project_type} project '{name}'") diff --git a/src/cli/cpl/cli/command/structure/new.py b/src/cli/cpl/cli/command/structure/new.py new file mode 100644 index 00000000..be3a74ae --- /dev/null +++ b/src/cli/cpl/cli/command/structure/new.py @@ -0,0 +1,64 @@ +import os +from pathlib import Path + +import click +from cpl.cli import cli as clim +from cpl.cli.cli import cli +from cpl.cli.const import PROJECT_TYPES, PROJECT_TYPES_SHORT +from cpl.cli.model.workspace import Workspace +from cpl.cli.utils.structure import Structure +from cpl.core.console import Console + + +@cli.command("new", aliases=["n"]) +@click.argument("type", type=click.STRING, required=True) +@click.argument("name", type=click.STRING, required=True) +@click.option("--name", "in_name", type=click.STRING, help="Name of the workspace or project to create.") +@click.option( + "--project", + "-p", + nargs=2, + metavar=" ", + help="Optional: when creating a workspace, also create a project with the given name and type.", +) +@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") +def new(type: str, name: str, in_name: str | None, project: list[str] | None, verbose: bool) -> None: + workspace_created = False + path = Path(name).parent + project_name = in_name or Path(name).stem + + if type in ["workspace", "ws"]: + Structure.init_workspace(name, project_name) + workspace = Workspace.from_file(Path(name) / "cpl.workspace.json") + workspace_created = True + + if len(project) == 2: + type = project[0] + if type not in PROJECT_TYPES + PROJECT_TYPES_SHORT: + raise ValueError(f"Unknown project type '{type}'") + + path = Path(workspace.path).parent / Path(project[1]).parent + project_name = Path(project[1]).stem + + workspace = Structure.find_workspace_in_path(path) + if workspace is None: + Console.error("No workspace found. Please run 'cpl init workspace' first.") + raise SystemExit(1) + + if project_name in workspace.project_names: + Console.error(f"Project '{project_name}' already exists in the workspace") + raise SystemExit(1) + + if verbose: + Console.write_line(f"Creating project '{path/project_name}'...") + + project_types = os.listdir(Path(clim.__file__).parent / ".cpl" / "new") + project_types.extend(set(x[0] for x in PROJECT_TYPES)) + + if type not in project_types: + raise ValueError(f"Unsupported project type '{type}'") + + Structure.create_project(path, type, project_name, workspace, verbose) + if workspace_created: + workspace.default_project = project_name + workspace.save() diff --git a/src/cli/cpl/cli/main.py b/src/cli/cpl/cli/main.py index e85d6a5d..4afb0ca8 100644 --- a/src/cli/cpl/cli/main.py +++ b/src/cli/cpl/cli/main.py @@ -12,6 +12,7 @@ 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.structure.new import new from cpl.cli.command.version import version from cpl.cli.model.workspace import Workspace from cpl.cli.utils.custom_command import script_command @@ -36,7 +37,7 @@ def _load_scripts(): if ws is None: continue - Configuration.set("workspace", Workspace.from_file(p)) + Configuration.set("workspace", ws) return ws.scripts return {} @@ -54,7 +55,7 @@ def configure(): # structure cli.add_command(init) - # cli.add_command(new) + cli.add_command(new) cli.add_command(generate) # packaging diff --git a/src/cli/cpl/cli/model/build.py b/src/cli/cpl/cli/model/build.py index bfd87003..b9939e29 100644 --- a/src/cli/cpl/cli/model/build.py +++ b/src/cli/cpl/cli/model/build.py @@ -3,6 +3,10 @@ from cpl.cli.model.cpl_sub_structure_model import CPLSubStructureModel class Build(CPLSubStructureModel): + @staticmethod + def new(include: list[str], exclude: list[str]) -> "Build": + return Build(include, exclude) + def __init__(self, include: list[str], exclude: list[str]): CPLSubStructureModel.__init__(self) diff --git a/src/cli/cpl/cli/model/cpl_structure_model.py b/src/cli/cpl/cli/model/cpl_structure_model.py index ba32133a..3d9434c1 100644 --- a/src/cli/cpl/cli/model/cpl_structure_model.py +++ b/src/cli/cpl/cli/model/cpl_structure_model.py @@ -1,5 +1,6 @@ import inspect import json +import os from inspect import isclass from pathlib import Path from typing import Any, Dict, List, Optional, Type, TypeVar @@ -81,6 +82,9 @@ class CPLStructureModel: if not self._path: raise ValueError("Cannot save model without a path.") + if not Path(self._path).exists(): + os.makedirs(Path(self._path).parent, exist_ok=True) + with open(self._path, "w", encoding="utf-8") as f: json.dump(self.to_json(), f, indent=2) diff --git a/src/cli/cpl/cli/model/project.py b/src/cli/cpl/cli/model/project.py index 326eee68..d89fda8e 100644 --- a/src/cli/cpl/cli/model/project.py +++ b/src/cli/cpl/cli/model/project.py @@ -31,7 +31,8 @@ class Project(CPLStructureModel): {}, [], None, - "src", + "./", + Build.new([], []), ) def __init__( @@ -192,6 +193,7 @@ class Project(CPLStructureModel): def _collect_files(self, rel_dir: Path) -> List[Path]: files: List[Path] = [] exclude_patterns = [p.strip() for p in self._build.exclude or []] + exclude_patterns.append("cpl.*.json") for root, dirnames, filenames in os.walk(rel_dir, topdown=True): root_path = Path(root) diff --git a/src/cli/cpl/cli/utils/structure.py b/src/cli/cpl/cli/utils/structure.py index c8952527..e1246704 100644 --- a/src/cli/cpl/cli/utils/structure.py +++ b/src/cli/cpl/cli/utils/structure.py @@ -1,60 +1,170 @@ +import os +import shutil import textwrap from pathlib import Path +import click + +import cpl.cli as cli from cpl.cli.model.project import Project -from cpl.core.configuration import Configuration +from cpl.cli.model.workspace import Workspace +from cpl.cli.utils.template_renderer import TemplateRenderer +from cpl.core.console import Console -def get_project_by_name_or_path(project: str) -> Project: - if project is None: - raise ValueError("Project name or path must be provided.") +class Structure: + @staticmethod + def find_workspace_in_path(path: Path) -> Workspace | None: + current_path = path.resolve() - workspace = Configuration.get("workspace") + for parent in [current_path] + list(current_path.parents): + workspace_file = parent / "cpl.workspace.json" + if workspace_file.exists() and workspace_file.is_file(): + return Workspace.from_file(workspace_file) - path = Path(project) - if path.exists() and path.is_dir() and (path / "cpl.project.json").exists(): - return Project.from_file(path / "cpl.project.json") + return None - if path.exists() and path.is_file(): - if not path.name.endswith("cpl.project.json"): - raise ValueError(f"File '{path}' is not a valid cpl.project.json file.") + @staticmethod + def create_pyproject_toml(project: Project, path: Path): + pyproject_path = path / "pyproject.toml" + if pyproject_path.exists(): + return - return Project.from_file(path) + content = textwrap.dedent( + f""" + [build-system] + requires = ["setuptools>=70.1.0", "wheel", "build"] + build-backend = "setuptools.build_meta" + [project] + name = "{project.name}" + version = "{project.version or '0.1.0'}" + description = "{project.description or ''}" + authors = [{{name="{project.author or ''}"}}] + license = "{project.license or ''}" + dependencies = [{', '.join([f'"{dep}"' for dep in project.dependencies])}] + """ + ).lstrip() - if workspace is not None: - for p in workspace.actual_projects: - if p.name == project: - return Project.from_file(Path(p.path)) + pyproject_path.write_text(content) - if not path.is_dir() and not path.is_file(): - raise ValueError(f"Unknown project {project}") + @staticmethod + def get_project_by_name_or_path(project: str) -> Project: + if project is None: + raise ValueError("Project name or path must be provided.") - if workspace.default_project is not None: + + path = Path(project) + if path.exists() and path.is_dir() and (path / "cpl.project.json").exists(): + return Project.from_file(path / "cpl.project.json") + + if path.exists() and path.is_file(): + if not path.name.endswith("cpl.project.json"): + raise ValueError(f"File '{path}' is not a valid cpl.project.json file.") + + return Project.from_file(path) + + workspace = Structure.find_workspace_in_path(path.parent) + if workspace is None: + raise RuntimeError("No workspace found. Please run 'cpl init workspace' first.") + + if workspace is not None: for p in workspace.actual_projects: - if p.name == workspace.default_project: + if p.name == project: return Project.from_file(Path(p.path)) - raise ValueError(f"Project '{project}' not found.") + if not path.is_dir() and not path.is_file(): + raise ValueError(f"Unknown project {project}") + + if workspace.default_project is not None: + for p in workspace.actual_projects: + if p.name == workspace.default_project: + return Project.from_file(Path(p.path)) + + raise ValueError(f"Project '{project}' not found.") + + @staticmethod + def init_workspace(path: Path | str, name: str): + path = Path(path) / Path("cpl.workspace.json") + + if path.exists(): + raise ValueError("workspace.json already exists.") + + workspace = Workspace.new(str(path), name) + workspace.save() + + Console.write_line(f"Created workspace '{name}'") -def create_pyproject_toml(project: Project, path: Path): - pyproject_path = path / "pyproject.toml" - if pyproject_path.exists(): - return + @staticmethod + def init_project(rel_path: str, name: str, project_type: str, workspace: Workspace | None, verbose=False): + if not Path(rel_path).exists(): + rel_path = click.prompt( + "Project directory", type=click.Path(exists=True, file_okay=False), default="src" + ) - content = textwrap.dedent( - f""" - [build-system] - requires = ["setuptools>=70.1.0", "wheel", "build"] - build-backend = "setuptools.build_meta" - [project] - name = "{project.name}" - version = "{project.version or '0.1.0'}" - description = "{project.description or ''}" - authors = [{{name="{project.author or ''}"}}] - license = "{project.license or ''}" - dependencies = [{', '.join([f'"{dep}"' for dep in project.dependencies])}] - """ - ).lstrip() + path = Path(rel_path) / Path("cpl.project.json") + if path.exists(): + Console.write_line("cpl.project.json already exists.") + return - pyproject_path.write_text(content) + project = Project.new(str(path), name, project_type) + + executable_path = Path(project.path).parent / "main.py" + executable_file = str(executable_path.relative_to(Path(project.path).parent)) if executable_path.exists() else None + + if project_type in ["console", "web", "service"]: + project.main = executable_file or click.prompt( + "Main executable", type=click.Path(exists=True, dir_okay=False), default="src/main.py" + ) + + project.save() + + if workspace is not None: + rel_path = str(path.resolve().absolute().relative_to(Path(workspace.path).parent)) + if rel_path not in workspace.projects: + workspace.projects.append(rel_path) + workspace.save() + + if verbose: + Console.write_line(f"Registered '{name}' in workspace.json") + + Console.write_line(f"Created {project_type} project '{name}'") + + @staticmethod + def create_project(path: Path, project_type: str, name: str, workspace: Workspace | None, verbose=False): + if not str(path).endswith(name): + path = path / name + + if not path.exists(): + os.makedirs(path, exist_ok=True) + + src_dir = Path(cli.__file__).parent / ".cpl" / "new" / project_type + + Console.write_line() + for root, dirs, files in os.walk(src_dir): + rel_root = Path(root).relative_to(src_dir) + target_root = path / rel_root + target_root.mkdir(parents=True, exist_ok=True) + + for filename in files: + src_file = Path(root) / filename + tgt_file = target_root / filename + + Console.set_foreground_color("green") + Console.write_line(f"Create {str(tgt_file).replace(".schematic", "")}") + Console.set_foreground_color() + + if filename.endswith(".schematic"): + with open(src_file, "r") as src: + with open(str(tgt_file).replace(".schematic", ""), "w") as tgt: + tgt.write( + TemplateRenderer.render_template( + str(src_file).split(".")[0], src.read(), name, str(path) + ) + ) + continue + + shutil.copy(src_file, tgt_file) + + Console.write_line() + Structure.init_project(str(path), name, project_type, workspace) \ No newline at end of file diff --git a/src/core/cpl/core/__init__.py b/src/core/cpl/core/__init__.py index a6d8caf7..d538f87e 100644 --- a/src/core/cpl/core/__init__.py +++ b/src/core/cpl/core/__init__.py @@ -1,2 +1 @@ -__version__ = "1.0.0" -__version__ = "1.0.0" +__version__ = "1.0.0" \ No newline at end of file diff --git a/src/core/cpl/core/configuration/configuration.py b/src/core/cpl/core/configuration/configuration.py index 5963d6db..e4ca5053 100644 --- a/src/core/cpl/core/configuration/configuration.py +++ b/src/core/cpl/core/configuration/configuration.py @@ -133,5 +133,4 @@ class Configuration: if isclass(key) and issubclass(key, ConfigurationModelABC) and result == default: result = key() cls.set(key, result) - return result diff --git a/src/core/cpl/core/console/console.py b/src/core/cpl/core/console/console.py index d45e3158..4a4d8620 100644 --- a/src/core/cpl/core/console/console.py +++ b/src/core/cpl/core/console/console.py @@ -72,13 +72,17 @@ class Console: cls._background_color = color @classmethod - def set_foreground_color(cls, color: Union[ForegroundColorEnum, str]): + def set_foreground_color(cls, color: Union[ForegroundColorEnum, str] = None): r"""Sets the foreground color Parameter: color: Union[:class:`cpl.core.console.background_color_enum.BackgroundColorEnum`, :class:`str`] Foreground color of the console """ + if color is None: + cls._foreground_color = ForegroundColorEnum.default + return + if type(color) is str: cls._foreground_color = ForegroundColorEnum[color] else: diff --git a/src/core/cpl/core/property.py b/src/core/cpl/core/property.py new file mode 100644 index 00000000..26d91936 --- /dev/null +++ b/src/core/cpl/core/property.py @@ -0,0 +1,3 @@ +class classproperty(property): + def __get__(self, obj, cls): + return self.fget(cls) \ No newline at end of file diff --git a/src/graphql/cpl/graphql/application/__init__.py b/src/graphql/cpl/graphql/application/__init__.py index 96b2346c..cd74b311 100644 --- a/src/graphql/cpl/graphql/application/__init__.py +++ b/src/graphql/cpl/graphql/application/__init__.py @@ -1 +1 @@ -from .graphql_app import WebApp +from .graphql_app import GraphQLApp