Romulan now supports CLIs without sub-commands


Command line interfaces essentially can be classified in three types as follows.

Simple command line interfaces

For these, one calls a program or script and then passes options and possible positional arguments. For example:

$ ls -lt /etc

Interfaces with subcommands

Here, the first positional argument is a command verb (a so-called subcommand) that decides what actually needs to be done.

$ git --config-env=/some/config log --oneline some-file

Typically, before the verb go global options (applicable to all subcommands) and after the verb subcommand specific options and positional arguments.

This type of command line interface has been made popular by version control systems like cvs, svn or git.

The command verb is the program name

These largely work like interfaces with subcommands, but here the command verb upon which the program dispatches is the name with which the program was called (argv[0]).

Typically the program has a name (for example my-apps), but is linked in bin under multiple different names (say foo or bar).

Calling the program via those names invokes different functionality which might do totally different things:

$ foo -n 113 some-file
$ bar some-file another-file

From their usage this type of interfaces is indistinguishable from multiple separate programs, but internally it is always the same program that is invoked. Such interfaces are useful if those different functions share a large runtime.

One popular example for such an interface is busybox.

This type of interfaces might also come handy when dumping images from lisp systems: Instead of having to dump multiple (large) images for a collection of small utilities one might just dump one image and link that under different names.


As described in Releasing Romulan 1.0.0 (first release), Romulan so far provided only the possibility to define interfaces with subcommands ("git style").

With release 1.1.1 Romulan also allows to define simple command line interfaces as described above.

For example:

(commandline-interface romulan-test (words &key user verbose)

    "shouts back anything you write"

    (:usage   "[options ...] [arguments ...]"
     :varargs t

     :options (:user (:description "user to greet"
                      :short-name #\u
                      :env-vars ("USER"))

               :verbose (:type :counter
                         :description "every -v bumps up the verbosity by 1"
                         :short-name #\v
                         :long-name "verbose")))

  (if (< 1 verbose)
      (format t "DEBUG: verbosity => ~S~%" verbose))

  (if (< 0 verbose)
      (format t "Hello, ~A!~%" user))

  (format t "~{~A~^ ~}!!!~%" (mapcar #'string-upcase words)))

This works in principle as described for subcommands in Releasing Romulan 1.0.0 (first release): For execution of the body, the command line arguments are extracted automatically from the command line and bound to the lisp parameters.


Due to legal pitfalls in Europe there is no comment section in this blog at the moment (sorry), but you can discuss this article or comment on its content ⮕ here on Mastodon.