Files
soleprint/soleprint/atlas2/docgen/cli/__init__.py
2026-09-24 00:58:19 -03:00

80 lines
3.0 KiB
Python

"""
The command line — every command docgen has, and nothing else.
python3 -m docgen <command> [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} <command> [args]", ""]
lines += [f" {name:<9} {desc}" for name, (_, desc) in COMMANDS.items()]
lines += ["", f" {PROG} <command> --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