Getting Started¶
Installing¶
cl-cli itself depends on uiop (ships with every modern ASDF) on every
implementation but SBCL, and additionally on
cl-host-kit on SBCL. Cloning
it where ASDF can find both (for example under ~/common-lisp/) is enough to
load it:
The flake wires up cl-prolog-kit,
cl-weave,
cl-process-kit (plus its
own cl-boundary-kit /
cl-log-kit /
cl-codec-kit dependencies),
cl-json-kit,
cl-concurrent-kit, and
cl-host-kit for you. The
test dependencies are only needed to run the test suite; cl-host-kit is
also a runtime dependency when cl-cli is loaded by SBCL. The optional
cl-cli/concurrent system uses cl-concurrent-kit on SBCL:
nix develop # drop into a shell with all dependencies available
nix flake check # run the sbcl and ecl suites and build the docs
The flake declares x86_64-linux (the CI platform) and aarch64-darwin
(a development output). On another host, run them inside a builder for
one of those systems; there are no flake outputs for other systems. See
Compatibility. The flake is
generated by cl-nix-forge's
mkPackageFlake, and also brings in
paredit-cli for nix develop
— both are development-only inputs and do not affect a consumer's own
flake.
Clone cl-prolog-kit, cl-weave, and cl-json-kit where ASDF can find
them, the same way as cl-cli above, then run run-tests.lisp at the
repository root:
Adding cl-process-kit, cl-boundary-kit, cl-date-kit,
cl-concurrent-kit, cl-log-kit, and cl-codec-kit alongside them
enables the extra suite that runs generated scripts through the real
shells.
Both the sbcl and ecl checks must be green. They do not run the same
thing: the shell-verification half of the suite needs cl-process-kit, whose
own cl-log-kit dependency is SBCL-only
(nerima-lisp/cl-log-kit#1),
so ECL runs the portable core. The runner prints which half it ran. See
Compatibility for the full matrix.
Your first app spec¶
(asdf:load-system "cl-cli")
(defparameter *app*
(cl-cli:make-app
:name "demo"
:version "0.1.0"
:global-options (list (cl-cli:make-option :name "verbose" :short #\v :kind :flag))
:commands (list
(cl-cli:make-command
:name "compile"
:options (list (cl-cli:make-option :name "output" :short #\o :kind :value))
:positionals (list (cl-cli:make-positional :key :input :required-p t))
:handler (lambda (invocation)
(format t "compile ~A -> ~A~%"
(cl-cli:positional-value invocation :input)
(cl-cli:option-value invocation :output)))))))
(cl-cli:run-app *app* :argv '("demo" "compile" "-o" "out.bin" "input.lisp"))
make-app builds an immutable spec — name, version, global options, and a
list of commands. run-app parses an argv list against that spec and
dispatches the matched command's :handler, returning a process exit code.
Call parse-argv directly instead when you want the parsed invocation
without running a handler (useful for tests); note that it takes argv as a
required positional argument — (cl-cli:parse-argv *app* '("demo" "compile"
"input.lisp")) — rather than as :argv.
Concurrent parsing on SBCL¶
The optional cl-cli/concurrent system keeps the portable parser unchanged
while parsing independent argv batches with a bounded worker pool:
(asdf:load-system "cl-cli/concurrent")
(cl-cli/concurrent:parse-argv-batch
*app*
'(("demo" "compile" "one.lisp")
("demo" "compile" "two.lisp"))
:parallelism 2
:max-in-flight 2)
Each element is a complete argv list. Results preserve input order, while
:parallelism controls workers and :max-in-flight bounds admitted requests.
Parser errors are propagated after submitted requests settle.
Reusable specs with the DSL¶
For named reusable building blocks, the DSL macros expand to the same
make-* constructors shown above. Use define-app, define-command,
define-option, and define-positional when the shape of a spec is known
at read or compile time:
(cl-cli:define-app *dsl-app*
(:name "dsl-demo" :version "0.1.0")
(:option "verbose" :short #\v :kind :flag)
(:command "compile" (:description "Compile a source file.")
(:positional :input :required-p t)))
(cl-cli:run-app *dsl-app*
:argv '("dsl-demo" "--verbose" "compile" "input.lisp"))
The functional make-* API remains the direct choice for dynamically
assembled specs; both forms produce the same immutable model objects.
Root positional example¶
Not every CLI needs subcommands. Attach :positionals and a :handler
directly to the app for script-style tools that dispatch on positional
arguments alone:
(defparameter *script-app*
(cl-cli:make-app
:name "script-runner"
:positionals (list (cl-cli:make-positional :key :script :required-p nil)
(cl-cli:make-positional :key :script-args :rest-p t))
:handler (lambda (invocation)
(format t "script=~S args=~S~%"
(cl-cli:positional-value invocation :script)
(cl-cli:positional-value invocation :script-args)))))
Built-in help and version¶
Every app gets --help / -h for free, and --version / -V once the app
declares a :version string:
If you want help and version as real subcommands instead of (or in
addition to) the built-in flags, splice in cl-cli:make-standard-commands. It
returns help and version by default; completion and docs are opt-in via
:include-completion-p t / :include-docs-p t — see
Shell Completion and
Documentation Generation.
Where to go next¶
- Option Values and Kinds — the six option kinds, typed values, delimiters, and repeatable options
- Option Relations and Grouping —
:requires,:conflicts-with, exclusive/inclusive groups, and help grouping - Commands and Dispatch — nested subcommands, aliases, and
:default-commandpolicy - Validation and Exit Codes — custom parsers,
option-value-source, and exit-code conventions - Migration Guide — mapping an existing hand-rolled
parser onto
cl-cli