Compare commits
10 Commits
2025.10.19
...
2025.10.19
| Author | SHA1 | Date | |
|---|---|---|---|
| 88d5d78583 | |||
| 472aba5990 | |||
| 9c75008c9f | |||
| df2c2b5b56 | |||
| e3b19c9984 | |||
| 91269f351b | |||
| 5d07973940 | |||
| 0a9b7b81be | |||
| 21c88bc442 | |||
| 04e0884e1c |
22
README.md
22
README.md
@@ -0,0 +1,22 @@
|
|||||||
|
## Prepare for development
|
||||||
|
|
||||||
|
After cloning the repository, run the following commands to set up your development environment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
# On Windows use `.venv\Scripts\activate`
|
||||||
|
# On Windows with git bash use `source .venv/Scripts/activate`
|
||||||
|
bash install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Install cpl-cli as a development package:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -e src/core
|
||||||
|
pip install -e src/cli
|
||||||
|
# test with:
|
||||||
|
cpl v
|
||||||
|
```
|
||||||
|
|
||||||
|
When using Pycharm, mark all directories under `src/` as "Sources Root" and `exa` to ensure proper module resolution.
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"projects": [
|
"projects": [
|
||||||
"src/cli/cpl.project.json",
|
"src/cli/cpl.project.json",
|
||||||
"src/core/cpl.project.json",
|
"src/core/cpl.project.json",
|
||||||
"test/cpl.project.json"
|
"src/mail/cpl.project.json"
|
||||||
],
|
],
|
||||||
"defaultProject": "cpl-cli",
|
"defaultProject": "cpl-cli",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# Find and combine requirements from src/cpl-*/requirements.txt,
|
# Find and combine requirements from src/*/requirements.txt,
|
||||||
# filtering out lines whose *package name* starts with "cpl-".
|
# filtering out lines whose *package name* starts with "cpl-".
|
||||||
# Works with pinned versions, extras, markers, editable installs, and VCS refs.
|
# Works with pinned versions, extras, markers, editable installs, and VCS refs.
|
||||||
|
|
||||||
shopt -s nullglob
|
shopt -s nullglob
|
||||||
|
|
||||||
req_files=(src/cpl-*/requirements.txt)
|
req_files=(src/*/requirements.txt)
|
||||||
if ((${#req_files[@]} == 0)); then
|
if ((${#req_files[@]} == 0)); then
|
||||||
echo "No requirements files found at src/cpl-*/requirements.txt" >&2
|
echo "No requirements files found at src/*/requirements.txt" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ from cpl.core.console import Console
|
|||||||
@click.command("init")
|
@click.command("init")
|
||||||
@click.argument("target", required=False)
|
@click.argument("target", required=False)
|
||||||
@click.argument("name", required=False)
|
@click.argument("name", required=False)
|
||||||
def init(target: str, name: str):
|
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output")
|
||||||
|
def init(target: str, name: str, verbose: bool = False):
|
||||||
workspace = None
|
workspace = None
|
||||||
project = None
|
project = None
|
||||||
|
|
||||||
@@ -30,9 +31,9 @@ def init(target: str, name: str):
|
|||||||
if target in ["workspace", "ws"]:
|
if target in ["workspace", "ws"]:
|
||||||
workspace = Structure.init_workspace("./", name or click.prompt("Workspace name", default="my-workspace"))
|
workspace = Structure.init_workspace("./", name or click.prompt("Workspace name", default="my-workspace"))
|
||||||
elif target in PROJECT_TYPES:
|
elif target in PROJECT_TYPES:
|
||||||
workspace = Structure.find_workspace_in_path(Path(name).parent)
|
workspace = Structure.find_workspace_in_path(Path(name or "./").parent)
|
||||||
project = Structure.init_project(
|
project = Structure.init_project(
|
||||||
"./", name or click.prompt("Project name", default=f"my-{target}"), target, workspace
|
"./", name or click.prompt("Project name", default=f"my-{target}"), target, workspace, verbose=verbose
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
Console.error(f"Unknown target '{target}'")
|
Console.error(f"Unknown target '{target}'")
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ def new(type: str, name: str, in_name: str | None, project: list[str] | None, ve
|
|||||||
path = Path(workspace.path).parent / Path(project[1]).parent
|
path = Path(workspace.path).parent / Path(project[1]).parent
|
||||||
project_name = Path(project[1]).stem
|
project_name = Path(project[1]).stem
|
||||||
|
|
||||||
workspace = Structure.find_workspace_in_path(path, with_parents=False)
|
workspace = Structure.find_workspace_in_path(path)
|
||||||
if workspace is None:
|
if workspace is None:
|
||||||
Console.error("No workspace found. Please run 'cpl init workspace' first.")
|
Console.error("No workspace found. Please run 'cpl init workspace' first.")
|
||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ T = TypeVar("T", bound="CPLStructureModel")
|
|||||||
|
|
||||||
class CPLStructureModel:
|
class CPLStructureModel:
|
||||||
def __init__(self, path: Optional[str] = None):
|
def __init__(self, path: Optional[str] = None):
|
||||||
self._path = path
|
self.__path = path
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path(self) -> Optional[str]:
|
def path(self) -> Optional[str]:
|
||||||
return self._path
|
return self.__path
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_file(cls: Type[T], path: Path | str) -> T:
|
def from_file(cls: Type[T], path: Path | str) -> T:
|
||||||
@@ -68,7 +68,7 @@ class CPLStructureModel:
|
|||||||
def to_json(self) -> Dict[str, Any]:
|
def to_json(self) -> Dict[str, Any]:
|
||||||
result: Dict[str, Any] = {}
|
result: Dict[str, Any] = {}
|
||||||
for key, value in self.__dict__.items():
|
for key, value in self.__dict__.items():
|
||||||
if not key.startswith("_") or key == "_path":
|
if not key.startswith("_") or key.startswith("__") or key.endswith("_"):
|
||||||
continue
|
continue
|
||||||
out_key = _self_or_cls_snake_to_camel(key[1:])
|
out_key = _self_or_cls_snake_to_camel(key[1:])
|
||||||
|
|
||||||
@@ -79,13 +79,13 @@ class CPLStructureModel:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
if not self._path:
|
if not self.__path:
|
||||||
raise ValueError("Cannot save model without a path.")
|
raise ValueError("Cannot save model without a path.")
|
||||||
|
|
||||||
if not Path(self._path).exists():
|
if not Path(self.__path).exists():
|
||||||
os.makedirs(Path(self._path).parent, exist_ok=True)
|
os.makedirs(Path(self.__path).parent, exist_ok=True)
|
||||||
|
|
||||||
with open(self._path, "w", encoding="utf-8") as f:
|
with open(self.__path, "w", encoding="utf-8") as f:
|
||||||
json.dump(self.to_json(), f, indent=2)
|
json.dump(self.to_json(), f, indent=2)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from pathlib import Path
|
||||||
from typing import Optional, List, Dict
|
from typing import Optional, List, Dict
|
||||||
|
|
||||||
from cpl.cli.model.cpl_structure_model import CPLStructureModel
|
from cpl.cli.model.cpl_structure_model import CPLStructureModel
|
||||||
@@ -28,6 +29,20 @@ class Workspace(CPLStructureModel):
|
|||||||
self._name = name
|
self._name = name
|
||||||
self._projects = projects
|
self._projects = projects
|
||||||
self._default_project = default_project
|
self._default_project = default_project
|
||||||
|
|
||||||
|
self.__actual_projects = []
|
||||||
|
self.__project_names = []
|
||||||
|
for project in projects:
|
||||||
|
if Path(project).is_dir() or not Path(project).exists():
|
||||||
|
raise ValueError(f"Project path '{project}' does not exist or is a directory.")
|
||||||
|
|
||||||
|
p = Project.from_file(project)
|
||||||
|
self.__actual_projects.append(p)
|
||||||
|
self.__project_names.append(p.name)
|
||||||
|
|
||||||
|
if default_project is not None and default_project not in self.__project_names:
|
||||||
|
raise ValueError(f"Default project '{default_project}' not found in workspace projects.")
|
||||||
|
|
||||||
self._scripts = scripts
|
self._scripts = scripts
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -48,11 +63,11 @@ class Workspace(CPLStructureModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def actual_projects(self) -> List[Project]:
|
def actual_projects(self) -> List[Project]:
|
||||||
return [Project.from_file(p) for p in self._projects]
|
return self.__actual_projects
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def project_names(self) -> List[str]:
|
def project_names(self) -> List[str]:
|
||||||
return [Project.from_file(p).name for p in self._projects]
|
return self.__project_names
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def default_project(self) -> Optional[str]:
|
def default_project(self) -> Optional[str]:
|
||||||
|
|||||||
@@ -32,11 +32,9 @@ class Structure:
|
|||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def find_workspace_in_path(path: Path, with_parents=False) -> Workspace | None:
|
def find_workspace_in_path(path: Path) -> Workspace | None:
|
||||||
current_path = path.resolve()
|
current_path = path.resolve()
|
||||||
paths = [current_path]
|
paths = [current_path, *current_path.parents]
|
||||||
if with_parents:
|
|
||||||
paths.extend(current_path.parents)
|
|
||||||
|
|
||||||
for parent in paths:
|
for parent in paths:
|
||||||
workspace_file = parent / "cpl.workspace.json"
|
workspace_file = parent / "cpl.workspace.json"
|
||||||
@@ -138,6 +136,16 @@ class Structure:
|
|||||||
)
|
)
|
||||||
|
|
||||||
project.save()
|
project.save()
|
||||||
|
Console.write_line(f"Created {project_type} project '{name}'")
|
||||||
|
|
||||||
|
if workspace is not None:
|
||||||
|
rel_path = str(path.resolve().absolute().relative_to(Path(workspace.path).parent)).replace("\\", "/")
|
||||||
|
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")
|
||||||
|
|
||||||
from cpl.cli.command.package.install import install
|
from cpl.cli.command.package.install import install
|
||||||
|
|
||||||
@@ -149,17 +157,6 @@ class Structure:
|
|||||||
install.callback(package, project.name, dev=False, verbose=verbose)
|
install.callback(package, project.name, dev=False, verbose=verbose)
|
||||||
|
|
||||||
os.chdir(old_cwd)
|
os.chdir(old_cwd)
|
||||||
|
|
||||||
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}'")
|
|
||||||
return project
|
return project
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -199,4 +196,4 @@ class Structure:
|
|||||||
shutil.copy(src_file, tgt_file)
|
shutil.copy(src_file, tgt_file)
|
||||||
|
|
||||||
Console.write_line()
|
Console.write_line()
|
||||||
Structure.init_project(str(path), name, project_type, workspace)
|
Structure.init_project(str(path), name, project_type, workspace, verbose=verbose)
|
||||||
|
|||||||
23
src/mail/cpl.project.json
Normal file
23
src/mail/cpl.project.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "cpl-mail",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "library",
|
||||||
|
"license": "",
|
||||||
|
"author": "",
|
||||||
|
"description": "",
|
||||||
|
"homepage": "",
|
||||||
|
"keywords": [],
|
||||||
|
"dependencies": {
|
||||||
|
"cpl-core": "~2024.7.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"cpl-cli": ">2024.7.0"
|
||||||
|
},
|
||||||
|
"references": [],
|
||||||
|
"main": null,
|
||||||
|
"directory": "./",
|
||||||
|
"build": {
|
||||||
|
"include": [],
|
||||||
|
"exclude": []
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user