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

* 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:
Kevin Veen-Birkenbach
2025-12-16 18:43:56 +01:00
parent be70dd4239
commit ffa9d9660a
7 changed files with 331 additions and 174 deletions

View File

@@ -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

View File

@@ -0,0 +1,5 @@
from __future__ import annotations
from .vscode import open_vscode_workspace
__all__ = ["open_vscode_workspace"]

View 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)

View 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}"')