Repository-wide mechanical cleanup so `ruff check src tests` has a chance of passing; no behavioural changes. - Add `from __future__ import annotations` where PEP 604 unions are used. This has to come first: pyproject declares requires-python >= 3.9, where `X | None` is not evaluable at runtime unless annotations are stringified. - Replace typing.List/Dict/Tuple/Set with the builtin generics and Optional[X] with X | None, then drop the imports that became unused. The four actions/*/__init__.py files needed this by hand because ruff leaves unused imports in __init__.py alone (possible re-exports). - Strip shebangs from 72 importable modules. None of them are executable or invoked directly; the entry points are console_scripts and runpy. - Flatten nested `with` blocks, collapse needless-bool returns, and apply the remaining mechanical ruff fixes (PIE810, FLY002, PERF102, FURB192, RUF059, I001). - Pass check=False explicitly to the four subprocess.run() calls that inspect returncode themselves. That is the existing default. Two rewrites are visible to mocks, so their tests move with them: subprocess.run(stdout=PIPE, stderr=PIPE) became capture_output=True, and open(path, "r", ...) lost the redundant mode. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
86 lines
2.3 KiB
Python
86 lines
2.3 KiB
Python
"""
|
|
CLI layer model for the pkgmgr installation pipeline.
|
|
|
|
We treat CLI entry points as coming from one of four conceptual layers:
|
|
|
|
- os-packages : system package managers (pacman/apt/dnf/…)
|
|
- nix : Nix flake / nix profile
|
|
- python : pip / virtualenv / user-local scripts
|
|
- makefile : repo-local Makefile / scripts inside the repo
|
|
|
|
The layer order defines precedence: higher layers "own" the CLI and
|
|
lower layers will not be executed once a higher-priority CLI exists.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from enum import Enum
|
|
|
|
|
|
class CliLayer(str, Enum):
|
|
OS_PACKAGES = "os-packages"
|
|
NIX = "nix"
|
|
PYTHON = "python"
|
|
MAKEFILE = "makefile"
|
|
|
|
|
|
# Highest priority first
|
|
CLI_LAYERS: list[CliLayer] = [
|
|
CliLayer.OS_PACKAGES,
|
|
CliLayer.NIX,
|
|
CliLayer.PYTHON,
|
|
CliLayer.MAKEFILE,
|
|
]
|
|
|
|
|
|
def layer_priority(layer: CliLayer | None) -> int:
|
|
"""
|
|
Return a numeric priority index for a given layer.
|
|
|
|
Lower index → higher priority.
|
|
Unknown / None → very low priority.
|
|
"""
|
|
if layer is None:
|
|
return len(CLI_LAYERS)
|
|
try:
|
|
return CLI_LAYERS.index(layer)
|
|
except ValueError:
|
|
return len(CLI_LAYERS)
|
|
|
|
|
|
def classify_command_layer(command: str, repo_dir: str) -> CliLayer:
|
|
"""
|
|
Heuristically classify a resolved command path into a CLI layer.
|
|
|
|
Rules (best effort):
|
|
|
|
- /usr/... or /bin/... → os-packages
|
|
- /nix/store/... or ~/.nix-profile → nix
|
|
- ~/.local/bin/... → python
|
|
- inside repo_dir → makefile
|
|
- everything else → python (user/venv scripts, etc.)
|
|
"""
|
|
command_abs = os.path.abspath(os.path.expanduser(command))
|
|
repo_abs = os.path.abspath(repo_dir)
|
|
home = os.path.expanduser("~")
|
|
|
|
# OS package managers
|
|
if command_abs.startswith(("/usr/", "/bin/")):
|
|
return CliLayer.OS_PACKAGES
|
|
|
|
# Nix store / profile
|
|
if command_abs.startswith(("/nix/store/", os.path.join(home, ".nix-profile"))):
|
|
return CliLayer.NIX
|
|
|
|
# User-local bin
|
|
if command_abs.startswith(os.path.join(home, ".local", "bin")):
|
|
return CliLayer.PYTHON
|
|
|
|
# Inside the repository → usually a Makefile/script
|
|
if command_abs.startswith(repo_abs):
|
|
return CliLayer.MAKEFILE
|
|
|
|
# Fallback: treat as Python-style/user-level script
|
|
return CliLayer.PYTHON
|