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 http://clojure.org/contributing, so I suppose that is confirmation enough. This is my proposal for tools.cli: * Merge the CLI arguments lexer from github.com/guns/optparse-clj to support GNU option parsing conventions. Examples: https://github.com/guns/optparse-clj#features guns.cli.optparse/tokenize-arguments: https://github.com/guns/optparse-clj/blob/master/src-cljx/guns/cli/optparse.cljx#L25-74 GNU options parsing spec: https://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html * 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 features: - Granular options specification map. Given the following setup: (ns my.ns …) (def cli-options [["-s" "--server HOSTNAME" "Remote server" :default (java.net.InetAddress/getByName \"example.com\") :default-desc "example.com" :parse-fn #(java.net.InetAddress/getByName %) :assert-fn (partial instance? Inet4Address) :assert-msg "%s is not an IPv4 host"] […] ]) A call to (clojure.tools.cli/compile-option-specs 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 ; # :default-desc String ; "example.com" :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 failure. The optspec map is a PAM to preserve options ordering for summary generation. - 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 example.com 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: parse-opts: [argument-seq [& option-vectors] & compiler-flags] -> {:options {Keyword Object} ; {:server "my-server.com"} :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. github.com/guns/optparse-clj 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. Cheers, Sung Pae