|
11 | 11 |
|
12 | 12 | #?(:clj (set! *warn-on-reflection* true)) |
13 | 13 |
|
14 | | -;; squint can't tell an injected keyword from a string arg, so wrap injected opts |
15 | | -#?(:squint (deftype Injected [opt])) |
16 | | - |
17 | 14 | (defn merge-opts |
18 | 15 | "Merges babashka CLI options." |
19 | 16 | [m & ms] |
|
239 | 236 | {:cmds cmds |
240 | 237 | :args args}))) |
241 | 238 |
|
242 | | -(defn- args->opts |
243 | | - ([args args->opt-keys] (args->opts args args->opt-keys #{})) |
244 | | - ([args args->opt-keys ignored-args] |
245 | | - (let [[new-args args->opts] |
246 | | - (if args->opt-keys |
247 | | - (if (and (seq args) |
248 | | - (not (contains? ignored-args (first args)))) |
249 | | - (let [arg-count (count args) |
250 | | - cnt (min arg-count |
251 | | - (bounded-count arg-count args->opt-keys))] |
252 | | - [(concat (interleave #?(:squint (map ->Injected args->opt-keys) |
253 | | - :default args->opt-keys) |
254 | | - args) |
255 | | - (drop cnt args)) |
256 | | - (drop cnt args->opt-keys)]) |
257 | | - [args args->opt-keys]) |
258 | | - [args args->opt-keys])] |
259 | | - {:args new-args |
260 | | - :args->opts args->opts}))) |
261 | | - |
262 | 239 | (defn- analyze-arg [arg mode open-opt boolean-opt? valued-opt known-keys alias-keys] |
263 | 240 | (let [fst-char (first-char arg) |
264 | 241 | snd-char (second-char arg) |
|
624 | 601 | ;; metadata (e.g., parsed option :foo might have been typed as :foo, --foo or -f), |
625 | 602 | ;; so error messages can echo what the user actually typed |
626 | 603 | stamp (fn [m k lit] (if lit (vary-meta m assoc-in [::opt->flag k] lit) m)) |
627 | | - ;; inject leading positional args (in CLIs these are typically commands) |
628 | | - ;; as options as per :args->opts |
| 604 | + ignored-args (::dispatch-tree-ignored-args parse-opts) |
| 605 | + a->o (or (:args->opts parse-opts) |
| 606 | + ;; :cmd-opts is the old name for :args->opts, left in for backward compat |
| 607 | + (:cmds-opts parse-opts)) |
| 608 | + ;; bind leading positional args (in CLIs these are typically commands) |
| 609 | + ;; to :args->opts keys, pairwise. An ignored first arg (a subcommand |
| 610 | + ;; name) leaves the whole leading group alone for dispatch to route. |
629 | 611 | {leading-pos-args :cmds args :args} (parse-cmds args) |
630 | | - {new-leading-pos-args :args a->o :args->opts} |
631 | | - (if-let [a->o (or (:args->opts parse-opts) |
632 | | - ;; :cmd-opts is the old name for :args->opts, left in for backward compat |
633 | | - (:cmds-opts parse-opts))] |
634 | | - (args->opts leading-pos-args a->o (::dispatch-tree-ignored-args parse-opts)) |
635 | | - {:args->opts nil :args args}) |
636 | | - [leading-pos-args args] (if (not= new-leading-pos-args args) |
637 | | - [nil (concat new-leading-pos-args args)] |
| 612 | + bind-leading? (and (seq a->o) (seq leading-pos-args) |
| 613 | + (not (contains? ignored-args (first leading-pos-args)))) |
| 614 | + [acc0 kpo0 leftover-leading a->o last-bound] |
| 615 | + (if bind-leading? |
| 616 | + (loop [acc {} kpo [] toks (seq leading-pos-args) ks a->o bound nil] |
| 617 | + (if (and toks (seq ks)) |
| 618 | + (let [k (first ks)] |
| 619 | + (recur (add-val-to-opt acc k (collect-fn collect coerce k) (first toks)) |
| 620 | + (track-kpo kpo k) (next toks) (rest ks) k)) |
| 621 | + [acc kpo toks ks bound])) |
| 622 | + [{} [] nil a->o nil]) |
| 623 | + ;; leftover leading args (keys exhausted) re-enter the loop and stop |
| 624 | + ;; option parsing at the trailing-args rule, like any unbound arg |
| 625 | + [leading-pos-args args] (if bind-leading? |
| 626 | + [nil (concat leftover-leading args)] |
638 | 627 | [leading-pos-args args]) |
639 | 628 | [parsed last-open-opt last-valued-opt implicit-values key-parse-order] |
640 | 629 | (if (and (::dispatch-tree parse-opts) (seq leading-pos-args)) |
641 | 630 | [(vary-meta {} assoc-in [:org.babashka/cli :args] (into (vec leading-pos-args) args)) nil nil #{} []] |
642 | | - (loop [acc {} |
| 631 | + (loop [acc acc0 |
643 | 632 | #_{:clj-kondo/ignore [:unused-binding]} |
644 | 633 | recur-action nil ;; for debugging only |
645 | | - open-opt nil ;; the cli option keyword we are working on |
646 | | - valued-opt nil ;; the cli option keyword that has been given value(s) (but not necessarily all values) |
| 634 | + open-opt last-bound ;; the cli option keyword we are working on |
| 635 | + valued-opt last-bound ;; the cli option keyword that has been given value(s) (but not necessarily all values) |
647 | 636 | mode (when no-keyword-opts :hyphens) ;; :hyphens --foo/-f else :keywords :foo |
648 | 637 | args (seq args) ;; remaining cli args |
649 | | - a->o a->o ;; requested args->opts |
| 638 | + a->o a->o ;; remaining args->opts keys |
650 | 639 | implicit-values {} |
651 | | - opt-parse-order []] |
| 640 | + opt-parse-order kpo0] |
652 | 641 | #_(println (format "loop %-31s o: %-10s v: %-10s a: %s" recur-action open-opt valued-opt args)) |
653 | 642 | (if-not args |
654 | 643 | ;; exit loop: no command line args left |
655 | 644 | [acc open-opt valued-opt implicit-values opt-parse-order] |
656 | 645 | (let [raw-arg (first args) |
657 | | - opt-injected? #?(:squint (instance? Injected raw-arg) |
658 | | - :default (keyword? raw-arg)) |
659 | | - #?@(:squint [raw-arg (if opt-injected? (.-opt raw-arg) raw-arg)])] |
660 | | - (if opt-injected? |
661 | | - ;; continue loop: this opt and its value was injected by args->opts |
662 | | - ;; opt-val-collector does not apply for injected opts, so is `nil`. |
663 | | - ;; valued-opt resets to nil: a freshly injected opt has no value yet |
664 | | - (recur (maybe-close-open-opt acc open-opt valued-opt nil) |
665 | | - :found-injected-opt |
666 | | - raw-arg nil |
667 | | - mode (next args) a->o |
668 | | - (track-ivs implicit-values open-opt valued-opt) |
669 | | - (track-kpo opt-parse-order raw-arg)) |
670 | | - (let [arg (str raw-arg) |
671 | | - opt-val-collector (collect-fn collect coerce open-opt) |
672 | | - boolean-opt? (expects-bool-val? open-opt) |
673 | | - {:keys [hyphen-opt composite-opt kwd-opt mode fst-colon]} |
674 | | - (analyze-arg arg mode open-opt boolean-opt? valued-opt known-keys alias-keys)] |
| 646 | + arg (str raw-arg) |
| 647 | + opt-val-collector (collect-fn collect coerce open-opt) |
| 648 | + boolean-opt? (expects-bool-val? open-opt) |
| 649 | + {:keys [hyphen-opt composite-opt kwd-opt mode fst-colon]} |
| 650 | + (analyze-arg arg mode open-opt boolean-opt? valued-opt known-keys alias-keys)] |
675 | 651 | (if (or hyphen-opt kwd-opt) |
676 | 652 | ;; arg is -f/-foo or :foo |
677 | 653 | (let [long-opt? (str/starts-with? arg "--") |
|
749 | 725 | repeated-opts ;; must specify --foo a --foo and not --foo a b |
750 | 726 | (contains? (::dispatch-tree-ignored-args parse-opts) (first args)))))] |
751 | 727 | (if done-parsing-options? |
752 | | - (let [{new-trailing-pos-args :args a->o :args->opts} |
753 | | - (if (and args a->o) |
754 | | - (args->opts args a->o (::dispatch-tree-ignored-args parse-opts)) |
755 | | - {:args args}) |
756 | | - new-args? (not= args new-trailing-pos-args)] |
757 | | - (if new-args? |
758 | | - ;; continue loop: with trailing args -> options |
759 | | - (recur acc |
760 | | - :injected-trailing-args-to-opts |
761 | | - open-opt valued-opt |
762 | | - mode new-trailing-pos-args a->o |
763 | | - implicit-values opt-parse-order) |
764 | | - ;; exit loop: args -> options resulted in no new args |
| 728 | + (let [k (when-not (contains? ignored-args arg) |
| 729 | + (first a->o))] |
| 730 | + (if k |
| 731 | + ;; continue loop: bind this arg to the next :args->opts key |
| 732 | + (recur (add-val-to-opt (maybe-close-open-opt acc open-opt valued-opt opt-val-collector) |
| 733 | + k (collect-fn collect coerce k) arg) |
| 734 | + :bound-positional |
| 735 | + k k |
| 736 | + mode (next args) (rest a->o) |
| 737 | + (track-ivs implicit-values open-opt valued-opt) |
| 738 | + (track-kpo opt-parse-order k)) |
| 739 | + ;; exit loop: no keys left (or a subcommand name): the rest are args |
765 | 740 | [(vary-meta acc assoc-in [:org.babashka/cli :args] (vec args)) open-opt valued-opt implicit-values opt-parse-order])) |
766 | 741 | (let [opt (when-not (and (= :keywords mode) fst-colon) open-opt)] |
767 | 742 | ;; continue loop: add/update opt with value (arg), and setup to parse next arg |
|
772 | 747 | opt opt ;; keep opt open |
773 | 748 | mode (next args) a->o |
774 | 749 | (cond-> implicit-values (boolean? raw-arg) (assoc open-opt raw-arg)) |
775 | | - opt-parse-order))))))))))) |
| 750 | + opt-parse-order))))))))) |
776 | 751 | ;; Finalize: process last opt, prepend leading positional args to args metadata |
777 | 752 | implicit-values (track-ivs implicit-values last-open-opt last-valued-opt) |
778 | 753 | opt-val-collector (collect-fn collect coerce last-open-opt) |
|
0 commit comments