Clingon
Command-line options parser system for Common Lisp
Install / Use
/learn @dnaeon/ClingonREADME
- clingon
=clingon= is a command-line options parser system for Common Lisp.
A summary of the features supported by =clingon= is provided below.
- Native support for sub-commands
- Support for command aliases
- Short and long option names support
- Related options may be grouped into categories
- Short options may be collapsed as a single argument, e.g. =-xyz=
- Long options support both notations - =--long-opt arg= and =--long-opt=arg=.
- Automatic generation of help/usage information for commands and sub-commands
- Out of the box support for =--version= and =--help= flags
- Support for various kinds of options like /string/, /integer/, /boolean/, /switches/, /enums/, /list/, /counter/, /filepath/, etc.
- Sub-commands can lookup global options and flags defined in parent commands
- Support for options, which may be required
- Options can be initialized via environment variables
- Single interface for creating options using =CLINGON:MAKE-OPTION=
- Generate documentation for your command-line app
- Support for =pre-hook= and =post-hook= actions for commands, which allows invoking functions before and after the respective handler of the command is executed
- Support for Bash and Zsh shell completions
- =clingon= is extensible, so if you don't find something you need you can extend it by developing a new option kind, or even new mechanism for initializing options, e.g. by looking up an external key/value store.
Scroll to the demo section in order to see some examples of =clingon= in action.
Other Common Lisp option parser systems, which you might consider checking out.
- [[https://github.com/libre-man/unix-opts][unix-opts]]
- [[https://github.com/sjl/adopt/][adopt]]
- [[https://github.com/didierverna/clon][clon]]
- Quick Example
Here's a really quick example of a simple CLI application, which greets people.
#+begin_src lisp (in-package :cl-user) (defpackage :clingon.example.greet (:use :cl) (:import-from :clingon) (:export :main)) (in-package :clingon.example.greet)
(defun greet/options () "Returns the options for the `greet' command" (list (clingon:make-option :string :description "Person to greet" :short-name #\u :long-name "user" :initial-value "stranger" :env-vars '("USER") :key :user)))
(defun greet/handler (cmd) "Handler for the `greet' command" (let ((who (clingon:getopt cmd :user))) (format t "Hello, ~A!~%" who)))
(defun greet/command () "A command to greet someone" (clingon:make-command :name "greet" :description "greets people" :version "0.1.0" :authors '("John Doe <john.doe@example.org") :license "BSD 2-Clause" :options (greet/options) :handler #'greet/handler))
(defun main () "The main entrypoint of our CLI program" (let ((app (greet/command))) (clingon:run app))) #+end_src
This small example shows a lot of details about how apps are structured with =clingon=.
You can see there's a =main= function, which will be the entrypoint for our ASDF system. Then you can find the =greet/command= function, which creates and returns a new command.
The =greet/options= functions returns the options associated with our sample command.
And we also have the =greet/handler= function, which is the function that will be invoked when users run our command-line app.
This way of organizing command, options and handlers makes it easy to re-use common options, or even handlers, and wire up any sub-commands anyway you prefer.
You can find additional examples included in the test suite for =clingon=.
- Demo
You can also build and run the =clingon= demo application, which includes the =greet= command introduced in the previous section, along with other examples.
[[./images/clingon-demo.gif]]
Clone the [[https://github.com/dnaeon/clingon][clingon]] repo in your [[https://www.quicklisp.org/beta/faq.html][Quicklisp local-projects]] directory.
#+begin_src shell git clone https://github.com/dnaeon/clingon #+end_src
Register it to your local Quicklisp projects.
#+begin_src lisp CL-USER> (ql:register-local-projects) #+end_src
** Building the Demo App
You can build the demo app using SBCL with the following command.
#+begin_src shell LISP=sbcl make demo #+end_src
Build the demo app using Clozure CL:
#+begin_src shell LISP=ccl make demo #+end_src
In order to build the demo app using ECL you need to follow these instructions, which are ECL-specific. See [[https://common-lisp.net/project/ecl/static/manual/System-building.html#Compiling-with-ASDF][Compiling with ASDF from the ECL manual]] for more details. First, load the =:clingon.demo= system.
#+begin_src lisp (ql:quickload :clingon.demo) #+end_src
And now build the binary with ECL:
#+begin_src lisp (asdf:make-build :clingon.demo :type :program :move-here #P"./" :epilogue-code '(clingon.demo:main)) #+end_src
This will create a new executable =clingon-demo=, which you can now execute.
Optionally, you can also enable the bash completions support.
#+begin_src shell APP=clingon-demo source extras/completions.bash #+end_src
In order to activate the Zsh completions, install the completions script in your =~/.zsh-completions= directory (or anywhere else you prefer) and update your =~/.zshrc= file, so that the completions are loaded.
Make sure that you have these lines in your =~/.zshrc= file.
#+begin_src shell fpath=(~/.zsh-completions $fpath) autoload -U compinit compinit #+end_src
The following command will generate the Zsh completions script.
#+begin_src shell ./clingon-demo zsh-completion > ~/.zsh-completions/_clingon-demo #+end_src
Use the =--help= flag to see some usage information about the demo application.
#+begin_src shell ./clingon-demo --help #+end_src
- Requirements
- [[https://www.quicklisp.org/beta/][Quicklisp]]
- Installation
The =clingon= system is not yet part of Quicklisp, so for now you need to install it in your local Quicklisp projects.
Clone the repo in your [[https://www.quicklisp.org/beta/faq.html][Quicklisp local-projects]] directory.
#+begin_src lisp (ql:register-local-projects) #+end_src
Then load the system.
#+begin_src lisp (ql:quickload :clingon) #+end_src
- Step By Step Guide
In this section we will implement a simple CLI application, and explain at each step what and why we do the things we do.
Once you are done with it, you should have a pretty good understanding of the =clingon= system and be able to further extend the sample application on your own.
We will be developing the application interactively and in the REPL. Finally we will create an ASDF system for our CLI app, so we can build it and ship it.
The code we develop as part of this section will reside in a file named =intro.lisp=. Anything we write will be sent to the Lisp REPL, so we can compile it and get quick feedback about the things we've done so far.
You can find the complete code we'll develop in this section in the =clingon/examples/intro= directory.
** Start the REPL
Start up your REPL session and let's load the =clingon= system.
#+begin_src lisp CL-USER> (ql:quickload :clingon) To load "clingon": Load 1 ASDF system: clingon ; Loading "clingon"
(:CLINGON) #+end_src
** Create a new package
First, we will define a new package for our application and switch to it.
#+begin_src lisp (in-package :cl-user) (defpackage :clingon.intro (:use :cl) (:import-from :clingon) (:export :main)) (in-package :clingon.intro) #+end_src
We have our package, so now we can proceed to the next section and create our first command.
** Creating a new command
The first thing we'll do is to create a new command. Commands are created using the =CLINGON:MAKE-COMMAND= function.
Each command has a name, description, any options that the command accepts, any sub-commands the command knows about, etc.
The command in =clingon= is represented by the =CLINGON:COMMAND= class, which contains many other slots as well, which you can lookup.
#+begin_src lisp (defun top-level/command () "Creates and returns the top-level command" (clingon:make-command :name "clingon-intro" :description "my first clingon cli app" :version "0.1.0" :license "BSD 2-Clause" :authors '("John Doe john.doe@example.com"))) #+end_src
This is how our simple command looks like. For now it doesn't do much, and in fact it won't execute anything, but we will fix that as we go.
What is important to note, is that we are using a convention here to make things easier to understand and organize our code base.
Functions that return new commands will be named =<name>/command=. A similar approach is taken when we define options for a given command, e.g. =<name>/options= and for sub-commands we use =<name>/sub-commands=. Handlers will use the =<name>/handler= notation.
This makes things easier later on, when we introduce new sub-commands, and when we need to wire things up we can refer to our commands using the established naming convention. Of course, it's up to you to decide which approach to take, so feel free to adjust the layout of the code to your personal preferences. In this guide we will use the afore mentioned approach.
Commands can be linked together in order to form a tree of commands and sub-commands. We will talk about that one in more details in the later sections of this guide.
** Adding options
Next, we will add a couple of options. Similar to the previous section we will define a new function, which simply returns a list of valid options. Defining it in the following way would make it easier to re-use these options later on, in case you have another command, which uses the exact same set of options.
=clingon= exposes a single interface for creating options via the =CLINGON:MAKE-OPTION= generic function. This unified interface will allow developers to create and ship new option kinds, and still have their users leverage a common interface for the options via the =CLINGON:MAKE-OPTION= interfa
Related Skills
node-connect
339.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.9kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
339.5kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.9kCommit, push, and open a PR
