This commit is contained in:
@@ -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": {
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
7
src/cli/cpl/cli/.cpl/new/console/main.py
Normal file
7
src/cli/cpl/cli/.cpl/new/console/main.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from cpl.core.console import Console
|
||||
|
||||
def main():
|
||||
Console.write_line("Hello, World!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
46
src/cli/cpl/cli/.cpl/new/graphql/main.py
Normal file
46
src/cli/cpl/cli/.cpl/new/graphql/main.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from cpl.api import ApiModule
|
||||
from cpl.application import ApplicationBuilder
|
||||
from cpl.core.configuration import Configuration
|
||||
from cpl.graphql.application import GraphQLApp
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
|
||||
def main():
|
||||
builder = ApplicationBuilder[GraphQLApp](GraphQLApp)
|
||||
|
||||
Configuration.add_json_file(f"appsettings.json", optional=True)
|
||||
|
||||
(
|
||||
builder.services.add_logging()
|
||||
# uncomment to add preferred database module
|
||||
# .add_module(MySQLModule)
|
||||
# .add_module(PostgresModule)
|
||||
.add_module(ApiModule)
|
||||
)
|
||||
|
||||
app = builder.build()
|
||||
app.with_logging()
|
||||
|
||||
app.with_authentication()
|
||||
app.with_authorization()
|
||||
|
||||
app.with_route(
|
||||
path="/ping",
|
||||
fn=lambda r: JSONResponse("pong"),
|
||||
method="GET",
|
||||
)
|
||||
|
||||
schema = app.with_graphql()
|
||||
schema.query.string_field("ping", resolver=lambda: "pong")
|
||||
|
||||
app.with_auth_root_queries(True)
|
||||
app.with_auth_root_mutations(True)
|
||||
|
||||
app.with_playground()
|
||||
app.with_graphiql()
|
||||
|
||||
app.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
3
src/cli/cpl/cli/.cpl/new/library/class.py
Normal file
3
src/cli/cpl/cli/.cpl/new/library/class.py
Normal file
@@ -0,0 +1,3 @@
|
||||
class Class1:
|
||||
|
||||
def __init__(self): ...
|
||||
13
src/cli/cpl/cli/.cpl/new/service/main.py
Normal file
13
src/cli/cpl/cli/.cpl/new/service/main.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from cpl.application import Host
|
||||
from my_hosted_service import MyHostedService
|
||||
|
||||
|
||||
async def main():
|
||||
Host.services.add_hosted_service(MyHostedService)
|
||||
Host.run_start_tasks()
|
||||
Host.run_hosted_services()
|
||||
await Host.wait_for_all()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
Host.run(main)
|
||||
13
src/cli/cpl/cli/.cpl/new/service/my_hosted_service.py
Normal file
13
src/cli/cpl/cli/.cpl/new/service/my_hosted_service.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from cpl.core.console import Console
|
||||
from cpl.dependency.hosted import HostedService
|
||||
|
||||
|
||||
class MyHostedService(HostedService):
|
||||
def __init__(self):
|
||||
HostedService.__init__(self)
|
||||
|
||||
async def start(self):
|
||||
Console.write_line("Hello, World!")
|
||||
|
||||
async def stop(self):
|
||||
Console.write_line("Goodbye, World!")
|
||||
37
src/cli/cpl/cli/.cpl/new/web/main.py
Normal file
37
src/cli/cpl/cli/.cpl/new/web/main.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
from cpl.api import ApiModule
|
||||
from cpl.api.application import WebApp
|
||||
from cpl.application import ApplicationBuilder
|
||||
from cpl.core.configuration import Configuration
|
||||
|
||||
|
||||
def main():
|
||||
builder = ApplicationBuilder[WebApp](WebApp)
|
||||
|
||||
Configuration.add_json_file(f"appsettings.json", optional=True)
|
||||
|
||||
(
|
||||
builder.services.add_logging()
|
||||
# uncomment to add preferred database module
|
||||
# .add_module(MySQLModule)
|
||||
# .add_module(PostgresModule)
|
||||
.add_module(ApiModule)
|
||||
)
|
||||
|
||||
app = builder.build()
|
||||
app.with_logging()
|
||||
|
||||
app.with_authentication()
|
||||
app.with_authorization()
|
||||
|
||||
app.with_route(
|
||||
path="/ping",
|
||||
fn=lambda r: JSONResponse("pong"),
|
||||
method="GET",
|
||||
)
|
||||
app.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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!")
|
||||
|
||||
@@ -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}':")
|
||||
|
||||
@@ -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!")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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...")
|
||||
|
||||
|
||||
@@ -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}'")
|
||||
|
||||
64
src/cli/cpl/cli/command/structure/new.py
Normal file
64
src/cli/cpl/cli/command/structure/new.py
Normal file
@@ -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="<type> <name>",
|
||||
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()
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -1,2 +1 @@
|
||||
__version__ = "1.0.0"
|
||||
__version__ = "1.0.0"
|
||||
|
||||
@@ -133,5 +133,4 @@ class Configuration:
|
||||
if isclass(key) and issubclass(key, ConfigurationModelABC) and result == default:
|
||||
result = key()
|
||||
cls.set(key, result)
|
||||
|
||||
return result
|
||||
|
||||
@@ -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:
|
||||
|
||||
3
src/core/cpl/core/property.py
Normal file
3
src/core/cpl/core/property.py
Normal file
@@ -0,0 +1,3 @@
|
||||
class classproperty(property):
|
||||
def __get__(self, obj, cls):
|
||||
return self.fget(cls)
|
||||
@@ -1 +1 @@
|
||||
from .graphql_app import WebApp
|
||||
from .graphql_app import GraphQLApp
|
||||
|
||||
Reference in New Issue
Block a user