gpt-5.2 ChatGPT: refactor tools code into cli.tools.vscode and add unit tests
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / lint-shell (push) Has been cancelled
Mark stable commit / lint-python (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / lint-shell (push) Has been cancelled
Mark stable commit / lint-python (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
* Move VS Code workspace logic (incl. guards) from cli.commands.tools into cli.tools.vscode * Extract shared repo path resolution into cli.tools.paths and reuse for explore/terminal * Simplify cli.commands.tools to pure orchestration via open_vscode_workspace * Update existing tools command unit test to assert delegation instead of patching removed internals * Add new unit tests for cli.tools.paths and cli.tools.vscode (workspace creation, reuse, guard errors) https://chatgpt.com/share/69419a6a-c9e4-800f-9538-b6652b2da6b3
This commit is contained in:
@@ -1,115 +1,41 @@
|
||||
from __future__ import annotations
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from pkgmgr .cli .context import CLIContext
|
||||
from pkgmgr .core .command .run import run_command
|
||||
from pkgmgr .core .repository .identifier import get_repo_identifier
|
||||
from pkgmgr .core .repository .dir import get_repo_dir
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from pkgmgr.cli.context import CLIContext
|
||||
from pkgmgr.cli.tools import open_vscode_workspace
|
||||
from pkgmgr.cli.tools.paths import resolve_repository_path
|
||||
from pkgmgr.core.command.run import run_command
|
||||
|
||||
Repository = Dict[str, Any]
|
||||
|
||||
|
||||
def _resolve_repository_path(repository: Repository, ctx: CLIContext) -> str:
|
||||
"""
|
||||
Resolve the filesystem path for a repository.
|
||||
|
||||
Priority:
|
||||
1. Use explicit keys if present (directory / path / workspace / workspace_dir).
|
||||
2. Fallback to get_repo_dir(...) using the repositories base directory
|
||||
from the CLI context.
|
||||
"""
|
||||
|
||||
# 1) Explicit path-like keys on the repository object
|
||||
for key in ("directory", "path", "workspace", "workspace_dir"):
|
||||
value = repository.get(key)
|
||||
if value:
|
||||
return value
|
||||
|
||||
# 2) Fallback: compute from base dir + repository metadata
|
||||
base_dir = (
|
||||
getattr(ctx, "repositories_base_dir", None)
|
||||
or getattr(ctx, "repositories_dir", None)
|
||||
)
|
||||
if not base_dir:
|
||||
raise RuntimeError(
|
||||
"Cannot resolve repositories base directory from context; "
|
||||
"expected ctx.repositories_base_dir or ctx.repositories_dir."
|
||||
)
|
||||
|
||||
return get_repo_dir(base_dir, repository)
|
||||
|
||||
|
||||
def handle_tools_command(
|
||||
args,
|
||||
ctx: CLIContext,
|
||||
selected: List[Repository],
|
||||
) -> None:
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# nautilus "explore" command
|
||||
# ------------------------------------------------------------------
|
||||
if args.command == "explore":
|
||||
for repository in selected:
|
||||
repo_path = _resolve_repository_path(repository, ctx)
|
||||
run_command(
|
||||
f'nautilus "{repo_path}" & disown'
|
||||
)
|
||||
return
|
||||
repo_path = resolve_repository_path(repository, ctx)
|
||||
run_command(f'nautilus "{repo_path}" & disown')
|
||||
return
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GNOME terminal command
|
||||
# ------------------------------------------------------------------
|
||||
if args.command == "terminal":
|
||||
for repository in selected:
|
||||
repo_path = _resolve_repository_path(repository, ctx)
|
||||
run_command(
|
||||
f'gnome-terminal --tab --working-directory="{repo_path}"'
|
||||
)
|
||||
return
|
||||
repo_path = resolve_repository_path(repository, ctx)
|
||||
run_command(f'gnome-terminal --tab --working-directory="{repo_path}"')
|
||||
return
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# VS Code workspace command
|
||||
# ------------------------------------------------------------------
|
||||
if args.command == "code":
|
||||
if not selected:
|
||||
print("No repositories selected.")
|
||||
return
|
||||
|
||||
identifiers = [
|
||||
get_repo_identifier(repo, ctx.all_repositories)
|
||||
for repo in selected
|
||||
]
|
||||
sorted_identifiers = sorted(identifiers)
|
||||
workspace_name = "_".join(sorted_identifiers) + ".code-workspace"
|
||||
|
||||
directories_cfg = ctx.config_merged.get("directories") or {}
|
||||
workspaces_dir = os.path.expanduser(
|
||||
directories_cfg.get("workspaces", "~/Workspaces")
|
||||
)
|
||||
os.makedirs(workspaces_dir, exist_ok=True)
|
||||
workspace_file = os.path.join(workspaces_dir, workspace_name)
|
||||
|
||||
folders = [
|
||||
{"path": _resolve_repository_path(repository, ctx)}
|
||||
for repository in selected
|
||||
]
|
||||
|
||||
workspace_data = {
|
||||
"folders": folders,
|
||||
"settings": {},
|
||||
}
|
||||
|
||||
if not os.path.exists(workspace_file):
|
||||
with open(workspace_file, "w", encoding="utf-8") as f:
|
||||
json.dump(workspace_data, f, indent=4)
|
||||
print(f"Created workspace file: {workspace_file}")
|
||||
else:
|
||||
print(f"Using existing workspace file: {workspace_file}")
|
||||
|
||||
run_command(f'code "{workspace_file}"')
|
||||
open_vscode_workspace(ctx, selected)
|
||||
return
|
||||
|
||||
5
src/pkgmgr/cli/tools/__init__.py
Normal file
5
src/pkgmgr/cli/tools/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from .vscode import open_vscode_workspace
|
||||
|
||||
__all__ = ["open_vscode_workspace"]
|
||||
35
src/pkgmgr/cli/tools/paths.py
Normal file
35
src/pkgmgr/cli/tools/paths.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict
|
||||
|
||||
from pkgmgr.cli.context import CLIContext
|
||||
from pkgmgr.core.repository.dir import get_repo_dir
|
||||
|
||||
Repository = Dict[str, Any]
|
||||
|
||||
|
||||
def resolve_repository_path(repository: Repository, ctx: CLIContext) -> str:
|
||||
"""
|
||||
Resolve the filesystem path for a repository.
|
||||
|
||||
Priority:
|
||||
1. Use explicit keys if present (directory / path / workspace / workspace_dir).
|
||||
2. Fallback to get_repo_dir(...) using the repositories base directory
|
||||
from the CLI context.
|
||||
"""
|
||||
for key in ("directory", "path", "workspace", "workspace_dir"):
|
||||
value = repository.get(key)
|
||||
if value:
|
||||
return value
|
||||
|
||||
base_dir = (
|
||||
getattr(ctx, "repositories_base_dir", None)
|
||||
or getattr(ctx, "repositories_dir", None)
|
||||
)
|
||||
if not base_dir:
|
||||
raise RuntimeError(
|
||||
"Cannot resolve repositories base directory from context; "
|
||||
"expected ctx.repositories_base_dir or ctx.repositories_dir."
|
||||
)
|
||||
|
||||
return get_repo_dir(base_dir, repository)
|
||||
102
src/pkgmgr/cli/tools/vscode.py
Normal file
102
src/pkgmgr/cli/tools/vscode.py
Normal file
@@ -0,0 +1,102 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from pkgmgr.cli.context import CLIContext
|
||||
from pkgmgr.cli.tools.paths import resolve_repository_path
|
||||
from pkgmgr.core.command.run import run_command
|
||||
from pkgmgr.core.repository.identifier import get_repo_identifier
|
||||
|
||||
Repository = Dict[str, Any]
|
||||
|
||||
|
||||
def _ensure_vscode_cli_available() -> None:
|
||||
"""
|
||||
Ensure that the VS Code CLI ('code') is available in PATH.
|
||||
"""
|
||||
if shutil.which("code") is None:
|
||||
raise RuntimeError(
|
||||
"VS Code CLI ('code') not found in PATH.\n\n"
|
||||
"Hint:\n"
|
||||
" Install Visual Studio Code and ensure the 'code' command is available.\n"
|
||||
" VS Code → Command Palette → 'Shell Command: Install code command in PATH'\n"
|
||||
)
|
||||
|
||||
|
||||
def _ensure_identifiers_are_filename_safe(identifiers: List[str]) -> None:
|
||||
"""
|
||||
Ensure identifiers can be used in a filename.
|
||||
|
||||
If an identifier contains '/', it likely means the repository has not yet
|
||||
been explicitly identified (no short identifier configured).
|
||||
"""
|
||||
invalid = [i for i in identifiers if "/" in i or os.sep in i]
|
||||
if invalid:
|
||||
raise RuntimeError(
|
||||
"Cannot create VS Code workspace.\n\n"
|
||||
"The following repositories are not yet identified "
|
||||
"(identifier contains '/'): \n"
|
||||
+ "\n".join(f" - {i}" for i in invalid)
|
||||
+ "\n\n"
|
||||
"Hint:\n"
|
||||
" The repository has no short identifier yet.\n"
|
||||
" Add an explicit identifier in your configuration before using `pkgmgr tools code`.\n"
|
||||
)
|
||||
|
||||
|
||||
def _resolve_workspaces_dir(ctx: CLIContext) -> str:
|
||||
directories_cfg = ctx.config_merged.get("directories") or {}
|
||||
return os.path.expanduser(directories_cfg.get("workspaces", "~/Workspaces"))
|
||||
|
||||
|
||||
def _build_workspace_filename(identifiers: List[str]) -> str:
|
||||
sorted_identifiers = sorted(identifiers)
|
||||
return "_".join(sorted_identifiers) + ".code-workspace"
|
||||
|
||||
|
||||
def _build_workspace_data(selected: List[Repository], ctx: CLIContext) -> Dict[str, Any]:
|
||||
folders = [{"path": resolve_repository_path(repo, ctx)} for repo in selected]
|
||||
return {
|
||||
"folders": folders,
|
||||
"settings": {},
|
||||
}
|
||||
|
||||
|
||||
def open_vscode_workspace(ctx: CLIContext, selected: List[Repository]) -> None:
|
||||
"""
|
||||
Create (if missing) and open a VS Code workspace for the selected repositories.
|
||||
|
||||
Policy:
|
||||
- Fail with a clear error if VS Code CLI is missing.
|
||||
- Fail with a clear error if any repository identifier contains '/', because that
|
||||
indicates the repo has not been explicitly identified (no short identifier).
|
||||
- Do NOT auto-sanitize identifiers and do NOT create subfolders under workspaces.
|
||||
"""
|
||||
if not selected:
|
||||
print("No repositories selected.")
|
||||
return
|
||||
|
||||
_ensure_vscode_cli_available()
|
||||
|
||||
identifiers = [get_repo_identifier(repo, ctx.all_repositories) for repo in selected]
|
||||
_ensure_identifiers_are_filename_safe(identifiers)
|
||||
|
||||
workspaces_dir = _resolve_workspaces_dir(ctx)
|
||||
os.makedirs(workspaces_dir, exist_ok=True)
|
||||
|
||||
workspace_name = _build_workspace_filename(identifiers)
|
||||
workspace_file = os.path.join(workspaces_dir, workspace_name)
|
||||
|
||||
workspace_data = _build_workspace_data(selected, ctx)
|
||||
|
||||
if not os.path.exists(workspace_file):
|
||||
with open(workspace_file, "w", encoding="utf-8") as f:
|
||||
json.dump(workspace_data, f, indent=4)
|
||||
print(f"Created workspace file: {workspace_file}")
|
||||
else:
|
||||
print(f"Using existing workspace file: {workspace_file}")
|
||||
|
||||
run_command(f'code "{workspace_file}"')
|
||||
Reference in New Issue
Block a user