#!/usr/bin/env python3 """Run the repository's common Decman workflows with safe, fixed commands.""" from __future__ import annotations import argparse import shlex import subprocess import sys from collections.abc import Callable, Sequence from pathlib import Path ROOT = Path(__file__).resolve().parents[1] SCRIPTS = ROOT / "scripts" def format_command(command: Sequence[str] | str) -> str: """Render a shell-safe command for diagnostic output.""" return command if isinstance(command, str) else shlex.join(command) def run(command: Sequence[str]) -> None: """Print and run a command from the repository root.""" print(f"+ {format_command(command)}", file=sys.stderr) subprocess.run(command, check=True, cwd=ROOT) def run_script(name: str) -> None: run([sys.executable, str(SCRIPTS / name)]) def verify() -> None: """Check the active profile's package and unit invariants.""" run_script("verify.py") def dry_run() -> None: """Verify the configuration, then preview Decman's changes.""" verify() run(["sudo", "decman", "--source", "source.py", "--dry-run", "--no-hooks"]) def apply() -> None: """Verify the configuration, then apply it without Decman hooks.""" verify() run(["sudo", "decman", "--source", "source.py", "--no-hooks"]) def split_packages() -> None: """Regenerate concern package lists from explicit installed packages.""" run_script("split_packages.py") def refresh_orphan_guards() -> None: """Regenerate dependency-orphan guards from installed packages.""" run_script("refresh_orphan_guards.py") def refresh_package_state() -> None: """Regenerate package data and verify the resulting configuration.""" split_packages() refresh_orphan_guards() verify() COMMANDS: dict[str, Callable[[], None]] = { "verify": verify, "dry-run": dry_run, "apply": apply, "split-packages": split_packages, "refresh-orphan-guards": refresh_orphan_guards, "refresh-package-state": refresh_package_state, } def failure_status(error: Exception) -> int | None: """Report a launch failure and return its shell-compatible status.""" if isinstance(error, subprocess.CalledProcessError): status = error.returncode if error.returncode > 0 else 128 - error.returncode print( f"error: command exited with status {status}: {format_command(error.cmd)}", file=sys.stderr, ) return status if isinstance(error, FileNotFoundError): print(f"error: executable not found: {error.filename}", file=sys.stderr) return 127 if isinstance(error, PermissionError): print(f"error: executable is not permitted: {error.filename}", file=sys.stderr) return 126 if isinstance(error, OSError): print( f"error: unable to execute {error.filename}: {error.strerror}", file=sys.stderr, ) return 126 return None def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "command", choices=COMMANDS, help="workflow to run; apply and package-state commands modify system or files", ) args = parser.parse_args() try: COMMANDS[args.command]() except KeyboardInterrupt: print("error: interrupted", file=sys.stderr) return 130 except Exception as error: status = failure_status(error) if status is None: raise return status return 0 if __name__ == "__main__": raise SystemExit(main())