mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-07 03:15:56 +00:00
- Replace static argparse-based CLI in cli.deploy.development with dynamic module dispatcher compatible with nested command discovery. - Ensure cli.deploy.development is executable via python -m and root dispatcher. - Fix unit test path resolution to work locally and in CI. - Make docker-* Makefile targets depend on install. - Simplify test runner script and keep Nix flake config compatibility. This aligns the CLI architecture with integration test discovery (cli/**/__main__.py) and removes the need for manual subcommand registration. https://chatgpt.com/share/696fdbbc-b6d8-800f-a8b5-067aa7e38706
75 lines
2.3 KiB
Python
75 lines
2.3 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import importlib.util
|
|
import runpy
|
|
import sys
|
|
from typing import Optional
|
|
|
|
|
|
def _module_exists(module: str) -> bool:
|
|
return importlib.util.find_spec(module) is not None
|
|
|
|
|
|
def _dispatch(argv: list[str]) -> int:
|
|
"""
|
|
Dynamic dispatcher for nested CLI command packages.
|
|
|
|
A command package is identified by existence of:
|
|
cli/<segments>/__main__.py
|
|
|
|
The integration test calls:
|
|
python cli/__main__.py <segments...> --help --no-signal
|
|
|
|
This dispatcher resolves the *longest* prefix of segments that forms a module:
|
|
cli.<segments>.__main__
|
|
and executes it as __main__ via runpy.
|
|
|
|
Example:
|
|
argv = ["deploy", "development", "--help", "--no-signal"]
|
|
-> module "cli.deploy.development.__main__"
|
|
"""
|
|
if not argv:
|
|
# Show a minimal help; keep it simple.
|
|
print("Usage: python -m cli <command> [<subcommand> ...] [args]")
|
|
print("Hint: run `python -m cli --help` or `python -m cli <command> --help`.")
|
|
return 0
|
|
|
|
# Find the longest prefix that matches a command package module
|
|
best_len = 0
|
|
best_module: Optional[str] = None
|
|
|
|
for i in range(1, len(argv) + 1):
|
|
mod = "cli." + ".".join(argv[:i]) + ".__main__"
|
|
if _module_exists(mod):
|
|
best_len = i
|
|
best_module = mod
|
|
|
|
if not best_module:
|
|
# Keep error format compatible with your current expectation.
|
|
# (Your failing output was: "Error: command 'deploy development --help' not found.")
|
|
joined = " ".join(argv)
|
|
print(f"Error: command '{joined}' not found.")
|
|
return 1
|
|
|
|
# Rewrite argv so the subcommand module sees only its own args
|
|
sub_argv = argv[best_len:]
|
|
sys.argv = [best_module] + sub_argv
|
|
|
|
# Execute the module like `python -m ...`
|
|
runpy.run_module(best_module, run_name="__main__")
|
|
return 0
|
|
|
|
|
|
def main(argv: Optional[list[str]] = None) -> int:
|
|
# We intentionally do NOT parse args here; we delegate to subcommands.
|
|
argv = list(sys.argv[1:] if argv is None else argv)
|
|
|
|
# Optional global flags could be handled here if you want.
|
|
# For now, we just pass everything through.
|
|
return int(_dispatch(argv))
|
|
|
|
|
|
if __name__ == "__main__": # pragma: no cover
|
|
raise SystemExit(main())
|