cli4clj aims on easing the implementation of interactive command line interfaces (CLIs) targeting “everyone” as CLI users. One implication of “targeting everyone” is that CLIs implemented with cli4clj should be usable by “non-lispers”. Thus, cli4clj purposely does not use lisp- or Clojure-style syntax for the commands in the interactive CLI, even though it uses the Clojure REPL underneath.
Yet, people familiar with Clojure or Lisp may miss the functionality of the “full” Clojure REPL. For me, the point of missing the full Clojure REPL was reached when I had to enter quite a long list of almost repetitive arguments in one of my applications.
To counter this, cli4clj had for quite some time the, admittedly pretty hidden, :allow-eval option, which defaults to false. Setting :allow-eval to true causes that “classical” Clojure input will also be processed in cli4clj-based CLIs.
However, even with :allow-eval true, the “cli4clj world” was split into two parts, the cli4clj commands configured in the cli4clj configuration map and the “full” Clojure REPL. It was not possible, e.g., to use cli4clj commands with the Clojure-style syntax.
With cli4clj version 1.5.3, two things changed:
- :allow-eval now defaults to true.
- cli4clj commands can be used with the Clojure-style syntax.
To illustrate the effect of these changes, I use the print-cmd from the example CLI provided with cli4clj. print-cmd takes one or more arguments and prints some information about the supplied arguments. Below, the definition of print-cmd is shown:
... :print-cmd {:fn (fn [arg & opt-args] (print "Arg-type:" (type arg) "Arg: ") (pprint/pprint arg) (print "Opt-args: ") (pprint/pprint opt-args)) :short-info "Pretty print the supplied arguments." :long-info "This function pretty prints its supplied arguments. It takes at least one argument."} ...
One use case of the mixed cli4clj- and Clojure-syntax is to ease the assembly of almost repetitive argument lists. Below, an example for programmatically assembling and applying a long argument list is shown:
cli# (apply print-cmd (map #(str "arg_" %) (range 1 10))) Arg-type: java.lang.String Arg: "arg_1" Opt-args: ("arg_2" "arg_3" "arg_4" "arg_5" "arg_6" "arg_7" "arg_8" "arg_9") cli#
Similarly, arguments can now also be stored at first in a var and passed to the command in a second step:
cli# (def args (map #(str "arg_" %) (range 1 10))) #'user/args cli# (apply print-cmd args) Arg-type: java.lang.String Arg: "arg_1" Opt-args: ("arg_2" "arg_3" "arg_4" "arg_5" "arg_6" "arg_7" "arg_8" "arg_9") cli#
Another use case is the repetitive execution of a command. This is shown below for a comparably small number of repetitions:
cli# (doseq [x (range 1 4)] (print-cmd x)) Arg-type: java.lang.Long Arg: 1 Opt-args: nil Arg-type: java.lang.Long Arg: 2 Opt-args: nil Arg-type: java.lang.Long Arg: 3 Opt-args: nil cli#
I hope these changes achieve a good compromise between still keeping cli4clj easy to use for basic use cases while enabling more sophisticated functionality for advanced use cases. If you have constructive feedback or comments, please let me know.