Romulan now supports CLIs without sub-commands ══════════════════════════════════════════════ Background ────────── 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. Romulan ─────── 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. [Releasing Romulan 1.0.0 (first release)] <./2023-04-16_romulan-1.0.0.org> [release 1.1.1] Comments ════════ 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]. [here on Mastodon]