_juju
7961 bytes
1#compdef juju
2(( $+functions[compdef] )) && compdef _juju juju
3
4# zsh completion for juju -*- shell-script -*-
5
6__juju_debug()
7{
8 local file="$BASH_COMP_DEBUG_FILE"
9 if [[ -n ${file} ]]; then
10 echo "$*" >> "${file}"
11 fi
12}
13
14__juju_help_options()
15{
16 local out line token cleaned desc f
17 local -a opts pending
18 typeset -U opts
19
20 __juju_debug "[options] called with args: $*"
21 out=$(command juju help "$@" 2>/dev/null)
22 local rc=$?
23 __juju_debug "[options] juju help exit code: $rc, output length: ${#out}"
24 (( rc )) && return 1
25
26 while IFS= read -r line; do
27 if [[ "$line" =~ '^[[:space:]]{0,3}-' ]]; then
28 for f in "${pending[@]}"; do opts+=("$f"); done
29 pending=()
30 for token in ${(z)line}; do
31 cleaned="${token%%,*}"
32 cleaned="${cleaned%%;*}"
33 cleaned="${cleaned%%]*}"
34 cleaned="${cleaned%%)*}"
35 cleaned="${cleaned%%=<*}"
36 cleaned="${cleaned%%=*}"
37 cleaned="${cleaned%%<*}"
38 cleaned="${cleaned%%\[*}"
39 cleaned="${cleaned%%\(*}"
40 [[ "$cleaned" == --* || "$cleaned" == -[[:alnum:]] ]] || continue
41 [[ "$cleaned" == "-" || "$cleaned" == "--" ]] && continue
42 __juju_debug "[options] found flag: $cleaned"
43 pending+=("$cleaned")
44 done
45 elif (( ${#pending} )) && [[ -n "$line" ]]; then
46 desc="${line#"${line%%[![:space:]]*}"}"
47 desc="${desc//:/\\:}"
48 __juju_debug "[options] desc for ${pending[*]}: $desc"
49 for f in "${pending[@]}"; do opts+=("${f}:${desc}"); done
50 pending=()
51 elif [[ -z "$line" ]]; then
52 for f in "${pending[@]}"; do opts+=("$f"); done
53 pending=()
54 fi
55 done < <(printf "%s\n" "$out")
56
57 for f in "${pending[@]}"; do opts+=("$f"); done
58 __juju_debug "[options] total opts: ${#opts}, first few: ${opts[1]} ${opts[2]} ${opts[3]}"
59
60 printf "%s\n" "${opts[@]}"
61}
62
63
64__juju_help_commands()
65{
66 local line cmd desc out
67 out=$(command juju help commands 2>/dev/null) || return 1
68
69 while IFS= read -r line; do
70 # Strip leading whitespace
71 line="${line#"${line%%[![:space:]]*}"}"
72 # Only process lines starting with an alphanumeric (command names)
73 [[ "$line" =~ '^[[:alnum:]]' ]] || continue
74 # Split on the first run of 2+ spaces: left = cmd, right = description
75 cmd="${line%% *}"
76 # Validate it's a clean command token (no spaces, only alnum and dash)
77 [[ "$cmd" =~ '^[[:alnum:]][[:alnum:]-]*$' ]] || continue
78 desc="${line#"$cmd"}"
79 desc="${desc#"${desc%%[![:space:]]*}"}"
80 if [[ -n "$desc" ]]; then
81 printf "%s:%s\n" "$cmd" "$desc"
82 else
83 printf "%s\n" "$cmd"
84 fi
85 done <<< "$out"
86}
87
88__juju_models()
89{
90 # Optional argument: controller name. If given, fetch models for that controller.
91 if [[ -n "$1" ]]; then
92 command juju models -c "$1" --format=json 2>/dev/null \
93 | command jq -r '.models[]."short-name"' 2>/dev/null
94 else
95 command juju models --format=json 2>/dev/null \
96 | command jq -r '.models[]."short-name"' 2>/dev/null
97 fi
98}
99
100# Complete a model token that may be prefixed with "controller:" — if a colon is
101# present, fetch models for that controller and offer "ctrl:model" completions.
102__juju_complete_model()
103{
104 local current="$1"
105 local -a completions
106
107 __juju_debug "[complete_model] current='${current}'"
108
109 if [[ "$current" == *:* ]]; then
110 local ctrl="${current%%:*}"
111 local models
112 models=("${(@f)$(__juju_models "$ctrl")}")
113 completions=("${models[@]/#/${ctrl}:}")
114 __juju_debug "[complete_model] ctrl=${ctrl} completions=${#completions}: ${completions[*]}"
115 compadd -S '' -q -- "${completions[@]}"
116 else
117 local -a models ctrls
118 models=("${(@f)$(__juju_models)}")
119 ctrls=("${(@f)$(__juju_controllers)}")
120 __juju_debug "[complete_model] models=${#models}: ${models[*]}"
121 __juju_debug "[complete_model] ctrls=${#ctrls}: ${ctrls[*]}"
122 __juju_debug "[complete_model] calling _alternative"
123 _alternative \
124 'models:models:{__juju_debug "[complete_model] compadd models"; compadd "$expl[@]" -a models}' \
125 'controllers:controllers:{__juju_debug "[complete_model] compadd ctrls"; compadd "$expl[@]" -S : -q -a ctrls}'
126 __juju_debug "[complete_model] _alternative returned $?"
127 fi
128}
129
130# Commands whose first positional argument is a model name.
131_juju_model_commands=(
132 destroy-model
133 grant-model
134 revoke-model
135 switch
136)
137
138# Flags that take a model name as their value.
139_juju_model_flags=(
140 -m
141 --model
142)
143
144__juju_controllers()
145{
146 command juju controllers --format=json 2>/dev/null \
147 | command jq -r '.controllers | keys | .[]' 2>/dev/null
148}
149
150# Commands whose first positional argument is a controller name.
151_juju_controller_commands=(
152 destroy-controller
153 kill-controller
154 login
155 logout
156 unregister
157)
158
159# Flags that take a controller name as their value.
160_juju_controller_flags=(
161 -c
162 --controller
163)
164
165_juju()
166{
167 __juju_debug "[_juju] curcontext: ${curcontext}"
168 local -a completions
169
170 __juju_debug "[_juju] words: ${words[*]}, CURRENT: $CURRENT"
171
172 # Find the subcommand: first non-flag word typed after "juju", excluding the
173 # word currently being completed (words[CURRENT]).
174 local subcmd=""
175 local i
176 for (( i = 2; i < CURRENT; i++ )); do
177 if [[ "${words[i]}" != -* ]]; then
178 subcmd="${words[i]}"
179 break
180 fi
181 done
182
183 local current="${words[CURRENT]}"
184 local prev="${words[CURRENT-1]}"
185
186 __juju_debug "[_juju] subcmd: '${subcmd}', current: '${current}', prev: '${prev}'"
187
188 # Controller name completion: flag value (e.g. juju status -c <TAB>)
189 if (( ${_juju_controller_flags[(I)$prev]} )); then
190 completions=("${(@f)$(__juju_controllers)}")
191 __juju_debug "[_juju] controller flag completions: ${#completions}"
192 (( ${#completions} )) && _describe "controller" completions && return 0
193 return 1
194 fi
195
196 # Model name completion: flag value (e.g. juju status -m <TAB> or -m ctrl:<TAB>)
197 if (( ${_juju_model_flags[(I)$prev]} )); then
198 __juju_debug "[_juju] model flag completion, current: '${current}'"
199 __juju_complete_model "$current" && return 0
200 return 1
201 fi
202
203 if [[ -z "$subcmd" ]]; then
204 # No subcommand yet — complete subcommand names.
205 completions=("${(@f)$(__juju_help_commands)}")
206 __juju_debug "[_juju] command completions count: ${#completions}"
207 (( ${#completions} )) && _describe "command" completions && return 0
208 return 1
209 fi
210
211 # Controller name completion: positional arg (e.g. juju destroy-controller <TAB>)
212 if (( ${_juju_controller_commands[(I)$subcmd]} )) && [[ "$current" != -* ]]; then
213 completions=("${(@f)$(__juju_controllers)}")
214 __juju_debug "[_juju] controller command completions: ${#completions}"
215 (( ${#completions} )) && _describe "controller" completions && return 0
216 return 1
217 fi
218
219 # Model name completion: positional arg (e.g. juju destroy-model <TAB> or ctrl:<TAB>)
220 if (( ${_juju_model_commands[(I)$subcmd]} )) && [[ "$current" != -* ]]; then
221 __juju_debug "[_juju] model command completion, current: '${current}'"
222 __juju_complete_model "$current" && return 0
223 return 1
224 fi
225
226 # Flag completion for all other subcommands (also shown without leading dash)
227 completions=("${(@f)$(__juju_help_options "$subcmd")}")
228 __juju_debug "[_juju] option completions count: ${#completions}"
229 (( ${#completions} )) && _describe "option" completions && return 0
230 return 1
231}