4.3 KiB
4.3 KiB
cli: the argument parser is a port of argparse
Goal: detz parses its command line exactly like ../tai.py (build_parser() run by the
Python installed here, 3.14). This document is the spec; it states what the code does today and
lists the gaps at the end.
Layout (one class per .h/.cpp pair, src/cli/)
| class | file | role |
|---|---|---|
FlagTable / FlagSpec |
flag_spec.* |
the single flag table: option strings, dest, arity, choices, metavar, help. Usage, help and the parser all read it, so they cannot drift |
TextWrap |
text_wrap.* |
port of Python textwrap.wrap (hyphen breaking, long-word breaking) |
HelpFormatter |
help_formatter.* |
port of argparse.HelpFormatter (3.14): usage wrapping, help columns |
ArgParser |
arg_parser.* |
port of ArgumentParser._parse_known_args (pattern string of A/O/-, abbreviations, explicit values, --) |
Parser, Command, DetzCli, Options |
cli.* |
stable API others compile against; maps ArgParser output onto Options |
Options, Parser::parse/parseOpts/helpText, Command::run keep their names and fields.
Behavior (mirrors argparse, each point has a differential case)
- Unique prefixes of long options resolve (
--versis--version); an ambiguous prefix fails withambiguous option: --ins could match --install, --install-telegram(matches listed in table order).--tui-resolves to--tui-localbecause--tuidoes not start with it. --flag=valuefor value flags; an explicit value on a switch fails withargument --yes: ignored explicit argument '1'. Single-dash clusters (-hx) follow argparse.- A flag that needs a value and sees an option-like word,
--, or the end fails withargument --profile: expected one argument.-1and-.5(anything matching-\.?\d) are values, not options. A word with a space in it is a value. --debugtakes an optional value (filewhen bare), checked against its choices.--capand--mplextake every remaining word (argparseREMAINDER), options included.--ends options; the first--is dropped from the prompt, later ones are kept.type=intaccepts what Pythonint()accepts for ASCII input (surrounding whitespace, sign, single underscores between digits); anything else isinvalid int value: 'abc'.-h/--helpacts the moment it is reached, before later words are examined, so-h --bogusprints help (exit 0) while--cap-permission-mode zzz -his an error (exit 2).Optionsthen carries onlyhelp = true.- Any parse error: full usage text on stderr, then
tai: error: <message>, exit 2.Options::errorholds both (usage then the error line, no trailing newline). - Terminal width:
COLUMNS(if a positive integer), else the width of stdout's tty, else 80; the formatter uses width - 2, likeshutil.get_terminal_size. - Flags that exist only in the old C++ parser (
--model,--driver,--task,--cwd,--argv,--binary,--max-risk,--auto-answer,--allow-tool,--deny-tool) are not intai.pyand are rejected as unrecognized, like tai.py does. TheOptionsfields stay so other modules compile.
Tests
Run all with make -f tests/cli.mk test (everything lands in build/cli).
tests/cli_test.cpp: unit cases (table, abbreviations, errors, REMAINDER,--, int, usage, wrap).tests/cli_parity.py+tests/cli_cases.txt: every argv line of the list at COLUMNS 80 and 40,--helpand the usage printed by an error at every width 12..200, and 1500 seeded random argvs, run throughbuild_parser()of../tai.py(in process, the installed Python) and throughbuild/cli/cli_driver; stdout, stderr and exit code must be equal (2080 jobs, 0 differences).make -f tests/cli.mk diff: the generated cases oftools/parity_diff.py(cli.*,tests/diff/cases/cli_generated.json) againstbuild/cli/cli_main, a cli-only entry point that mirrors the first lines ofmain.cpp.
Gaps
- Colored help (Python 3.14 colors help when
FORCE_COLORis set or stdout is a tty with color): not implemented, plain text only. - Widths count bytes, not code points (all help text is ASCII; user words only appear in error messages, which are not wrapped).
- The
--cap-manifesttext and every action after parsing live inmain.cpp, not here: casecli.cap_manifestfails againstcli_mainfor that reason (55 of 56cli.*cases pass).