Browse files

Removing built-in help stuff, remove all System/exit calls, move :args

out and return a vector from the call to cli.
  • Loading branch information...
1 parent e0f6e6f commit 7b066db4b5ecdb0312f21dad313550e762ddf31a @gar3thjon3s gar3thjon3s committed Oct 29, 2011
Showing with 112 additions and 109 deletions.
  1. +36 −18 README.md
  2. +17 −22 src/main/clojure/clojure/tools/cli.clj
  3. +59 −69 src/test/clojure/clojure/tools/cli_test.clj
View
54 README.md
@@ -14,30 +14,36 @@ with args of:
["-p" "8080"
"--no-verbose"
- "--log-directory" "/tmp"]
+ "--log-directory" "/tmp"
+ "some-file"]
-will produce a clojure map with the names picked out for you as keywords:
+will produce a vector containing three elements:
- {:port 8080
- :host "localhost"
- :verbose false
+a clojure map with the names picked out for you as keywords:
+
+ {:port 8080
+ :host "localhost"
+ :verbose false
:log-directory "/tmp"}
-A flag of -h or --help is provided which will print a documentation
-string to STDOUT and call System/exit:
+a vector of trailing arguments that are not options:
+
+ ["some-file"]
+
+and a documentation string to use to provide help:
- Usage:
+ "Usage:
Switches Default Required Desc
-------- ------- -------- ----
-p, --port Yes Listen on this port
- -h, --host localhost No The hostname
+ -t, --host localhost No The hostname
-v, --no-verbose --verbose true No
- -l, --log-directory /some/path No
+ -l, --log-directory /some/path No"
## Options
-An argument is specified by providing a vector of information:
+An option is specified by providing a vector of information:
Switches should be provided first, from least to most specific. The
last switch you provide will be used as the name for the argument in
@@ -85,26 +91,25 @@ set the argument to false:
(cli ["-v"]
["-v" "--[no-]verbose"])
- => {:verbose true}
+ => [{:verbose true}, ...]
(cli ["--no-verbose"]
["-v" "--[no-]verbose"])
- => {:verbose false}
+ => [{:verbose false}, ...]
Note: there is no short-form to set the flag to false (-no-v will not
work!).
## Trailing Arguments
-After all of your arguments have been parsed, any trailing arguments
-given will be available to your program under a key called :args in
-the resulting hash-map:
+Any trailing arguments given to `cli` are returned as the second item
+in the resulting vector:
(cli ["--port" "9999" "some" "extra" "arguments"]
["--port" :parse-fn #(Integer. %)])
- => {:port 9999, :args ["some" "extra" "arguments"]}
+ => [{:port 9999}, ["some" "extra" "arguments"], ...]
This allows you to deal with parameters such as filenames which are
commonly provided at the end of an argument list.
@@ -115,10 +120,23 @@ double-hyphen:
(cli ["--port" "9999" "-- "some" "--extra" "arguments"]
["--port" :parse-fn #(Integer. %)])
- => {:port 9999, :args ["some" "--extra" "arguments"]}
+ => [{:port 9999}, ["some" "--extra" "arguments"], ...]
This is useful when your extra arguments look like switches.
+## Banner
+
+The third item in the resulting vector is a banner useful for
+providing help to the user:
+
+ (let [[options args banner] (cli ["--faux" "bar"]
+ ["-h" "--help" "Show help" :default false :flag true]
+ ["-f" "--faux" "The faux du fafa"])]
+ (when (:help options)
+ (println banner)
+ (System/exit 0))
+ (println options))
+
## License
Copyright (c) Rich Hickey and contributors. All rights reserved.
View
39 src/main/clojure/clojure/tools/cli.clj
@@ -10,7 +10,7 @@
(if required "Yes" "No")
(or docs "")])
-(defn show-help [specs]
+(defn banner-for [specs]
(println "Usage:")
(println)
(let [docs (into (map build-doc specs)
@@ -25,10 +25,6 @@
(cl-format true "~{ ~vA ~vA ~vA ~vA ~}" v)
(prn))))
-(defn help-and-quit [specs]
- (show-help specs)
- (System/exit 0))
-
(defn name-for [k]
(replace k #"^--no-|^--\[no-\]|^--|^-" ""))
@@ -50,33 +46,36 @@
(defn default-values-for
[specs]
- (into {:args []} (for [s specs] [(s :name) (s :default)])))
+ (into {} (for [s specs] [(s :name) (s :default)])))
(defn apply-specs
[specs args]
- (loop [result (default-values-for specs)
- args args]
+ (loop [options (default-values-for specs)
+ extra-args []
+ args args]
(if-not (seq args)
- result
+ [options extra-args]
(let [opt (first args)
spec (spec-for opt specs)]
(cond
(end-of-args? opt)
- (recur (assoc result :args (vec (rest args))) nil)
+ (recur options (into extra-args (vec (rest args))) nil)
(and (opt? opt) (nil? spec))
(throw (Exception. (str "'" opt "' is not a valid argument")))
(and (opt? opt) (spec :flag))
- (recur (assoc result (spec :name) (flag-for opt))
+ (recur (assoc options (spec :name) (flag-for opt))
+ extra-args
(rest args))
(opt? opt)
- (recur (assoc result (spec :name) ((spec :parse-fn) (second args)))
+ (recur (assoc options (spec :name) ((spec :parse-fn) (second args)))
+ extra-args
(drop 2 args))
:default
- (recur (update-in result [:args] conj (first args)) (rest args)))))))
+ (recur options (conj extra-args (first args)) (rest args)))))))
(defn switches-for
[switches flag]
@@ -104,10 +103,6 @@
:flag flag}
options)))
-(defn wants-help?
- [args]
- (some #(or (= % "-h") (= % "--help")) args))
-
(defn ensure-required-provided
[m specs]
(doseq [s specs
@@ -118,8 +113,8 @@
(defn cli
[args & specs]
(let [specs (map generate-spec specs)]
- (when (wants-help? args)
- (help-and-quit specs))
- (let [result (apply-specs specs args)]
- (ensure-required-provided result specs)
- result)))
+ (let [[options extra-args] (apply-specs specs args)
+ banner (with-out-str (banner-for specs))]
+ (ensure-required-provided options specs)
+ [options extra-args banner])))
+
View
128 src/test/clojure/clojure/tools/cli_test.clj
@@ -4,63 +4,53 @@
(testing "syntax"
(deftest should-handle-simple-strings
- (is (= {:host "localhost"
- :args []}
- (cli ["--host" "localhost"]
- ["--host"]))))
+ (is (= {:host "localhost"}
+ (first (cli ["--host" "localhost"]
+ ["--host"])))))
(testing "booleans"
(deftest should-handle-trues
- (is (= {:verbose true
- :args []}
- (cli ["--verbose"]
- ["--[no-]verbose"]))))
+ (is (= {:verbose true}
+ (first (cli ["--verbose"]
+ ["--[no-]verbose"])))))
(deftest should-handle-falses
- (is (= {:verbose false
- :args []}
- (cli ["--no-verbose"]
- ["--[no-]verbose"]))))
+ (is (= {:verbose false}
+ (first (cli ["--no-verbose"]
+ ["--[no-]verbose"])))))
(testing "explicit syntax"
- (is (= {:verbose true
- :args []}
- (cli ["--verbose"]
- ["--verbose" :flag true])))
- (is (= {:verbose false
- :args []}
- (cli ["--no-verbose"]
- ["--verbose" :flag true])))))
+ (is (= {:verbose true}
+ (first (cli ["--verbose"]
+ ["--verbose" :flag true]))))
+ (is (= {:verbose false}
+ (first (cli ["--no-verbose"]
+ ["--verbose" :flag true]))))))
(testing "default values"
(deftest should-default-when-no-value
- (is (= {:server "10.0.1.10"
- :args []}
- (cli []
- ["--server" :default "10.0.1.10"]))))
+ (is (= {:server "10.0.1.10"}
+ (first (cli []
+ ["--server" :default "10.0.1.10"])))))
(deftest should-override-when-supplied
- (is (= {:server "127.0.0.1"
- :args []}
- (cli ["--server" "127.0.0.1"]
- ["--server" :default "10.0.1.10"])))))
+ (is (= {:server "127.0.0.1"}
+ (first (cli ["--server" "127.0.0.1"]
+ ["--server" :default "10.0.1.10"]))))))
(deftest should-apply-parse-fn
- (is (= {:names ["john" "jeff" "steve"]
- :args []}
- (cli ["--names" "john,jeff,steve"]
- ["--names" :parse-fn #(vec (.split % ","))]))))
+ (is (= {:names ["john" "jeff" "steve"]}
+ (first (cli ["--names" "john,jeff,steve"]
+ ["--names" :parse-fn #(vec (.split % ","))])))))
(testing "aliases"
(deftest should-support-multiple-aliases
- (is (= {:server "localhost"
- :args []}
- (cli ["-s" "localhost"]
- ["-s" "--server"]))))
+ (is (= {:server "localhost"}
+ (first (cli ["-s" "localhost"]
+ ["-s" "--server"])))))
(deftest should-use-last-alias-provided-as-name-in-map
- (is (= {:server "localhost"
- :args []}
- (cli ["-s" "localhost"]
- ["-s" "--server"])))))
+ (is (= {:server "localhost"}
+ (first (cli ["-s" "localhost"]
+ ["-s" "--server"]))))))
(testing "required"
(deftest should-succeed-when-provided
@@ -74,38 +64,38 @@
(testing "extra arguments"
(deftest should-provide-access-to-trailing-args
- (is (= {:foo "bar"
- :args ["a" "b" "c"]}
- (cli ["--foo" "bar" "a" "b" "c"]
- ["-f" "--foo"]))))
+ (let [[options args _] (cli ["--foo" "bar" "a" "b" "c"]
+ ["-f" "--foo"])]
+ (is (= {:foo "bar"} options))
+ (is (= ["a" "b" "c"] args))))
(deftest should-work-with-trailing-boolean-args
- (is (= {:verbose false
- :args ["some-file"]}
- (cli ["--no-verbose" "some-file"]
- ["--[no-]verbose"]))))
+ (let [[options args _] (cli ["--no-verbose" "some-file"]
+ ["--[no-]verbose"])]
+ (is (= {:verbose false}))
+ (is (= ["some-file"] args))))
(deftest should-accept-double-hyphen-as-end-of-args
- (is (= {:foo "bar"
- :verbose true
- :args ["file" "-x" "other"]}
- (cli ["--foo" "bar" "--verbose" "--" "file" "-x" "other"]
- ["--foo"]
- ["--[no-]verbose"]))))))
+ (let [[options args _] (cli ["--foo" "bar" "--verbose" "--" "file" "-x" "other"]
+ ["--foo"]
+ ["--[no-]verbose"])]
+ (is (= {:foo "bar" :verbose true} options))
+ (is (= ["file" "-x" "other"] args))))))
(deftest all-together-now
- (is (= {:port 8080
- :host "localhost"
- :verbose false
- :log-directory "/tmp"
- :server "localhost"
- :args []}
- (cli ["-p" "8080"
- "--no-verbose"
- "--log-directory" "/tmp"
- "--server" "localhost"]
- ["-p" "--port" :parse-fn #(Integer. %)]
- ["--host" :default "localhost"]
- ["--[no-]verbose" :default true]
- ["--log-directory" :default "/some/path"]
- ["--server"]))))
+ (let [[options args _] (cli ["-p" "8080"
+ "--no-verbose"
+ "--log-directory" "/tmp"
+ "--server" "localhost"
+ "filename"]
+ ["-p" "--port" :parse-fn #(Integer. %)]
+ ["--host" :default "localhost"]
+ ["--[no-]verbose" :default true]
+ ["--log-directory" :default "/some/path"]
+ ["--server"])]
+ (is (= {:port 8080
+ :host "localhost"
+ :verbose false
+ :log-directory "/tmp"
+ :server "localhost"} options))
+ (is (= ["filename"] args))))

0 comments on commit 7b066db

Please sign in to comment.