80 lines
3.0 KiB
Python
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
|