You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A full MkDocs (Material) documentation site under docs/, published to
GitHub Pages via nix build .#docs and .github/workflows/docs.yml.
Covers installation, a quick start, a per-topic guide (option values,
option relations, commands, validation, CLI behavior, shell completion,
documentation generation), an API reference, the migration guide, scope
and non-goals, and the project's governance documents.
define-app / define-command (src/model-dsl.lisp): a declarative,
clause-based DSL over make-app / make-command / make-option / make-positional. :option, :positional, and :command clauses replace
the nested :global-options (list ...) / :positionals (list ...) / :commands (list ...) keyword arguments; :command clauses nest the same
vocabulary for a command's own options, positionals, and subcommands, and :commands-from splices in an already-built command list (e.g. make-standard-commands, or another spec bound via define-command).
Purely additive -- the functional API is unchanged and remains
independently usable. See README's "Declarative DSL" section.
Adopted cl-json-kit as a
test-only dependency (mirroring cl-process-kit's test-only role): the json renderer test suite now parses render-json's own output back
through cl-json-kit's independent reader as an additional correctness
oracle, catching structural/escaping defects that substring-matching the
writer's output can't. Not needed to use cl-cli itself.
Fixed
Two README code examples called run-app with argv as a positional
argument instead of :argv, which signals odd number of &KEY arguments
if copy-pasted as-is.
Changed
The Nushell and Elvish completion renderers' quoting functions
(%COMPLETION-NUSHELL-QUOTE, %COMPLETION-ELVISH-QUOTE) and the
Markdown documentation renderer's prose-escaping functions
(%MD-ESCAPE-PROSE, %MD-SINGLE-LINE, %MD-MAX-BACKTICK-RUN, all in src/doc-renderers-markdown.lisp) had the same double-nested WITH-OUTPUT-TO-STRING pattern found and fixed in bash/zsh/PowerShell's
quoting functions earlier in this changelog -- each ran its own escaping
pass over the already-built result of a separate control-character-
stripping pass, rather than folding both into one. %MD-SINGLE-LINE's
second pass turned out to be provably dead code once traced through
(its input can never contain the newline/return characters it was
checking for, since the upstream control-stripping pass already maps
them to a space), so it now simply delegates. %MD-MAX-BACKTICK-RUN
needed care: a character control-stripping drops entirely doesn't
break a backtick run in the final output, while one it maps to a
space does, so its merged single pass reproduces that distinction
exactly rather than just concatenating both transformations (see the
new regression test in tests/cases-doc-markdown.lisp, which checks a
control character between two backticks counts as one run of 2 while a
newline between them counts as two runs of 1). Nushell and Elvish
completion rendering were separately profiled and found already fast
enough (tens of microseconds per call on a 330-option benchmark app)
that this fix's effect is not separately measurable there; it is applied
for consistency with the other renderers, not for a measured win. No
public API or observable output changed.
The PowerShell completion renderer (RENDER-POWERSHELL-COMPLETION): %COMPLETION-POWERSHELL-QUOTE folds control-character-stripping and
single-quote-doubling into one pass instead of two nested WITH-OUTPUT-TO-STRING calls (the same fix already applied to %COMPLETION-SHELL-QUOTE and %COMPLETION-ZSH-ARGUMENTS-FIELD), and %COMPLETION-POWERSHELL-COMMAND-OPTION-MAP now builds each command's
option-array literal once and reuses it across every alias instead of
rebuilding it (re-quoting every token again) per alias. (Profiled all
four remaining completion renderers this round: nushell and elvish are
already tens of microseconds per call -- optimizing them further would
be immeasurable -- so only PowerShell needed work.) Measured on a
330-option/33-command benchmark app: PowerShell rendering is roughly 1.3x
faster. No public API or observable script output changed; see tests/cases-parser-benchmark.lisp.
The fish completion renderer (RENDER-FISH-COMPLETION) no longer
re-shell-quotes the same loop-invariant value on every iteration of a
loop it's constant across: the app name, each level's "already seen a
sibling command" condition, and each command's description are now
quoted once and reused, instead of being re-quoted once per command
alias, once per option candidate, or once per complete line as
before. (Profiling on a 330-option/33-command benchmark app after fixing
the bash and zsh renderers, above, found fish was the next-most-expensive
renderer, dominated almost entirely by this redundant re-quoting rather
than the tree-copy pattern those two had; nushell and elvish were also
profiled and are already fast enough -- tens of microseconds per call --
that optimizing them further would be immeasurable.) Measured: fish
rendering on the same benchmark app is roughly 1.5x faster. No public API
or observable script output changed; see tests/cases-parser-benchmark.lisp.
The bash and zsh completion renderers (RENDER-BASH-COMPLETION, RENDER-ZSH-COMPLETION) write directly into one shared output stream
instead of building a separate string per recursive subcommand node (each
of which a parent then copied again via WRITE-STRING) and per
shell-quoted or array/case-label value (each of which was quoted into its
own string, joined into a second string, then copied a third time into a
parent's buffer). Profiling a 330-option/33-command benchmark app showed
this quote-then-join-then-copy pattern accounting for the large majority
of sampled render time in both renderers, dominated by repeated WITH-OUTPUT-TO-STRING setup/copy overhead rather than the actual
shell-quoting logic. %COMPLETION-SHELL-QUOTE's core loop is now also
exposed as %COMPLETION-WRITE-SHELL-QUOTED, which writes straight to a
caller-supplied stream, with shared %COMPLETION-WRITE-CASE-LABELS / %COMPLETION-WRITE-SPACE-JOINED-QUOTED writers built on top of it used by
both renderers (%COMPLETION-BASH-ARRAY-LITERAL and %COMPLETION-CASE-LABELS, now dead, were removed). Zsh's %COMPLETION-ZSH-ARGUMENTS-FIELD (used to build every _arguments
bracket/placeholder field) also folded its control-character-stripping and
bracket-blanking into one pass instead of two nested WITH-OUTPUT-TO-STRING calls, the same fix already applied to %COMPLETION-SHELL-QUOTE. Measured on the 330-option benchmark app: bash
rendering is roughly 1.5x faster and zsh rendering roughly 1.5x faster
than before this round of changes. No public API or observable script
output changed; see tests/cases-parser-benchmark.lisp.
Measured on the same 330-option benchmark app: bash completion rendering
is roughly twice as fast as before this round of changes (and faster
still relative to the pre-caching baseline). No public API or observable
script output changed; see tests/cases-parser-benchmark.lisp.
PARSE-ARGV is dramatically faster, especially across repeated calls
against the same APP (a REPL, a long-running server, or simply a hot
loop). MAKE-APP now precomputes and caches, once per app/command scope,
the built-in-augmented option list, its name/alias lookup table, and the
command-name/alias lookup table -- previously all three were rebuilt from
scratch (including re-running MAKE-OPTION for the built-in --help/ --version specs) on every single PARSE-ARGV call. Three option-
relationship validators (VALIDATE-CONDITIONAL-REQUIREMENTS, VALIDATE-REQUIRED-OPTION-GROUPS, VALIDATE-INCLUSIVE-GROUPS) now skip
building their lookup tables entirely when the app declares none of the
corresponding feature (conditional requirements / option groups), mirroring VALIDATE-OPTION-RELATIONSHIPS' existing early exit. The per-parse
accumulating-option-value hash table is now allocated lazily, on the first
value that actually needs it, instead of unconditionally on every call.
Measured on a representative subcommand-dispatch benchmark: ~4.5x faster
for repeated parses against one app, up to ~15x for a flag-only app with
no subcommands. BUILT-IN-OPTION-SPECS -- also called directly by every
help and completion renderer, not just the parser -- is cached the same
way, so generating help text or a shell-completion script no longer
reruns MAKE-OPTION for the built-ins either. No public API or
observable behavior changed; see tests/cases-parser-benchmark.lisp.
CANONICAL-OPTION-NAME (the hot path for every long-option token during
parsing) now strips a leading -/-- and downcases the remainder in one
pass instead of two separate allocations (SUBSEQ then STRING-DOWNCASE). Shell-completion rendering is also faster on apps
with many options: %COMPLETION-SHELL-QUOTE (used by the bash/zsh/fish
renderers) now strips control characters and quote-escapes in a single
pass rather than two nested string streams, and three REMOVE-DUPLICATES call sites switched from :TEST #'STRING= to :TEST #'EQUAL -- semantically identical for strings, but lets SBCL use
its hash-table-based dedup instead of an O(n^2) pairwise scan. Measured
on a 330-option benchmark app: shell-completion rendering is noticeably
faster; no observable behavior changed.
cl-cli no longer depends on cl-prolog at runtime. Option-relation
validation (:requires, :requires-any-of, :conflicts-with) is now a
plain in-memory adjacency graph (src/option-relations.lisp) with the same
public behavior and error messages. cl-cli's only dependency is now uiop; cl-prolog remains a test-only dependency (used as an independent
validation oracle in the test suite).
The test suite's real-shell verification (tests/cases-shell-verification.lisp)
now launches every subprocess (bash -n, zsh -n, fish --no-execute, mandoc -T lint, ...) through cl-process-kit with an explicit timeout
instead of an unbounded uiop:run-program call. A hung or misbehaving
tool is now terminated (SIGTERM escalating to SIGKILL, in its own process
group) rather than blocking the test run indefinitely; a regression test
proves the timeout actually fires. Test-only; does not affect cl-cli
itself, which never spawns subprocesses.
The man page and Markdown doc renderers (%MANPAGE-OPTION-ENTRY, %MD-OPTION-TABLE) no longer rebuild a full %OPTION-TARGET-TABLE from
scratch for every single option in their loop -- an O(n^2) hash-table-
construction cost that dominated both renderers' profiles on a large app.
Each loop now hoists one table build to the top and threads it through
explicitly, mirroring the plain-text help renderer's existing pattern.
Measured on a 330-option/33-command benchmark app: render-manpage ~40%
faster (682us -> 406us), render-markdown ~50% faster (1073us -> 534us).
No public API or observable output changed; see tests/cases-parser-benchmark.lisp.
render-json writes directly into a shared stream instead of each layer
(%option->json, %command->json, %app->json, ...) building and
returning its own string for its caller to copy again one level up --
the same allocation-cascade bug already fixed in the bash/zsh/PowerShell
completion renderers, one level removed. render-json's public contract
(a string when called without a stream, no return value when passed one)
is unchanged. Measured on a 330-option/33-command benchmark app: ~3.7x
faster (963us -> 263us per call), verified against cl-json-kit's
independent reader to confirm the output is still valid and semantically
identical, not just byte-similar. See tests/cases-parser-benchmark.lisp.
MAKE-APP-time option-relation validation (:requires, :requires-any-of, :required-if, :required-unless, :conflicts-with)
no longer rebuilds a full option target-table from scratch once per
declared relation target, plus again independently in cycle detection and
relation-graph construction -- the table is now built once per MAKE-APP
call and threaded through. Measured on a relation-heavy 100-option
benchmark app: MAKE-APP ~36% faster (4.822ms -> 3.109ms). Apps declaring
no option relations at all also skip relation-graph construction entirely
now, rather than building an empty one: ~27% faster MAKE-APP on a
330-option/33-command app with no relations declared (1.755ms -> 1.288ms).
No public API or observable behavior changed; see tests/cases-parser-benchmark.lisp.
PARSE-ARGV's four option-relation validators (VALIDATE-OPTION- RELATIONSHIPS, VALIDATE-REQUIRED-OPTION-GROUPS, VALIDATE-INCLUSIVE- GROUPS, VALIDATE-CONDITIONAL-REQUIREMENTS) no longer each independently
rebuild their own key/target lookup tables from the validated-specs list
on every parse; the tables are now cached once per app/command scope
(alongside the existing relation-graph cache) and threaded through.
Measured on an app combining multiple relation kinds on overlapping
options: PARSE-ARGV ~37% faster (2.862us -> 1.801us per call). No public
API or observable behavior changed; see tests/cases-parser-benchmark.lisp.