""" The command line — every command docgen has, and nothing else. python3 -m docgen [args] book one operation, measured at both ends run every book a run file lists check docgen's own suite, or one book's own level scaffold empty notes for every page and step, never overwriting contract the instructions to hand a model that writes docgen.toml extract source -> ir.json validate check an ir.json at the boundary view ir.json -> a narrower ir.json emit ir.json -> an artifact ## Why all of it lives here The packages beside this one — `extractors/`, `ops/`, `emitters/`, `book/` — are library code, and they hold no command-line code at all: no argparse, no `__main__.py`, no `cli_dot.py` sitting next to `dot.py`. Two kinds of file in one folder makes every folder answer two questions, and the command-line half is the part that grows copies — each old `cli_*` re-implemented "read the IR, validate it, load the style" by hand. So the split is by kind rather than by feature: library in the packages, the command line here, and `selftest.py` asserts that nothing outside this package imports `argparse`, so it stays that way without anyone remembering to. Plain argparse and no scaffold, on purpose. docgen is copied out of the repo as a folder, and a shared CLI helper that lives in some other tool would tie the copy back to the repo. """ import sys # command -> (module in this package, one-line description) COMMANDS = { "book": ("book", "one operation, measured at both ends"), "run": ("run", "every book a run file lists"), "check": ("check", "docgen's own suite, or one book's own level"), "scaffold": ("scaffold", "empty notes for every page and step, never overwriting"), "contract": ("contract", "the instructions to hand a model that writes docgen.toml"), "extract": ("extract", "source -> ir.json"), "validate": ("validate", "check an ir.json at the boundary"), "view": ("view", "ir.json -> a narrower ir.json"), "emit": ("emit", "ir.json -> an artifact"), } def usage() -> str: from .common import PROG lines = [f"usage: {PROG} [args]", ""] lines += [f" {name:<9} {desc}" for name, (_, desc) in COMMANDS.items()] lines += ["", f" {PROG} --help for a command's options"] return "\n".join(lines) def main(argv=None) -> int: from importlib import import_module from .common import Abort argv = sys.argv[1:] if argv is None else list(argv) if not argv or argv[0] in ("-h", "--help", "help"): print(usage()) return 0 if argv else 2 name, rest = argv[0], argv[1:] if name not in COMMANDS: print(f"Error: no command {name!r}\n\n{usage()}", file=sys.stderr) return 2 module = import_module(f".{COMMANDS[name][0]}", package=__name__) try: return module.main(rest) except Abort as e: # The only exit path for an expected failure: one line, stderr, exit 1. print(f"Error: {e}", file=sys.stderr) return 1