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:
git clone https://github.com/nerima-lisp/cl-cli ~/common-lisp/cl-cli(asdf:load-system "cl-cli")=== "Nix (recommended for development)"
The flake wires up [`cl-prolog-kit`](https://github.com/nerima-lisp/cl-prolog-kit),
[`cl-weave`](https://github.com/nerima-lisp/cl-weave),
[`cl-process-kit`](https://github.com/nerima-lisp/cl-process-kit) (plus its
own [`cl-boundary-kit`](https://github.com/nerima-lisp/cl-boundary-kit) /
[`cl-log-kit`](https://github.com/nerima-lisp/cl-log-kit) /
[`cl-codec-kit`](https://github.com/nerima-lisp/cl-codec-kit) dependencies),
[`cl-json-kit`](https://github.com/nerima-lisp/cl-json-kit),
[`cl-concurrent-kit`](https://github.com/nerima-lisp/cl-concurrent-kit), and
[`cl-host-kit`](https://github.com/nerima-lisp/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:
```bash
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](reference/compatibility.md#platforms). The flake is
generated by [`cl-nix-forge`](https://github.com/nerima-lisp/cl-nix-forge)'s
`mkPackageFlake`, and also brings in
[`paredit-cli`](https://github.com/nerima-lisp/paredit-cli) for `nix develop`
— both are development-only inputs and do not affect a consumer's own
flake.
=== "Running the test suite without Nix"
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:
```bash
sbcl --script run-tests.lisp
```
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.
(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.
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.
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.
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)))))Every app gets --help / -h for free, and --version / -V once the app
declares a :version string:
$ sbcl --script demo.lisp -- --help
Usage: demo [global-options] <command> [args]
...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.
- 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