Source code for httk.core.cli

"""Extensible root command-line interface for httk."""

import os
import sys
from collections.abc import Sequence
from dataclasses import dataclass
from importlib.metadata import PackageNotFoundError, version
from pathlib import Path

from .register.cli import cli_command, known_cli_commands


@dataclass(frozen=True)
[docs] class CLIContext: """Carry invocation context to a registered top-level command. :param program: Program name used in command output and help. :param cwd: Working directory selected for the command invocation. """
[docs] program: str
[docs] cwd: Path
def _version() -> str: try: return version("httk-core") except PackageNotFoundError: # Source-tree execution has no requirement to install package metadata. return "unknown" def _root_help(program: str) -> str: lines = [ f"usage: {program} [-C DIR] [--help] [--version] COMMAND [ARG ...]", "", "The high-throughput toolkit command line.", "", "options:", " -C DIR run as if httk was started in DIR", " -h, --help show this help message", " --version show the httk-core version", "", "commands:", " help show root help or help for one command", ] for name in known_cli_commands(): command = cli_command(name) assert command is not None lines.append(f" {name:<12s} {command.summary}") return "\n".join(lines) def _error(program: str, message: str) -> int: print(f"{program}: {message}", file=sys.stderr) print(f"Try '{program} --help' for more information.", file=sys.stderr) return 2
[docs] def main(argv: Sequence[str] | None = None) -> int: """Dispatch the httk executable. The optional -C prefix changes the process working directory before resolving the command, and the returned value is the command's exit code. :param argv: Command-line arguments without the executable name, or None to use sys.argv. :return: Exit status for the requested command. """ arguments = list(sys.argv[1:] if argv is None else argv) program = Path(sys.argv[0]).name if argv is None else "httk" directory: str | None = None position = 0 while position < len(arguments): argument = arguments[position] if argument == "-C": if position + 1 >= len(arguments): return _error(program, "option -C requires a directory") directory = arguments[position + 1] del arguments[position : position + 2] continue if argument.startswith("-C") and argument != "-C": directory = argument[2:] del arguments[position] continue break if directory is not None: try: os.chdir(directory) except OSError as exc: return _error(program, f"cannot change directory to {directory!r}: {exc}") context = CLIContext(program=program, cwd=Path.cwd()) if not arguments or arguments[0] in {"-h", "--help"}: print(_root_help(program)) return 0 if arguments[0] == "--version": print(f"{program} {_version()}") return 0 if arguments[0] == "help": if len(arguments) == 1: print(_root_help(program)) return 0 if len(arguments) != 2: return _error(program, "help accepts at most one command") command = cli_command(arguments[1]) if command is None: return _error(program, f"unknown command: {arguments[1]}") return int(command.resolve()(["--help"], context)) name = arguments.pop(0) command = cli_command(name) if command is None: return _error(program, f"unknown command: {name}") try: return int(command.resolve()(arguments, context)) except KeyboardInterrupt: print(f"{program}: interrupted", file=sys.stderr) return 130
if __name__ == "__main__": raise SystemExit(main())