|
35 | 35 | Long options with `=` are always parsed as option + optarg, even if nothing |
36 | 36 | follows the `=` sign. |
37 | 37 |
|
38 | | - If the :in-order flag is true, the first non-option, non-optarg argument |
| 38 | + If the :subcommand option is present, the first non-option, non-optarg argument |
39 | 39 | stops options processing. This is useful for handling subcommand options." |
40 | 40 | [required-set args & options] |
41 | | - (let [{:keys [in-order]} (apply hash-map options)] |
| 41 | + (let [{:keys [subcommand]} (apply hash-map options)] |
42 | 42 | (loop [opts [] argv [] [car & cdr] args] |
43 | 43 | (if car |
44 | 44 | (condp re-seq car |
|
66 | 66 | (recur (conj os [:short-opt o]) cs) |
67 | 67 | [(conj os [:short-opt o]) cdr]))))] |
68 | 68 | (recur (into opts os) argv cdr)) |
69 | | - (if in-order |
| 69 | + (if subcommand |
70 | 70 | (recur opts (into argv (cons car cdr)) []) |
71 | 71 | (recur opts (conj argv car) cdr))) |
72 | 72 | [opts argv])))) |
|
282 | 282 | If the :no-defaults flag is true, only options specified in the tokens are |
283 | 283 | included in the option-map. |
284 | 284 |
|
285 | | - Unknown options, missing options, missing required arguments, option |
286 | | - argument parsing exceptions, and validation failures are collected into |
287 | | - a vector of error message strings. |
| 285 | + By default, unknown options, missing options, missing required arguments, |
| 286 | + option argument parsing exceptions, and validation failures are collected |
| 287 | + into a vector of error message strings. |
| 288 | +
|
| 289 | + If :subcommand :implicit is provided, unknown options trigger in-order |
| 290 | + processing, collecting the rest of the tokens as positional arguments. |
288 | 291 |
|
289 | 292 | If the :strict flag is true, required arguments that match other options |
290 | 293 | are treated as missing, instead of a literal value beginning with - or --. |
291 | 294 |
|
292 | 295 | Returns [option-map error-messages-vector]." |
293 | 296 | [specs tokens & options] |
294 | | - (let [{:keys [no-defaults strict]} (apply hash-map options) |
| 297 | + (let [{:keys [no-defaults strict subcommand]} (apply hash-map options) |
295 | 298 | defaults (default-option-map specs :default) |
296 | 299 | default-fns (default-option-map specs :default-fn) |
297 | | - requireds (missing-errors specs)] |
| 300 | + requireds (missing-errors specs) |
| 301 | + collecting (atom false)] |
298 | 302 | (-> (reduce |
299 | | - (fn [[m ids errors] [opt-type opt optarg]] |
300 | | - (if-let [spec (find-spec specs opt-type opt)] |
301 | | - (let [[value error] (parse-optarg spec opt optarg) |
302 | | - id (:id spec)] |
303 | | - (if-not (= value ::error) |
304 | | - (if (and strict |
305 | | - (or (find-spec specs :short-opt optarg) |
306 | | - (find-spec specs :long-opt optarg))) |
307 | | - [m ids (conj errors (missing-required-error opt (:required spec)))] |
308 | | - (let [m' (if-let [update-fn (:update-fn spec)] |
309 | | - (if (:multi spec) |
310 | | - (update m id update-fn value) |
311 | | - (update m id update-fn)) |
312 | | - ((:assoc-fn spec assoc) m id value))] |
313 | | - (if (:post-validation spec) |
314 | | - (let [[value error] (validate (get m' id) spec opt optarg)] |
315 | | - (if (= value ::error) |
316 | | - [m ids (conj errors error)] |
317 | | - [m' (conj ids id) errors])) |
318 | | - [m' (conj ids id) errors]))) |
319 | | - [m ids (conj errors error)])) |
320 | | - [m ids (conj errors (str "Unknown option: " (pr-str opt)))])) |
321 | | - [defaults [] []] tokens) |
| 303 | + (fn [[m ids errors args] [opt-type opt optarg]] |
| 304 | + (if-let [spec (when-not @collecting (find-spec specs opt-type opt))] |
| 305 | + (let [[value error] (parse-optarg spec opt optarg) |
| 306 | + id (:id spec)] |
| 307 | + (if-not (= value ::error) |
| 308 | + (if (and strict |
| 309 | + (or (find-spec specs :short-opt optarg) |
| 310 | + (find-spec specs :long-opt optarg))) |
| 311 | + [m ids (conj errors (missing-required-error opt (:required spec))) args] |
| 312 | + (let [m' (if-let [update-fn (:update-fn spec)] |
| 313 | + (if (:multi spec) |
| 314 | + (update m id update-fn value) |
| 315 | + (update m id update-fn)) |
| 316 | + ((:assoc-fn spec assoc) m id value))] |
| 317 | + (if (:post-validation spec) |
| 318 | + (let [[value error] (validate (get m' id) spec opt optarg)] |
| 319 | + (if (= value ::error) |
| 320 | + [m ids (conj errors error)] |
| 321 | + [m' (conj ids id) errors])) |
| 322 | + [m' (conj ids id) errors args]))) |
| 323 | + [m ids (conj errors error) args])) |
| 324 | + (if (or @collecting (= :implicit subcommand)) |
| 325 | + (do |
| 326 | + (reset! collecting true) |
| 327 | + [m ids errors (conj args opt)]) |
| 328 | + [m ids (conj errors (str "Unknown option: " (pr-str opt))) args]))) |
| 329 | + [defaults [] [] []] tokens) |
322 | 330 | (#(reduce |
323 | | - (fn [[m ids errors] [id error]] |
| 331 | + (fn [[m ids errors args] [id error]] |
324 | 332 | (if (contains? m id) |
325 | | - [m ids errors] |
326 | | - [m ids (conj errors error)])) |
| 333 | + [m ids errors args] |
| 334 | + [m ids (conj errors error) args])) |
327 | 335 | % requireds)) |
328 | 336 | (#(reduce |
329 | | - (fn [[m ids errors] [id f]] |
| 337 | + (fn [[m ids errors args] [id f]] |
330 | 338 | (if (contains? (set ids) id) |
331 | | - [m ids errors] |
332 | | - [(assoc m id (f (first %))) ids errors])) |
| 339 | + [m ids errors args] |
| 340 | + [(assoc m id (f (first %))) ids errors args])) |
333 | 341 | % default-fns)) |
334 | | - (#(let [[m ids errors] %] |
| 342 | + (#(let [[m ids errors args] %] |
335 | 343 | (if no-defaults |
336 | | - [(select-keys m ids) errors] |
337 | | - [m errors])))))) |
| 344 | + [(select-keys m ids) errors args] |
| 345 | + [m errors args])))))) |
338 | 346 |
|
339 | 347 | (defn make-summary-part |
340 | 348 | "Given a single compiled option spec, turn it into a formatted string, |
|
588 | 596 | A few function options may be specified to influence the behavior of |
589 | 597 | parse-opts: |
590 | 598 |
|
591 | | - :in-order Stop option processing at the first unknown argument. Useful |
592 | | - for building programs with subcommands that have their own |
593 | | - option specs. |
594 | | -
|
595 | 599 | :no-defaults Only include option values specified in arguments and do not |
596 | 600 | include any default values in the resulting options map. |
597 | 601 | Useful for parsing options from multiple sources; i.e. from a |
|
601 | 605 | matches any other option, it is considered to be missing (and |
602 | 606 | you have a parse error). |
603 | 607 |
|
| 608 | + :subcommand Stop option processing at the first unknown argument. Useful |
| 609 | + for building programs with subcommands that have their own |
| 610 | + option specs. Can be set to :explicit or :implicit. :explicit |
| 611 | + requires a non-option (explicit) subcommand argument to |
| 612 | + trigger collection of subcommand arguments. :implicit treats an |
| 613 | + unknown option as starting a new subcommand. |
| 614 | +
|
604 | 615 | :summary-fn A function that receives the sequence of compiled option specs |
605 | 616 | (documented at #'clojure.tools.cli/compile-option-specs), and |
606 | 617 | returns a custom option summary string. |
607 | 618 | " |
608 | 619 | [args option-specs & options] |
609 | | - (let [{:keys [in-order no-defaults strict summary-fn]} (apply hash-map options) |
| 620 | + (let [{:keys [in-order no-defaults strict subcommand summary-fn]} |
| 621 | + (apply hash-map options) |
| 622 | + ;; handle deprecation of in-order here: |
| 623 | + subcommand (if subcommand |
| 624 | + (do |
| 625 | + (when in-order |
| 626 | + (throw (ex-info ":in-order is deprecated and cannot be used if :subcommand is present" |
| 627 | + {:in-order in-order |
| 628 | + :subcommand subcommand}))) |
| 629 | + subcommand) |
| 630 | + (when in-order :explicit)) |
610 | 631 | specs (compile-option-specs option-specs) |
611 | 632 | req (required-arguments specs) |
612 | | - [tokens rest-args] (tokenize-args req args :in-order in-order) |
613 | | - [opts errors] (parse-option-tokens specs tokens |
614 | | - :no-defaults no-defaults :strict strict)] |
| 633 | + [tokens rest-args] (tokenize-args req args :subcommand subcommand) |
| 634 | + [opts errors implicit-args] |
| 635 | + (parse-option-tokens specs tokens |
| 636 | + :no-defaults no-defaults |
| 637 | + :strict strict |
| 638 | + :subcommand subcommand)] |
615 | 639 | {:options opts |
616 | | - :arguments rest-args |
| 640 | + :arguments (into rest-args implicit-args) |
617 | 641 | :summary ((or summary-fn summarize) specs) |
618 | 642 | :errors (when (seq errors) errors)})) |
619 | 643 |
|
|
0 commit comments