Skip to content

Commit 2a779bf

Browse files
committed
TCLI-96 add :multi true to modify :update-fn behavior
1 parent 991fa60 commit 2a779bf

4 files changed

Lines changed: 71 additions & 22 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Change Log
22

3-
* Release 1.0.next
4-
* Planning to add `:multi true` to modify behavior of `:update-fn` [TCLI-96](https://clojure.atlassian.net/browse/TCLI-96).
3+
* Release 1.0.next (on **master**)
4+
* Add `:multi true` to modify behavior of `:update-fn` [TCLI-96](https://clojure.atlassian.net/browse/TCLI-96).
55

66
* Release 1.0.194 2020-02-20
77
* Switch to 1.0.x versioning.

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ http://clojure.github.io/tools.cli/index.html#clojure.tools.cli/parse-opts
102102
An interesting library built on top of `tool.cli` that provides a more compact,
103103
higher-level API is [cli-matic](https://github.com/l3nz/cli-matic).
104104

105-
## New Features in 0.3.x
105+
## Since Release 0.3.x
106106

107107
### Better Option Tokenization
108108

@@ -249,6 +249,12 @@ only `parse-opts` and `summarize` were available.
249249
:default 0
250250
;; Use :update-fn to create non-idempotent options (:default is applied first)
251251
:update-fn inc]
252+
["-f" "--file NAME" "File names to read"
253+
:multi true ; use :update-fn to combine multiple instance of -f/--file
254+
:default []
255+
;; with :multi true, the :update-fn is passed both the existing parsed
256+
;; value(s) and the new parsed value from each option
257+
:update-fn conj]
252258
;; A boolean option that can explicitly be set to false
253259
["-d" "--[no-]daemon" "Daemonize the process" :default true]
254260
["-h" "--help"]])

src/main/clojure/clojure/tools/cli.cljc

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
"Given a sequence of column widths, return a string suitable for use in
5858
format to print a sequences of strings in those columns."
5959
[lens]
60-
(s/join (map #(str " %" (if-not (zero? %) (str "-" %)) "s") lens)))
60+
(s/join (map #(str " %" (when-not (zero? %) (str "-" %)) "s") lens)))
6161
;;
6262
;; Legacy API
6363
;;
@@ -148,7 +148,7 @@
148148
extra-args
149149
(drop 2 args))
150150

151-
:default
151+
:else
152152
(recur options (conj extra-args (first args)) (rest args)))))))
153153

154154
(defn- switches-for
@@ -160,7 +160,7 @@
160160
(and flag (s/starts-with? s "--"))
161161
[(s/replace s #"--" "--no-") s]
162162

163-
:default
163+
:else
164164
[s]))
165165
flatten))
166166

@@ -218,18 +218,18 @@
218218
[(first specs) (rest specs)]
219219
[nil specs])
220220
specs (map generate-spec specs)
221-
args (normalize-args specs args)]
222-
(let [[options extra-args] (apply-specs specs args)
223-
banner (with-out-str (banner-for desc specs))]
224-
[options extra-args banner])))
221+
args (normalize-args specs args)
222+
[options extra-args] (apply-specs specs args)
223+
banner (with-out-str (banner-for desc specs))]
224+
[options extra-args banner]))
225225

226226
;;
227227
;; New API
228228
;;
229229

230230
(def ^{:private true} spec-keys
231231
[:id :short-opt :long-opt :required :desc :default :default-desc :default-fn
232-
:parse-fn :assoc-fn :update-fn :validate-fn :validate-msg :missing])
232+
:parse-fn :assoc-fn :update-fn :multi :validate-fn :validate-msg :missing])
233233

234234
(defn- select-spec-keys
235235
"Select only known spec entries from map and warn the user about unknown
@@ -381,7 +381,7 @@
381381
(let [{:keys [validate-fn validate-msg]} spec]
382382
(or (loop [[vfn & vfns] validate-fn [msg & msgs] validate-msg]
383383
(when vfn
384-
(if (try (vfn value) (catch #?(:clj Throwable :cljs :default) e))
384+
(if (try (vfn value) (catch #?(:clj Throwable :cljs :default) _))
385385
(recur vfns msgs)
386386
[::error (validation-error opt optarg msg)])))
387387
[value nil])))
@@ -444,7 +444,9 @@
444444
(find-spec specs :long-opt optarg)))
445445
[m ids (conj errors (missing-required-error opt (:required spec)))]
446446
[(if-let [update-fn (:update-fn spec)]
447-
(update-in m [id] update-fn)
447+
(if (:multi spec)
448+
(update m id update-fn value)
449+
(update m id update-fn))
448450
((:assoc-fn spec assoc) m id value))
449451
(conj ids id)
450452
errors])
@@ -641,7 +643,9 @@
641643
You cannot specify both :assoc-fn and :update-fn for an
642644
option.
643645
644-
:update-fn A function that receives the the current parsed option value,
646+
:update-fn Without :multi true:
647+
648+
A function that receives just the existing parsed option value,
645649
and returns a new option value, for each option :id present.
646650
The default is 'identity'.
647651
@@ -659,8 +663,31 @@
659663
[\"-v\" \"--verbose\"
660664
:update-fn (fnil inc 0)]
661665
662-
You cannot specify both :assoc-fn and :update-fn for an
663-
option.
666+
With :multi true:
667+
668+
A function that receives both the existing parsed option value,
669+
and the parsed option value from each instance of the option,
670+
and returns a new option value, for each option :id present.
671+
The :multi option is ignored if you do not specify :update-fn.
672+
673+
For non-idempotent options, where you need to compute a option
674+
value based on the current value and a new value from the
675+
command line. This can sometimes be easier than use :assoc-fn.
676+
677+
[\"-f\" \"--file NAME\"
678+
:default []
679+
:update-fn conj
680+
:multi true]
681+
682+
:default is applied first. If you wish to omit the :default
683+
option value, use fnil in your :update-fn as follows:
684+
685+
[\"-f\" \"--file NAME\"
686+
:update-fn (fnil conj [])
687+
:multi true]
688+
689+
Regardless of :multi, you cannot specify both :assoc-fn
690+
and :update-fn for an option.
664691
665692
:validate A vector of [validate-fn validate-msg ...]. Multiple pairs
666693
of validation functions and error messages may be provided.

src/test/clojure/clojure/tools/cli_test.cljc

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,23 +193,31 @@
193193
:update-fn not]
194194
["-v" "--verbose"
195195
:default 0
196-
:update-fn inc]])]
196+
:update-fn inc]
197+
["-f" "--file NAME"
198+
:multi true
199+
:default []
200+
:update-fn conj]])]
197201
(is (= (parse-option-tokens specs [])
198-
[{:alpha true :verbose 0} []]))
202+
[{:alpha true :verbose 0 :file []} []]))
199203
(is (= (parse-option-tokens specs [[:short-opt "-a"]])
200-
[{:alpha false :verbose 0} []]))
204+
[{:alpha false :verbose 0 :file []} []]))
205+
(is (= (parse-option-tokens specs [[:short-opt "-f" "ONE"]
206+
[:short-opt "-f" "TWO"]
207+
[:long-opt "--file" "THREE"]])
208+
[{:alpha true :verbose 0 :file ["ONE" "TWO" "THREE"]} []]))
201209
(is (= (parse-option-tokens specs [[:short-opt "-v"]
202210
[:short-opt "-v"]
203211
[:long-opt "--verbose"]])
204-
[{:alpha true :verbose 3} []]))
212+
[{:alpha true :verbose 3 :file []} []]))
205213
(is (= (parse-option-tokens specs [[:short-opt "-v"]] :no-defaults true)
206214
[{:verbose 1} []]))))
207215
(testing "associates :id and value with :assoc-fn, without :default"
208216
(let [specs (compile-option-specs
209217
[["-a" nil
210218
:id :alpha
211219
;; use fnil to have an implied :default true
212-
:assoc-fn (fn [m k v] (update-in m [k] (fnil not true)))]
220+
:assoc-fn (fn [m k _] (update-in m [k] (fnil not true)))]
213221
["-v" "--verbose"
214222
;; use fnil to have an implied :default 0
215223
:assoc-fn (fn [m k _] (update-in m [k] (fnil inc 0)))]])]
@@ -231,11 +239,19 @@
231239
:update-fn (fnil not true)]
232240
["-v" "--verbose"
233241
;; use fnil to have an implied :default 0
234-
:update-fn (fnil inc 0)]])]
242+
:update-fn (fnil inc 0)]
243+
["-f" "--file NAME"
244+
:multi true
245+
;; use fnil to have an implied :default []
246+
:update-fn (fnil conj [])]])]
235247
(is (= (parse-option-tokens specs [])
236248
[{} []]))
237249
(is (= (parse-option-tokens specs [[:short-opt "-a"]])
238250
[{:alpha false} []]))
251+
(is (= (parse-option-tokens specs [[:short-opt "-f" "A"]
252+
[:short-opt "-f" "B"]
253+
[:long-opt "--file" "C"]])
254+
[{:file ["A" "B" "C"]} []]))
239255
(is (= (parse-option-tokens specs [[:short-opt "-v"]
240256
[:short-opt "-v"]
241257
[:long-opt "--verbose"]])

0 commit comments

Comments
 (0)