|
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