|
686 | 686 |
|
687 | 687 | (defn- command-match?
|
688 | 688 | [command search-term]
|
689 |
| - (let [{:keys [doc title command-path]} command] |
| 689 | + (let [{:keys [doc group-doc title command-path]} command] |
690 | 690 | (or (string-matches? doc search-term)
|
| 691 | + (string-matches? group-doc search-term) |
691 | 692 | (string-matches? title search-term)
|
692 | 693 | (some #(string-matches? % search-term) command-path))))
|
693 | 694 |
|
|
709 | 710 | [command-map]
|
710 | 711 | (or (:title command-map)
|
711 | 712 | (-> command-map :doc first-sentence)
|
712 |
| - (when-not (:fn command-map) |
| 713 | + (-> command-map :group-doc first-sentence) |
| 714 | + (when (-> command-map :subs seq) |
713 | 715 | (let [{command-count true
|
714 | 716 | group-count false} (->> command-map
|
715 | 717 | :subs
|
716 | 718 | vals
|
| 719 | + ;; Not quite accurate if messy (command and group at same time) |
717 | 720 | (map #(-> % :fn some?))
|
718 | 721 | frequencies)]
|
719 | 722 | [:faint
|
|
743 | 746 | (pout (when recurse? "\n")
|
744 | 747 | [:bold (string/join " " (:command-path container-map))]
|
745 | 748 | " - "
|
746 |
| - (or (some-> container-map :doc cleanup-docstring) |
| 749 | + (or (some-> container-map :group-doc cleanup-docstring) |
747 | 750 | missing-doc)))
|
748 | 751 |
|
749 | 752 | (when (seq sorted-commands)
|
|
761 | 764 | ;; Recurse and print sub-groups
|
762 | 765 | (when recurse?
|
763 | 766 | (->> sorted-commands
|
764 |
| - (remove :fn) ; Remove commands, leave groups |
| 767 | + (filter #(-> % :subs seq)) ; Remove commands, leave groups and messy command/groups |
765 | 768 | (run! #(print-commands command-name-width' % (:subs %) true))))))
|
766 | 769 |
|
767 | 770 | (defn- command-path-width
|
|
840 | 843 | [tool-name]
|
841 | 844 | (list ", use " [:bold.green tool-name " help"] " to list commands"))
|
842 | 845 |
|
| 846 | +(defn- no-command |
| 847 | + [tool-name] |
| 848 | + (abort [:bold.green tool-name] ": no command provided" (use-help-message tool-name))) |
| 849 | + |
| 850 | +(defn- incomplete |
| 851 | + [tool-name command-path matchable-terms] |
| 852 | + (abort |
| 853 | + [:bold.green tool-name ": " |
| 854 | + (string/join " " (butlast command-path)) |
| 855 | + [:red (last command-path)]] |
| 856 | + " is incomplete; " |
| 857 | + (compose-list matchable-terms) |
| 858 | + " could follow; use " |
| 859 | + [:bold [:green tool-name " " (string/join " " command-path) " --help (or -h)"]] |
| 860 | + " to list commands")) |
| 861 | + |
| 862 | +(defn- no-match |
| 863 | + [tool-name command-path term matched-terms matchable-terms] |
| 864 | + (let [body (if (-> matched-terms count pos?) |
| 865 | + (list "could match " |
| 866 | + (compose-list matched-terms {:conjuction "or"})) |
| 867 | + (list "is not a command, expected " |
| 868 | + (compose-list matchable-terms {:conjuction "or"}))) |
| 869 | + help-suffix (list |
| 870 | + "; use " |
| 871 | + [:bold [:green tool-name " " |
| 872 | + (if (seq command-path) |
| 873 | + (string/join " " command-path) |
| 874 | + "help")]] |
| 875 | + (when (seq command-path) |
| 876 | + " --help (or -h)") |
| 877 | + " to list commands")] |
| 878 | + (abort |
| 879 | + [:bold [:green tool-name] ": " |
| 880 | + [:green (string/join " " command-path)] |
| 881 | + (when (seq command-path) " ") |
| 882 | + [:red term]] |
| 883 | + " " |
| 884 | + body |
| 885 | + help-suffix))) |
| 886 | + |
843 | 887 | (defn dispatch
|
844 | 888 | [{:keys [command-root arguments tool-name] :as options}]
|
845 | 889 | (binding [*tool-options* options]
|
846 | 890 | (let [command-name (first arguments)]
|
847 | 891 | (if (or (nil? command-name)
|
848 | 892 | (string/starts-with? command-name "-"))
|
849 |
| - (abort [:bold.green tool-name] ": no command provided" (use-help-message tool-name)) |
850 |
| - (loop [prefix [] ; Needed? |
851 |
| - term command-name |
852 |
| - remaining-args (next arguments) |
853 |
| - container-map nil |
854 |
| - commands-map command-root] |
| 893 | + (no-command tool-name) |
| 894 | + (loop [command-path [] |
| 895 | + term command-name |
| 896 | + remaining-args (next arguments) |
| 897 | + container-map nil |
| 898 | + commands-map command-root |
| 899 | + invoke-last-command-fn nil] |
855 | 900 | (cond-let
|
856 | 901 | (#{"-h" "--help"} term)
|
857 | 902 | (do
|
|
864 | 909 | ;; Options start with a '-', but we're still looking for commands
|
865 | 910 | (or (nil? term)
|
866 | 911 | (string/starts-with? term "-"))
|
867 |
| - (abort |
868 |
| - [:bold.green tool-name ": " |
869 |
| - (string/join " " (butlast prefix)) |
870 |
| - [:red (last prefix)]] |
871 |
| - " is incomplete; " |
872 |
| - (compose-list matchable-terms) |
873 |
| - " could follow; use " |
874 |
| - [:bold [:green tool-name " " (string/join " " prefix) " --help (or -h)"]] |
875 |
| - " to list commands") |
| 912 | + ;; In messy mode, this lets us backtrack to the containing command (which is also a group, that's |
| 913 | + ;; why we call it messy) and invoke it as a command now that know the next term doesn't indentify |
| 914 | + ;; another command or group. |
| 915 | + (if invoke-last-command-fn |
| 916 | + (invoke-last-command-fn) |
| 917 | + (incomplete tool-name command-path matchable-terms)) |
876 | 918 |
|
877 | 919 | :let [matched-terms (find-matches term matchable-terms)
|
878 | 920 | match-count (count matched-terms)]
|
879 | 921 |
|
880 | 922 | (not= 1 match-count)
|
881 |
| - (let [body (if (pos? match-count) |
882 |
| - (list "could match " |
883 |
| - (compose-list matched-terms {:conjuction "or"})) |
884 |
| - (list "is not a command, expected " |
885 |
| - (compose-list matchable-terms {:conjuction "or"}))) |
886 |
| - help-suffix (list |
887 |
| - "; use " |
888 |
| - [:bold [:green tool-name " " |
889 |
| - (if (seq prefix) |
890 |
| - (string/join " " prefix) |
891 |
| - "help")]] |
892 |
| - (when (seq prefix) |
893 |
| - " --help (or -h)") |
894 |
| - " to list commands")] |
895 |
| - (abort |
896 |
| - [:bold [:green tool-name] ": " |
897 |
| - [:green (string/join " " prefix)] |
898 |
| - (when (seq prefix) " ") |
899 |
| - [:red term]] |
900 |
| - " " |
901 |
| - body |
902 |
| - help-suffix)) |
| 923 | + ;; Likewise, if the next term doesn't look like an option, but doesn't match a nested command or group |
| 924 | + ;; then it must be a positional argument to the container command (that's also a messy group). |
| 925 | + (if invoke-last-command-fn |
| 926 | + (invoke-last-command-fn) |
| 927 | + (no-match tool-name command-path term matched-terms matchable-terms)) |
903 | 928 |
|
904 | 929 | ;; Exactly one match
|
905 | 930 | :let [matched-term (first matched-terms)
|
906 | 931 | matched-command (get possible-commands matched-term)]
|
907 | 932 |
|
908 | 933 | (:fn matched-command)
|
909 |
| - (binding [*command-map* matched-command] |
910 |
| - (apply (-> matched-command :fn requiring-resolve) remaining-args)) |
| 934 | + (let [invoke-command #(binding [*command-map* matched-command] |
| 935 | + (apply (-> matched-command :fn requiring-resolve) remaining-args))] |
| 936 | + ;; It's a command, but has :subs, so it's also a group (this is the "messy" scenario) |
| 937 | + (if (-> matched-command :subs seq) |
| 938 | + (recur (:command-path matched-command) |
| 939 | + (first remaining-args) |
| 940 | + (rest remaining-args) |
| 941 | + matched-command |
| 942 | + (:subs matched-command) |
| 943 | + invoke-command) |
| 944 | + (invoke-command))) |
911 | 945 |
|
912 | 946 | ;; Otherwise, it was a command group.
|
913 | 947 | :else
|
914 | 948 | (recur (:command-path matched-command)
|
915 | 949 | (first remaining-args)
|
916 | 950 | (rest remaining-args)
|
917 | 951 | matched-command
|
918 |
| - (:subs matched-command))))))) |
| 952 | + (:subs matched-command) |
| 953 | + invoke-last-command-fn)))))) |
919 | 954 | nil)
|
920 | 955 |
|
921 | 956 | (defn command-map?
|
|
951 | 986 | {:fn (symbol command-var)
|
952 | 987 | ;; Commands have a full :doc and an optional short :title
|
953 | 988 | ;; (the title defaults to the first sentence of the :doc
|
954 |
| - ;; if not provided |
| 989 | + ;; if not provided) |
955 | 990 | :doc doc
|
956 | 991 | :command command-name
|
957 | 992 | :command-path (conj path command-name)}
|
|
971 | 1006 | ;; Mix in nested groups to form the subs for this group
|
972 | 1007 | subs (reduce-kv
|
973 | 1008 | (fn [commands group-command group-descriptor]
|
974 |
| - (assoc commands group-command |
| 1009 | + (update commands group-command merge |
975 | 1010 | (build-command-group group-descriptor path' group-command)))
|
976 | 1011 | direct-commands
|
977 | 1012 | groups)
|
978 | 1013 | doc' (or doc
|
979 | 1014 | (some #(-> % find-ns meta :doc) namespaces))]
|
980 |
| - {:doc doc' ; groups have just :doc, no :title |
981 |
| - :command command |
982 |
| - :command-path path' |
983 |
| - :subs subs})) |
| 1015 | + (cond-> { |
| 1016 | + :command command |
| 1017 | + :command-path path' |
| 1018 | + :subs subs} |
| 1019 | + ; groups have just :group-doc, no :title |
| 1020 | + doc' (assoc :group-doc doc')))) |
984 | 1021 |
|
985 | 1022 | (defn expand-tool-options
|
986 | 1023 | [dispatch-options]
|
|
0 commit comments