Merge optparse-clj and increase modularity


  • Type: Enhancement Enhancement
  • Status: Closed Closed
  • Priority: Major Major
  • Resolution: Completed
  • Affects Version/s: None
  • Fix Version/s: None
  • Component/s: None
  • Labels:


On Sun 25 Aug 2013 at 09:05:15PM -0500, gaz jones wrote:

> Hey, i am the current maintainer of tools.cli - i have very little
> time to make any changes to it at the moment (kids ). I'm not sure
> what the process is for adding you as a developer or transferring
> ownership etc but if I'm happy to do so as I have no further plans for
> working on it.

Hello Gareth,

Sorry for delay in action. I submitted my Clojure CA that week and have
been casually awaiting confirmation ever since.

Only today have I noticed that my name has appeared on, so I suppose that is confirmation

This is my proposal for tools.cli:

  • Merge the CLI arguments lexer from to
    support GNU option parsing conventions.



GNU options parsing spec:

  • Adapt tools.cli/cli to use the arguments lexer, then freeze it to
    maintain backwards compatibility.
  • Create a new function tools.cli/parse-opts, based largely on the
    design of guns.cli.optparse/parse, that supports the following
  • Granular options specification map.

Given the following setup:

(ns my.ns

(def cli-options
[["s" "-server HOSTNAME" "Remote server"
:default ( \"\")
:default-desc ""
:parse-fn #( %)
:assert-fn (partial instance? Inet4Address)
:assert-msg "%s is not an IPv4 host"]

A call to ( cli-options)
will result in the following PersistentArrayMap:

{option-id ; :server
{:short-opt String ; "-s"
:long-opt String ; "--server"
:required String ; "HOSTNAME"
:desc String ; "Remote server"
:default Object ; #<Inet4Address>
:default-desc String ; ""
:parse-fn IFn ; #(InetAddress/getByName %)
:assoc-fn IFn ; assoc
:assert-fn IFn ; (partial instance? Inet4Address)
:assert-msg String ; "%s is not an IPv4 host"

The optspec compiler will verify uniqueness of option-id,
:short-opt, and :long-opt values and throw an AssertionError on

The optspec map is a PAM to preserve options ordering for summary

  • Customizable options summary.

tools.cli/parse-opts will return an options summary string to
the caller. Printing the summary with a banner will be the
responsibility of the caller.

The default options summary will look like:

-p, --port NUMBER 8080 Remote port
-s, --server HOSTNAME Remote server
--detach Detach and run in the background
-h, --help

The above format can be changed by supplying an optional :summary-fn
flag that will receive the optspec map values from above and return
a summary string. The default summary-fn will be a public var.

This addresses TCLI-3.

  • Optional in-order options processing, with trailing options parsed
    by default.

This is necessary for managing different option sets for
subcommands. Indirectly addresses TCLI-5.

  • No runtime exceptions.

While parse-opts will throw an AssertionError for duplicate
option-id, :short-opt, and :long-opt values during compilation,
option parsing errors will no longer throw exceptions.

Instead, a map of {option-id error-string} will be provided, or nil
if there are no errors. Correspondingly, parse-opts will have the
following function prototype:

[argument-seq [& option-vectors] & compiler-flags]
{:options PersistentArrayMap ; optspec above
:arguments PersistentVector ; non-optarg arguments
:summary String ; options summary produced by summary-fn
:errors {Keyword String} ; error messages by option

The expected usage of this function will look like:

(def usage-banner
"Usage: my-program [options] arg1 arg2\n\nOptions:\n")

(defn exit [status msg]
(println msg)
(System/exit status))

(defn -main [& argv]
(let [{:keys [options arguments summary errors]}
(cli/parse-opts argv cli-options :in-order true)]
(when (:help options)
(exit 0 (str usage-banner summary)))
(when (not= (count arguments) 2)
(exit 1 (str usage-banner summary)))
(when errors
(exit 1 (string/join "\n" (cons "The following errors have occured:\n"
(vals errors)))))
(apply my-program! options arguments)))

  • ClojureScript support. currently supports CLJS/node.js via
the cljx preprocessor and an extern file for the Closure Compiler
`process` namespace.

If this is desirable for tools.cli, a similar setup will be applied,
except that I will attempt to avoid cljx for easier hacking.

Comments are appreciated, and I am eager to amend this proposal to gain
community acceptance.

Sung Pae


Sung Pae made changes -
Field Original Value New Value
Assignee Gareth Jones [ gar3thjon3s ] Sung Pae [ guns ]
Sung Pae made changes -
Status Open [ 1 ] Resolved [ 5 ]
Resolution Completed [ 1 ]
Sean Corfield made changes -
Status Resolved [ 5 ] Closed [ 6 ]


Vote (0)
Watch (1)


  • Created: