Skip to content

Latest commit

 

History

History
190 lines (153 loc) · 7.44 KB

File metadata and controls

190 lines (153 loc) · 7.44 KB

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:

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.

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:

$ 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.

Where to go next