Last active
December 28, 2015 22:48
-
-
Save guns/7573819 to your computer and use it in GitHub Desktop.
tools.cli proposal
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ; #<Inet4Address example.com/93.184.216.119> | |
: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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
As mentioned in my email to you, I think it would be nice to consider offering much of the option handling for arguments too. argparse in Python does this, and it's a real pleasure to use. The idea is that if you're going to have a library that can help validate various aspects about your options, help convert them, and provide defaults then why would you foist that same work on your callers for argument? The specification is largely the same. The real difference is that argument order matters where option order generally does not. Since you're already using a list of lists, I think this would work out nicely.
I do think adding ClojureScript support would be a nice addition too.