Update on cli4clj for easing the implementation of CLIs in Clojure

In my last post, I introduced my first shot on cli4clj, which is intended to ease the implementation of command line interfaces (CLIs) in Clojure. Since then, I thought a lot about the future directions of cli4clj and did various experiments for further improving it. In this post, I first give an overview over the most important improvements and afterwards outline some more details about the new implementation and on how to use cli4clj.

The major improvements since the last version are:

  • Command history
  • Command line editing
  • Tab completion
  • Functionality for testing CLIs

Command history, command line editing, and tab completion are implemented with the help of jline2. While currently the command history is not persisted across CLI runs, jline2 also allows to store the history of commands such that perspectively this feature could be added as well. Tab completion currently only considers the available commands. These features are present out of the box and nothing special needs to be implemented for activating them.

With respect to testing CLIs, I added functionality for programmatically entering input and for evaluating the corresponding output. In a sense, programmatically entering input could be also considered as “scripting” actions in the CLI. For evaluating the output, the resulting string output, either to stdout or stderr, is returned which can then be used to verify that the CLI actions resulted in the desired behavior.

Simple examples for using the testing functionality are provided in the projects test implementation. For convenience, I also include two examples below:

(deftest add-cmd-cli-interaction-stdout-test
 (let [cli-opts {:cmds {:add {:fn #(+ %1 %2)}}}
       test-cmd-input ["add 1 2"]
       out-string (test-cli-stdout #(start-cli cli-opts) test-cmd-input)]
   (is (= "3" out-string))))

(deftest add-cmd-cli-interaction-stdout-multiline-test
 (let [cli-opts {:cmds {:add {:fn #(+ %1 %2)}}}
       test-cmd-input ["add 1 2" "add 3 4" "add 5 6"]
       out-string (test-cli-stdout #(start-cli cli-opts) test-cmd-input)]
   (is (= (expected-string ["3" "7" "11"]) out-string))))

The first example only covers a single command. The second example shows how multiple commands can be handled. Input commands are defined as a vector of strings in which each string corresponds to one line of input to the CLI. The “expected-string” function is a convenience function to format the expected strings into the format as will be emitted by the test-cli-xxxxx function (The most important point here is that the platform specific line delimiter will be used.). Furthermore, depending on the test case it may be desired to evaluate the output that is printed to either stdout or stderr. The above example show the functions for stdout. Below is an example for stderr:

(deftest add-cmd-cli-interaction-cmd-error-test
 (let [cli-opts {:cmds {:div {:fn #(/ %1 %2)}}}
       test-cmd-input ["div 1 0"]
       out-string (test-cli-stderr #(start-cli cli-opts) test-cmd-input)]
   (is (= "Divide by zero" out-string))))

My main use case for CLIs is to implement them in the -main method for launching them via simple “java -jar xxxxx” calls. Thus, I implemented the testing functionality such that it can also be easily used for testing -main methods without the need to pass additional parameters to these methods etc. Examples of how this is done can be seen in https://github.com/ruedigergad/cli4clj/blob/master/test/cli4clj/test/example.clj which demonstrates the testing functionality for the example -main method that is included cli4clj.

Overall, I am much happier with the current state of cli4clj right now. I think that it now suits my needs much better than before. I especially like the interactive features provided by jline2 and in addition the new testing capabilities which should ease the implementation and improve the understandability of tests.

As usual, the latest version of cli4clj is available on clojars. I hope that you consider cli4clj useful. Comments and inspirations are always highly appreciated.

This entry was posted in cli4clj, Libs. and tagged , , , . Bookmark the permalink.

1 Response to Update on cli4clj for easing the implementation of CLIs in Clojure

  1. Pingback: cli4clj Version 1.0.0 Released | ruedigergad

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.