Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 1.0.3 -- 3 Sep 2026

* fix: Nested group completions used multi-word names that zsh could not parse; also `#compdef` and `--version` [#72](https://github.com/hlship/cli-tools/issues/72)

# 1.0.2 -- 3 Sep 2026

* fix: Completions generation fails when tool options include extra tools.cli keys [#70](https://github.com/hlship/cli-tools/issues/70)
Expand Down
2 changes: 1 addition & 1 deletion resources/net/lewisship/cli_tools/top-level.tpl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#compdef _{{tool}} {{tool}}
#compdef {{tool}}

_{{tool}}() {
local line state
Expand Down
9 changes: 5 additions & 4 deletions src/net/lewisship/cli_tools.clj
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@
(merge {:tool-name tool-name'
:cache-digest digest
:command-root command-root}
(select-keys options [:doc :arguments :tool-summary :pre-dispatch :pre-invoke :extra-tool-options]))))
(select-keys options [:doc :arguments :tool-summary :pre-dispatch :pre-invoke
:extra-tool-options :version]))))

(defn- dispatch*
"Called (indirectly/anonymously) from a tool handler to process remaining command line arguments."
Expand Down Expand Up @@ -297,9 +298,9 @@
(default-dispatch-options)
dispatch-options)
{:keys [extra-tool-options tool-options-handler version]} merged-options
version-option (when version
[["-V" "--version" "Display version"]])
full-options (concat extra-tool-options version-option impl/default-tool-options)
full-options (concat extra-tool-options
(when version [impl/version-tool-option])
impl/default-tool-options)
{:keys [options arguments summary errors]}
(cli/parse-opts (:arguments merged-options)
full-options
Expand Down
27 changes: 12 additions & 15 deletions src/net/lewisship/cli_tools/completions.clj
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,14 @@
title (-> (impl/extract-command-title command-map)
ansi/compose
string/trim)
fn-name (simplify fn-prefix command-name)]
fn-name (simplify fn-prefix command-name)
base {:name command-name
:fn-name fn-name
:title title}]
;; TODO: Support messy group/command combos
(if fn
{:name command-name
:fn-name fn-name
:title title
:options (options command-map)}
{:name (->> command-map
:command-path
(string/join " "))
:title title
:fn-name fn-name
:subs (map #(extract-command fn-name %) (:subs command-map))})))
(assoc base :options (options command-map))
(assoc base :subs (map #(extract-command fn-name %) (:subs command-map))))))

(defn- render-commands
[tool-name commands]
Expand All @@ -85,9 +80,11 @@
:command command}))))

(defn- print-tool
[tool-name command-root extra-options]
[tool-name command-root extra-options version]
(let [prefix (str "_" tool-name)
options (map to-opt (concat extra-options impl/default-tool-options))
options (map to-opt (concat extra-options
(when version [impl/version-tool-option])
impl/default-tool-options))
commands (->> command-root
(keep #(extract-command prefix %)))]
(selmer.util/without-escaping
Expand All @@ -103,9 +100,9 @@
output-path ["PATH" "File to write completions to."
:optional true]]
(binding [impl/*introspection-mode* true]
(let [{:keys [command-root tool-name extra-tool-options]} impl/*tool-options*
(let [{:keys [command-root tool-name extra-tool-options version]} impl/*tool-options*
generator #(binding [ansi/*color-enabled* false]
(print-tool tool-name command-root extra-tool-options))]
(print-tool tool-name command-root extra-tool-options version))]
(if output-path
(do
(with-open [w (-> output-path
Expand Down
4 changes: 4 additions & 0 deletions src/net/lewisship/cli_tools/impl.clj
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,10 @@
(cond->> root
transformer (transformer dispatch-options))))

(def version-tool-option
"Tool option added when dispatch is given a :version."
["-V" "--version" "Display version"])

(def default-tool-options
"Default tool command line options."
[["-C" "--color" "Enable ANSI color output"]
Expand Down
2 changes: 1 addition & 1 deletion test-resources/expected/messy-completions.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#compdef _messy messy
#compdef messy

_messy() {
local line state
Expand Down
106 changes: 106 additions & 0 deletions test-resources/expected/nested-group-completions.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#compdef nested

_nested() {
local line state

_arguments -C \
'(-C --color)'{-C,--color}$'[Enable ANSI color output]' \
'(-N --no-color)'{-N,--no-color}$'[Disable ANSI color output]' \
'(-h --help)'{-h,--help}$'[This command summary]' \
"1: :->cmds" \
"*::args:->args"

case "$state" in
cmds)
_values "nested command" \
"completions[Generate zsh command completions]" \
"help[List available commands]" \
"group[Grouped commands]"
;;
args)
case $line[1] in
completions) _nested_completions ;;

help) _nested_help ;;

group) _nested_group ;;

esac
;;
esac
}
_nested_completions() {
_arguments -s \
'(-h --help)'{-h,--help}$'[This command summary]'
}

_nested_help() {
_arguments -s \
'(-c --commands)'{-c,--commands}$'[Print commands: all, none, root]':FILTER \
'(-h --help)'{-h,--help}$'[This command summary]'
}

_nested_group() {
local state line

_arguments -C \
"1: :->cmds" \
"*::arg:->args"

case "$state" in
cmds)
_values "nested group subcommands" \
"edit[Edit a whatever]" \
"echo[Echo a string]" \
"nested[Nested commands inside group]"
;;
args)
case $line[1] in
edit) _nested_group_edit ;;
echo) _nested_group_echo ;;
nested) _nested_group_nested ;;
esac
;;
esac
}
_nested_group_edit() {
_arguments -s \
'(-h --help)'{-h,--help}$'[This command summary]'
}

_nested_group_echo() {
_arguments -s \
'(-h --help)'{-h,--help}$'[This command summary]'
}

_nested_group_nested() {
local state line

_arguments -C \
"1: :->cmds" \
"*::arg:->args"

case "$state" in
cmds)
_values "nested nested subcommands" \
"butterfly[Nested command butterfly]" \
"leaf[Nested command leaf]"
;;
args)
case $line[1] in
butterfly) _nested_group_nested_butterfly ;;
leaf) _nested_group_nested_leaf ;;
esac
;;
esac
}
_nested_group_nested_butterfly() {
_arguments -s \
'(-h --help)'{-h,--help}$'[This command summary]'
}

_nested_group_nested_leaf() {
_arguments -s \
'(-h --help)'{-h,--help}$'[This command summary]'
}

2 changes: 1 addition & 1 deletion test-resources/expected/simple-completions.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#compdef _simple simple
#compdef simple

_simple() {
local line state
Expand Down
2 changes: 1 addition & 1 deletion test-resources/expected/subgroup-completions.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#compdef _subgroup subgroup
#compdef subgroup

_subgroup() {
local line state
Expand Down
2 changes: 1 addition & 1 deletion test-resources/expected/tool-options.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#compdef _options options
#compdef options

_options() {
local line state
Expand Down
39 changes: 39 additions & 0 deletions test-resources/expected/version-option.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#compdef versioned

_versioned() {
local line state

_arguments -C \
'(-V --version)'{-V,--version}$'[Display version]' \
'(-C --color)'{-C,--color}$'[Enable ANSI color output]' \
'(-N --no-color)'{-N,--no-color}$'[Disable ANSI color output]' \
'(-h --help)'{-h,--help}$'[This command summary]' \
"1: :->cmds" \
"*::args:->args"

case "$state" in
cmds)
_values "versioned command" \
"completions[Generate zsh command completions]" \
"help[List available commands]"
;;
args)
case $line[1] in
completions) _versioned_completions ;;

help) _versioned_help ;;

esac
;;
esac
}
_versioned_completions() {
_arguments -s \
'(-h --help)'{-h,--help}$'[This command summary]'
}

_versioned_help() {
_arguments -s \
'(-c --commands)'{-c,--commands}$'[Print commands: all, none, root]':FILTER \
'(-h --help)'{-h,--help}$'[This command summary]'
}
18 changes: 18 additions & 0 deletions test/net/lewisship/cli_tools/completions_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@
:groups
{"subgroup" {:namespaces [net.lewisship.cli-tools.completion-group]}}}))))

(deftest nested-group-completion
(is (match? (expected "nested-group-completions.txt")
(dispatch
'{:tool-name "nested"
:namespaces [net.lewisship.cli-tools.completions]
:groups
{"group" {:namespaces [net.lewisship.group-ns]
:doc "Grouped commands"
:groups {"nested" {:namespaces [net.lewisship.cli-tools.group-nested]
:doc "Nested commands inside group"}}}}}))))

(deftest messy-completions
;; where command name and group name collide
;; Not sure the current behavior is correct
Expand All @@ -64,3 +75,10 @@
:default "-"
:parse-fn identity
:validate [some? "Must be provided"]]]}))))

(deftest version-option
(is (match? (expected "version-option.txt")
(dispatch
{:tool-name "versioned"
:namespaces '[net.lewisship.cli-tools.completions]
:version "1.2.3"}))))