Require runnable dispatcher subcommands (#1585)

* Require runnable dispatcher subcommands

* Use modern dispatcher test loader
This commit is contained in:
red person
2026-06-03 02:56:56 +03:00
committed by GitHub
parent e68d0448b8
commit 15a3b71802
2 changed files with 30 additions and 2 deletions

View File

@@ -68,6 +68,10 @@ def _short_help(path: Path) -> str:
return first return first
def _is_runnable_subcommand(path: Path) -> bool:
return path.exists() and path.is_file() and os.access(path, os.X_OK)
def _print_listing() -> None: def _print_listing() -> None:
"""`odysseus` with no args (or `odysseus help`) — print the table.""" """`odysseus` with no args (or `odysseus help`) — print the table."""
sys.stdout.write(f"odysseus {VERSION} — every feature, on the shell.\n\n") sys.stdout.write(f"odysseus {VERSION} — every feature, on the shell.\n\n")
@@ -101,7 +105,7 @@ def main(argv: list[str] | None = None) -> int:
_print_listing() _print_listing()
return 0 return 0
sub = SCRIPTS_DIR / f"odysseus-{argv[1]}" sub = SCRIPTS_DIR / f"odysseus-{argv[1]}"
if not sub.exists(): if not _is_runnable_subcommand(sub):
sys.stderr.write(f"odysseus: unknown subcommand {argv[1]!r}\n") sys.stderr.write(f"odysseus: unknown subcommand {argv[1]!r}\n")
return 1 return 1
return subprocess.call([str(sub), "--help"]) return subprocess.call([str(sub), "--help"])
@@ -109,7 +113,7 @@ def main(argv: list[str] | None = None) -> int:
# `odysseus foo ...` → exec `odysseus-foo ...` under the project venv. # `odysseus foo ...` → exec `odysseus-foo ...` under the project venv.
name = argv[0] name = argv[0]
sub = SCRIPTS_DIR / f"odysseus-{name}" sub = SCRIPTS_DIR / f"odysseus-{name}"
if not sub.exists(): if not _is_runnable_subcommand(sub):
sys.stderr.write( sys.stderr.write(
f"odysseus: unknown subcommand {name!r}. " f"odysseus: unknown subcommand {name!r}. "
f"Try `odysseus help` to see available ones.\n" f"Try `odysseus help` to see available ones.\n"

View File

@@ -0,0 +1,24 @@
import importlib.machinery
import importlib.util
from pathlib import Path
def _load_dispatcher():
path = Path(__file__).resolve().parent.parent / "scripts" / "odysseus"
loader = importlib.machinery.SourceFileLoader("odysseus_dispatcher_under_test", str(path))
spec = importlib.util.spec_from_loader(loader.name, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
return module
def test_is_runnable_subcommand_requires_executable_file(tmp_path):
cli = _load_dispatcher()
sub = tmp_path / "odysseus-demo"
sub.write_text("#!/bin/sh\n")
sub.chmod(0o644)
assert cli._is_runnable_subcommand(sub) is False
sub.chmod(0o755)
assert cli._is_runnable_subcommand(sub) is True