Cleanup & fixes

This commit is contained in:
2025-10-11 11:43:23 +02:00
parent 45dcb400da
commit 6e0ae1f25e
8 changed files with 74 additions and 123 deletions

View File

@@ -11,7 +11,7 @@
"click": "~8.3.0" "click": "~8.3.0"
}, },
"devDependencies": { "devDependencies": {
"black": "~25.9.0" "black": "~25.9"
}, },
"references": [], "references": [],
"main": "cpl/cli/main.py", "main": "cpl/cli/main.py",

View File

@@ -1,26 +1,16 @@
from pathlib import Path
import click import click
from cpl.cli.cli import cli 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.model.workspace import Workspace
from cpl.core.configuration import Configuration
from cpl.core.console import Console from cpl.core.console import Console
@cli.command("add", aliases=["a"]) @cli.command("add", aliases=["a"])
@click.argument("reference", type=click.STRING, required=True) @click.argument("reference", type=click.STRING, required=True)
@click.argument("target", type=click.STRING, required=True) @click.argument("target", type=click.STRING, required=True)
@click.option("--verbose", is_flag=True, help="Enable verbose output") def add(reference: str, target: str):
def add(reference: str, target: str, verbose: bool): reference_project = get_project_by_name_or_path(reference)
workspace = None target_project = get_project_by_name_or_path(target)
if Configuration.get("workspace_path") is not None:
workspace = Workspace.from_file(Configuration.get("workspace_path"))
reference_project = _get_project_by_name_or_path(reference, workspace)
target_project = _get_project_by_name_or_path(target, workspace)
if reference_project.name == target_project.name: if reference_project.name == target_project.name:
raise ValueError("Cannot add a project as a dependency to itself!") raise ValueError("Cannot add a project as a dependency to itself!")
@@ -30,22 +20,3 @@ def add(reference: str, target: str, verbose: bool):
target_project.references.append(reference_project.path) target_project.references.append(reference_project.path)
target_project.save() target_project.save()
Console.write_line(f"Added '{reference_project.name}' to '{target_project.name}' project") Console.write_line(f"Added '{reference_project.name}' to '{target_project.name}' project")
def _get_project_by_name_or_path(project: str, workspace=None) -> Project:
path = Path(project)
if path.exists() and path.is_dir():
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)
if workspace is not None:
for p in workspace.actual_projects:
if p.name == project:
return Project.from_file(Path(p.path))
raise ValueError(f"Project '{project}' not found.")

View File

@@ -1,38 +1,31 @@
import subprocess import subprocess
from pathlib import Path
import click import click
from cpl.cli.cli import cli from cpl.cli.cli import cli
from cpl.cli.utils.pip import Pip from cpl.cli.utils.pip import Pip
from cpl.cli.utils.structure import resolve_project from cpl.cli.utils.structure import get_project_by_name_or_path
from cpl.core.console import Console from cpl.core.console import Console
@cli.command("install", aliases=["i"]) @cli.command("install", aliases=["i"])
@click.argument("package", type=click.STRING, required=False) @click.argument("package", type=click.STRING, required=False)
@click.option( @click.argument("project", type=click.STRING, required=False)
"--path",
"project_path",
type=click.Path(file_okay=False, exists=False),
default=".",
help="Path to project directory",
)
@click.option("--name", "project_name", help="Project name (lookup via workspace)")
@click.option("--dev", is_flag=True, help="Include dev dependencies") @click.option("--dev", is_flag=True, help="Include dev dependencies")
@click.option("--verbose", is_flag=True, help="Enable verbose output") @click.option("--verbose", is_flag=True, help="Enable verbose output")
def install(package: str, project_path: str, project_name: str, dev: bool, verbose: bool): def install(package: str, project: str, dev: bool, verbose: bool):
project = resolve_project(Path(project_path), project_name) project = get_project_by_name_or_path(project or "./")
if package is not None: if package is not None:
Console.write_line(dev)
Console.write_line(f"Installing {package} to '{project.name}':") Console.write_line(f"Installing {package} to '{project.name}':")
try: try:
Pip.command("install", package, verbose=verbose, path=project_path) Pip.command("install", package, verbose=verbose, path=project.path)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
Console.error(f"Failed to install {package}: exit code {e.returncode}") Console.error(f"Failed to install {package}: exit code {e.returncode}")
return
package_name = Pip.get_package_without_version(package) package_name = Pip.get_package_without_version(package)
installed_version = Pip.get_package_version(package_name, path=project_path) installed_version = Pip.get_package_version(package_name, path=project.path)
if installed_version is None: if installed_version is None:
Console.error(f"Package '{package_name}' not found after installation.") Console.error(f"Package '{package_name}' not found after installation.")
return return
@@ -58,6 +51,6 @@ def install(package: str, project_path: str, project_name: str, dev: bool, verbo
dep = Pip.normalize_dep(name, version) dep = Pip.normalize_dep(name, version)
Console.write_line(f" -> {dep}") Console.write_line(f" -> {dep}")
try: try:
Pip.command("install", dep, verbose=verbose, path=project_path) Pip.command("install", dep, verbose=verbose, path=project.path)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
Console.error(f"Failed to install {dep}: exit code {e.returncode}") Console.error(f"Failed to install {dep}: exit code {e.returncode}")

View File

@@ -1,26 +1,16 @@
from pathlib import Path
import click import click
from cpl.cli.cli import cli 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.model.workspace import Workspace
from cpl.core.configuration import Configuration
from cpl.core.console import Console from cpl.core.console import Console
@cli.command("remove", aliases=["rm"]) @cli.command("remove", aliases=["rm"])
@click.argument("reference", type=click.STRING, required=True) @click.argument("reference", type=click.STRING, required=True)
@click.argument("target", type=click.STRING, required=True) @click.argument("target", type=click.STRING, required=True)
@click.option("--verbose", is_flag=True, help="Enable verbose output") def remove(reference: str, target: str):
def remove(reference: str, target: str, verbose: bool): reference_project = get_project_by_name_or_path(reference)
workspace = None target_project = get_project_by_name_or_path(target)
if Configuration.get("workspace_path") is not None:
workspace = Workspace.from_file(Configuration.get("workspace_path"))
reference_project = _get_project_by_name_or_path(reference, workspace)
target_project = _get_project_by_name_or_path(target, workspace)
if reference_project.name == target_project.name: if reference_project.name == target_project.name:
raise ValueError("Cannot add a project as a dependency to itself!") raise ValueError("Cannot add a project as a dependency to itself!")
@@ -31,22 +21,3 @@ def remove(reference: str, target: str, verbose: bool):
target_project.references.remove(reference_project.path) target_project.references.remove(reference_project.path)
target_project.save() target_project.save()
Console.write_line(f"Removed '{reference_project.name}' from '{target_project.name}' project") Console.write_line(f"Removed '{reference_project.name}' from '{target_project.name}' project")
def _get_project_by_name_or_path(project: str, workspace=None) -> Project:
path = Path(project)
if path.exists() and path.is_dir():
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)
if workspace is not None:
for p in workspace.actual_projects:
if p.name == project:
return Project.from_file(Path(p.path))
raise ValueError(f"Project '{project}' not found.")

View File

@@ -1,35 +1,28 @@
import subprocess import subprocess
from pathlib import Path
import click import click
from cpl.cli.cli import cli from cpl.cli.cli import cli
from cpl.cli.utils.pip import Pip from cpl.cli.utils.pip import Pip
from cpl.cli.utils.structure import resolve_project from cpl.cli.utils.structure import get_project_by_name_or_path
from cpl.core.console import Console from cpl.core.console import Console
@cli.command("uninstall", aliases=["ui"]) @cli.command("uninstall", aliases=["ui"])
@click.argument("package", required=False) @click.argument("package", required=False)
@click.option( @click.argument("project", type=click.STRING, required=False)
"--path",
"project_path",
type=click.Path(file_okay=False, exists=False),
default=".",
help="Path to project directory",
)
@click.option("--name", "project_name", help="Project name (lookup via workspace)")
@click.option("--dev", is_flag=True, help="Include dev dependencies") @click.option("--dev", is_flag=True, help="Include dev dependencies")
@click.option("--verbose", is_flag=True, help="Enable verbose output") @click.option("--verbose", is_flag=True, help="Enable verbose output")
def uninstall(package: str, project_path: str, project_name: str, dev: bool, verbose: bool): def uninstall(package: str, project: str, dev: bool, verbose: bool):
if package is None: if package is None:
package = Console.read("Package name to uninstall: ").strip() package = Console.read("Package name to uninstall: ").strip()
project = resolve_project(Path(project_path), project_name) project = get_project_by_name_or_path(project or "./")
deps = project.dependencies if not dev else project.dev_dependencies deps = project.dependencies if not dev else project.dev_dependencies
try: try:
Pip.command("uninstall -y", package, verbose=verbose, path=project_path) Pip.command("uninstall -y", package, verbose=verbose, path=project.path)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
Console.error(f"Failed to uninstall {package}: exit code {e.returncode}") Console.error(f"Failed to uninstall {package}: exit code {e.returncode}")
return return

View File

@@ -1,28 +1,21 @@
import subprocess import subprocess
from pathlib import Path
import click import click
from cpl.cli.cli import cli from cpl.cli.cli import cli
from cpl.cli.utils.pip import Pip from cpl.cli.utils.pip import Pip
from cpl.cli.utils.structure import resolve_project from cpl.cli.utils.structure import get_project_by_name_or_path
from cpl.core.console import Console from cpl.core.console import Console
@cli.command("update", aliases=["u"]) @cli.command("update", aliases=["u"])
@click.argument("package", type=click.STRING, required=False) @click.argument("package", type=click.STRING, required=False)
@click.option( @click.argument("project", type=click.STRING, required=False)
"--path",
"project_path",
type=click.Path(file_okay=False, exists=False),
default=".",
help="Path to project directory",
)
@click.option("--name", "project_name", help="Project name (lookup via workspace)")
@click.option("--dev", is_flag=True, help="Include dev dependencies") @click.option("--dev", is_flag=True, help="Include dev dependencies")
@click.option("--verbose", is_flag=True, help="Enable verbose output") @click.option("--verbose", is_flag=True, help="Enable verbose output")
def update(package: str, project_path: str, project_name: str, dev: bool, verbose: bool): def update(package: str, project: str, dev: bool, verbose: bool):
project = resolve_project(Path(project_path), project_name) project = get_project_by_name_or_path(project or "./")
deps: dict = project.dependencies deps: dict = project.dependencies
if dev: if dev:
deps = project.dev_dependencies deps = project.dev_dependencies
@@ -40,13 +33,13 @@ def update(package: str, project_path: str, project_name: str, dev: bool, verbos
"install --upgrade", "install --upgrade",
f"{Pip.normalize_dep(package, old_spec)}", f"{Pip.normalize_dep(package, old_spec)}",
verbose=verbose, verbose=verbose,
path=project_path, path=project.path,
) )
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
Console.error(f"Failed to install {package}: exit code {e.returncode}") Console.error(f"Failed to install {package}: exit code {e.returncode}")
return return
installed_version = Pip.get_package_version(package, path=project_path) installed_version = Pip.get_package_version(package, path=project.path)
if installed_version is None: if installed_version is None:
Console.error(f"Package '{package}' not found after update.") Console.error(f"Package '{package}' not found after update.")
return return
@@ -66,12 +59,12 @@ def update(package: str, project_path: str, project_name: str, dev: bool, verbos
dep = Pip.normalize_dep(name, version) dep = Pip.normalize_dep(name, version)
Console.write_line(f" -> {dep}") Console.write_line(f" -> {dep}")
try: try:
Pip.command("install --upgrade", dep, verbose=verbose, path=project_path) Pip.command("install --upgrade", dep, verbose=verbose, path=project.path)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
Console.error(f"Failed to update {dep}: exit code {e.returncode}") Console.error(f"Failed to update {dep}: exit code {e.returncode}")
return return
installed_version = Pip.get_package_version(name, path=project_path) installed_version = Pip.get_package_version(name, path=project.path)
if installed_version is None: if installed_version is None:
Console.error(f"Package '{name}' not found after update.") Console.error(f"Package '{name}' not found after update.")
continue continue

View File

@@ -1,3 +1,4 @@
import os.path
import re import re
import subprocess import subprocess
from pathlib import Path from pathlib import Path
@@ -97,19 +98,22 @@ class Pip:
@staticmethod @staticmethod
def command(command: str, *args, verbose: bool = False, path: str = None): def command(command: str, *args, verbose: bool = False, path: str = None):
if path is not None and Path(path).is_file():
path = os.path.dirname(path)
venv_path = ensure_venv(Path(path or "./")) venv_path = ensure_venv(Path(path or "./"))
pip = get_venv_pip(venv_path) pip = get_venv_pip(venv_path)
if verbose: if verbose:
Console.write_line() Console.write_line()
Console.write_line(f"Running: {pip} {command} {''.join(args)}")
subprocess.run( subprocess.run(
f"{pip} {command} {''.join(args)}", [*pip.split(), command, *args],
shell=True,
check=True, check=True,
stdin=subprocess.DEVNULL if not verbose else None, stdin=subprocess.DEVNULL if not verbose else None,
stdout=subprocess.DEVNULL if not verbose else None, stdout=subprocess.DEVNULL if not verbose else None,
stderr=subprocess.STDOUT if not verbose else None, stderr=subprocess.DEVNULL if not verbose else None,
) )
@staticmethod @staticmethod
@@ -119,8 +123,7 @@ class Pip:
try: try:
result = subprocess.run( result = subprocess.run(
f"{pip} show {package}", [*pip.split(), "show", package],
shell=True,
check=True, check=True,
capture_output=True, capture_output=True,
text=True, text=True,
@@ -140,8 +143,7 @@ class Pip:
pip = get_venv_pip(venv_path) pip = get_venv_pip(venv_path)
try: try:
result = subprocess.run( result = subprocess.run(
f"{pip} list --format=freeze", [*pip.split(), "list", "--format=freeze"],
shell=True,
check=True, check=True,
capture_output=True, capture_output=True,
text=True, text=True,
@@ -163,8 +165,7 @@ class Pip:
try: try:
result = subprocess.run( result = subprocess.run(
f"{pip} --version", [*pip.split(), "--version"],
shell=True,
check=True, check=True,
capture_output=True, capture_output=True,
text=True, text=True,

View File

@@ -1,9 +1,8 @@
import json
import os
from pathlib import Path from pathlib import Path
from cpl.cli.model.project import Project from cpl.cli.model.project import Project
from cpl.cli.model.workspace import Workspace from cpl.cli.model.workspace import Workspace
from cpl.core.configuration import Configuration
from cpl.core.console import Console from cpl.core.console import Console
@@ -29,3 +28,33 @@ def resolve_project(path: Path, name: str | None) -> Project:
Console.error(f"Could not find project file '{path}'") Console.error(f"Could not find project file '{path}'")
exit(1) exit(1)
def get_project_by_name_or_path(project: str) -> Project:
if project is None:
raise ValueError("Project name or path must be provided.")
workspace = None
if Configuration.get("workspace_path") is not None:
workspace = Workspace.from_file(Configuration.get("workspace_path"))
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)
if workspace is not None:
for p in workspace.actual_projects:
if p.name == project:
return Project.from_file(Path(p.path))
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.")