politty 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{arg-registry-C3GP-5C9.d.ts → arg-registry-BNoIwnNr.d.cts} +15 -8
- package/dist/arg-registry-BNoIwnNr.d.cts.map +1 -0
- package/dist/{arg-registry-B4a4Fj7f.d.cts → arg-registry-BUUhZ7JR.d.ts} +15 -8
- package/dist/arg-registry-BUUhZ7JR.d.ts.map +1 -0
- package/dist/augment.d.cts +1 -1
- package/dist/augment.d.ts +1 -1
- package/dist/completion/index.cjs +20 -20
- package/dist/completion/index.cjs.map +1 -1
- package/dist/completion/index.d.cts +3 -3
- package/dist/completion/index.d.ts +3 -3
- package/dist/completion/index.js +2 -2
- package/dist/docs/index.cjs +6 -6
- package/dist/docs/index.cjs.map +1 -1
- package/dist/docs/index.d.cts +3 -3
- package/dist/docs/index.d.cts.map +1 -1
- package/dist/docs/index.d.ts +3 -3
- package/dist/docs/index.d.ts.map +1 -1
- package/dist/docs/index.js +3 -3
- package/dist/index.cjs +10 -8
- package/dist/index.d.cts +4 -4
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/{schema-extractor-Mk1MHBkQ.cjs → lazy-BEDnSR0m.cjs} +77 -2
- package/dist/lazy-BEDnSR0m.cjs.map +1 -0
- package/dist/{schema-extractor-DAkmmrOy.js → lazy-BrEg8SgI.js} +59 -2
- package/dist/lazy-BrEg8SgI.js.map +1 -0
- package/dist/{runner-0yr2HFay.cjs → runner-C4fSHJMe.cjs} +11 -10
- package/dist/runner-C4fSHJMe.cjs.map +1 -0
- package/dist/{runner-BoZpJtIR.js → runner-D6k4BgB4.js} +6 -5
- package/dist/runner-D6k4BgB4.js.map +1 -0
- package/dist/{schema-extractor-DyfK21m_.d.cts → schema-extractor-DFaAZzaY.d.cts} +49 -4
- package/dist/schema-extractor-DFaAZzaY.d.cts.map +1 -0
- package/dist/{schema-extractor-CHiBRT39.d.ts → schema-extractor-n9288WJ6.d.ts} +49 -4
- package/dist/schema-extractor-n9288WJ6.d.ts.map +1 -0
- package/dist/{subcommand-router-DtCeT_O9.js → subcommand-router-CAzBsLSI.js} +4 -1
- package/dist/{subcommand-router-DtCeT_O9.js.map → subcommand-router-CAzBsLSI.js.map} +1 -1
- package/dist/{subcommand-router-4d1Xbp8B.cjs → subcommand-router-ZjNjFaUL.cjs} +3 -1
- package/dist/subcommand-router-ZjNjFaUL.cjs.map +1 -0
- package/dist/{value-completion-resolver-0xf8_07p.d.cts → value-completion-resolver-BQgHsX7b.d.cts} +20 -12
- package/dist/value-completion-resolver-BQgHsX7b.d.cts.map +1 -0
- package/dist/{value-completion-resolver-CUKbibx-.d.ts → value-completion-resolver-C9LTGr0O.d.ts} +20 -12
- package/dist/value-completion-resolver-C9LTGr0O.d.ts.map +1 -0
- package/dist/zsh-CASZWn0o.cjs +1586 -0
- package/dist/zsh-CASZWn0o.cjs.map +1 -0
- package/dist/zsh-hjvdI8uZ.js +1508 -0
- package/dist/zsh-hjvdI8uZ.js.map +1 -0
- package/package.json +4 -4
- package/dist/arg-registry-B4a4Fj7f.d.cts.map +0 -1
- package/dist/arg-registry-C3GP-5C9.d.ts.map +0 -1
- package/dist/extractor-DO-FDKkW.js +0 -1000
- package/dist/extractor-DO-FDKkW.js.map +0 -1
- package/dist/extractor-cjruDqQ2.cjs +0 -1078
- package/dist/extractor-cjruDqQ2.cjs.map +0 -1
- package/dist/runner-0yr2HFay.cjs.map +0 -1
- package/dist/runner-BoZpJtIR.js.map +0 -1
- package/dist/schema-extractor-CHiBRT39.d.ts.map +0 -1
- package/dist/schema-extractor-DAkmmrOy.js.map +0 -1
- package/dist/schema-extractor-DyfK21m_.d.cts.map +0 -1
- package/dist/schema-extractor-Mk1MHBkQ.cjs.map +0 -1
- package/dist/subcommand-router-4d1Xbp8B.cjs.map +0 -1
- package/dist/value-completion-resolver-0xf8_07p.d.cts.map +0 -1
- package/dist/value-completion-resolver-CUKbibx-.d.ts.map +0 -1
|
@@ -1,1078 +0,0 @@
|
|
|
1
|
-
const require_subcommand_router = require('./subcommand-router-4d1Xbp8B.cjs');
|
|
2
|
-
const require_schema_extractor = require('./schema-extractor-Mk1MHBkQ.cjs');
|
|
3
|
-
let zod = require("zod");
|
|
4
|
-
let node_child_process = require("node:child_process");
|
|
5
|
-
|
|
6
|
-
//#region src/core/command.ts
|
|
7
|
-
function defineCommand(config) {
|
|
8
|
-
return {
|
|
9
|
-
name: config.name,
|
|
10
|
-
description: config.description,
|
|
11
|
-
args: config.args,
|
|
12
|
-
subCommands: config.subCommands,
|
|
13
|
-
setup: config.setup,
|
|
14
|
-
run: config.run,
|
|
15
|
-
cleanup: config.cleanup,
|
|
16
|
-
notes: config.notes,
|
|
17
|
-
examples: config.examples
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
//#endregion
|
|
22
|
-
//#region src/completion/bash.ts
|
|
23
|
-
/**
|
|
24
|
-
* Generate bash completion script for a command
|
|
25
|
-
*
|
|
26
|
-
* Generates a minimal script that delegates all logic to the CLI's __complete command.
|
|
27
|
-
* The shell script only handles:
|
|
28
|
-
* - Getting current command line tokens
|
|
29
|
-
* - Calling __complete with --shell bash
|
|
30
|
-
* - Setting COMPREPLY from the output
|
|
31
|
-
* - Falling back to native file/directory completion when directed
|
|
32
|
-
*/
|
|
33
|
-
function generateBashCompletion(_command, options) {
|
|
34
|
-
const programName = options.programName;
|
|
35
|
-
return {
|
|
36
|
-
script: `# Bash completion for ${programName}
|
|
37
|
-
# Generated by politty
|
|
38
|
-
|
|
39
|
-
_${programName}_completions() {
|
|
40
|
-
COMPREPLY=()
|
|
41
|
-
local IFS=$'\\n'
|
|
42
|
-
|
|
43
|
-
# Rejoin words split by '=' in COMP_WORDBREAKS (e.g. --opt=value)
|
|
44
|
-
local -a _words=()
|
|
45
|
-
local _i=1
|
|
46
|
-
while (( _i <= COMP_CWORD )); do
|
|
47
|
-
if [[ "\${COMP_WORDS[_i]}" == "=" && \${#_words[@]} -gt 0 ]]; then
|
|
48
|
-
_words[\${#_words[@]}-1]+="=\${COMP_WORDS[_i+1]:-}"
|
|
49
|
-
(( _i += 2 ))
|
|
50
|
-
else
|
|
51
|
-
_words+=("\${COMP_WORDS[_i]}")
|
|
52
|
-
(( _i++ ))
|
|
53
|
-
fi
|
|
54
|
-
done
|
|
55
|
-
|
|
56
|
-
local lines prev_opts="$-"
|
|
57
|
-
set -f
|
|
58
|
-
lines=($(${programName} __complete --shell bash -- "\${_words[@]}" 2>/dev/null))
|
|
59
|
-
[[ "$prev_opts" != *f* ]] && set +f
|
|
60
|
-
|
|
61
|
-
local count=\${#lines[@]}
|
|
62
|
-
if (( count == 0 )); then
|
|
63
|
-
return 0
|
|
64
|
-
fi
|
|
65
|
-
|
|
66
|
-
local last="\${lines[count-1]}"
|
|
67
|
-
local directive=0
|
|
68
|
-
if [[ "$last" == :* ]]; then
|
|
69
|
-
directive="\${last:1}"
|
|
70
|
-
unset 'lines[count-1]'
|
|
71
|
-
(( count-- ))
|
|
72
|
-
fi
|
|
73
|
-
|
|
74
|
-
# Parse @ext: metadata (extension filter for native file completion)
|
|
75
|
-
local extensions=""
|
|
76
|
-
if (( count > 0 )); then
|
|
77
|
-
local maybe_ext="\${lines[count-1]}"
|
|
78
|
-
if [[ "$maybe_ext" == @ext:* ]]; then
|
|
79
|
-
extensions="\${maybe_ext:5}"
|
|
80
|
-
unset 'lines[count-1]'
|
|
81
|
-
fi
|
|
82
|
-
fi
|
|
83
|
-
|
|
84
|
-
local cur=""
|
|
85
|
-
(( \${#_words[@]} > 0 )) && cur="\${_words[\${#_words[@]}-1]}"
|
|
86
|
-
|
|
87
|
-
# Strip --opt= prefix for native file/directory completion
|
|
88
|
-
local inline_prefix=""
|
|
89
|
-
if [[ "$cur" == --*=* ]]; then
|
|
90
|
-
inline_prefix="\${cur%%=*}="
|
|
91
|
-
cur="\${cur#*=}"
|
|
92
|
-
fi
|
|
93
|
-
|
|
94
|
-
# 16 = FileCompletion: delegate entirely to native file completion
|
|
95
|
-
if (( directive & 16 )); then
|
|
96
|
-
local -a entries=($(compgen -f -- "$cur"))
|
|
97
|
-
if [[ -n "$inline_prefix" ]]; then
|
|
98
|
-
local i
|
|
99
|
-
for (( i=0; i<\${#entries[@]}; i++ )); do
|
|
100
|
-
entries[$i]="\${inline_prefix}\${entries[$i]}"
|
|
101
|
-
done
|
|
102
|
-
fi
|
|
103
|
-
COMPREPLY=("\${entries[@]}")
|
|
104
|
-
compopt -o filenames
|
|
105
|
-
return 0
|
|
106
|
-
fi
|
|
107
|
-
|
|
108
|
-
# Extension-filtered file completion: keep matching files + directories
|
|
109
|
-
if [[ -n "$extensions" ]]; then
|
|
110
|
-
local -a all_entries=($(compgen -f -- "$cur"))
|
|
111
|
-
local IFS=','
|
|
112
|
-
local -a ext_arr=($extensions)
|
|
113
|
-
IFS=$'\\n'
|
|
114
|
-
for f in "\${all_entries[@]}"; do
|
|
115
|
-
if [[ -d "$f" ]]; then
|
|
116
|
-
COMPREPLY+=("\${inline_prefix}$f")
|
|
117
|
-
else
|
|
118
|
-
for ext in "\${ext_arr[@]}"; do
|
|
119
|
-
if [[ "$f" == *".$ext" ]]; then
|
|
120
|
-
COMPREPLY+=("\${inline_prefix}$f")
|
|
121
|
-
break
|
|
122
|
-
fi
|
|
123
|
-
done
|
|
124
|
-
fi
|
|
125
|
-
done
|
|
126
|
-
compopt -o filenames
|
|
127
|
-
compopt +o default 2>/dev/null
|
|
128
|
-
return 0
|
|
129
|
-
fi
|
|
130
|
-
|
|
131
|
-
# Start with JS candidates
|
|
132
|
-
if (( \${#lines[@]} > 0 )); then
|
|
133
|
-
COMPREPLY=("\${lines[@]}")
|
|
134
|
-
fi
|
|
135
|
-
|
|
136
|
-
# 32 = DirectoryCompletion: merge native directory matches
|
|
137
|
-
if (( directive & 32 )); then
|
|
138
|
-
local -a dirs=($(compgen -d -- "$cur"))
|
|
139
|
-
if [[ -n "$inline_prefix" ]]; then
|
|
140
|
-
local i
|
|
141
|
-
for (( i=0; i<\${#dirs[@]}; i++ )); do
|
|
142
|
-
dirs[$i]="\${inline_prefix}\${dirs[$i]}"
|
|
143
|
-
done
|
|
144
|
-
fi
|
|
145
|
-
COMPREPLY+=("\${dirs[@]}")
|
|
146
|
-
compopt -o filenames
|
|
147
|
-
fi
|
|
148
|
-
|
|
149
|
-
# 2 = NoFileCompletion, 32 = DirectoryCompletion:
|
|
150
|
-
# suppress -o default file fallback when completions are restricted
|
|
151
|
-
if (( directive & 2 )) || (( directive & 32 )); then
|
|
152
|
-
compopt +o default 2>/dev/null
|
|
153
|
-
fi
|
|
154
|
-
|
|
155
|
-
return 0
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
# Register the completion function
|
|
159
|
-
complete -o default -F _${programName}_completions ${programName}
|
|
160
|
-
`,
|
|
161
|
-
shell: "bash",
|
|
162
|
-
installInstructions: `# To enable completions, add the following to your ~/.bashrc:
|
|
163
|
-
|
|
164
|
-
# Option 1: Source directly
|
|
165
|
-
eval "$(${programName} completion bash)"
|
|
166
|
-
|
|
167
|
-
# Option 2: Save to a file
|
|
168
|
-
${programName} completion bash > ~/.local/share/bash-completion/completions/${programName}
|
|
169
|
-
|
|
170
|
-
# Then reload your shell or run:
|
|
171
|
-
source ~/.bashrc`
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
//#endregion
|
|
176
|
-
//#region src/completion/dynamic/candidate-generator.ts
|
|
177
|
-
/**
|
|
178
|
-
* Generate completion candidates based on context
|
|
179
|
-
*/
|
|
180
|
-
/**
|
|
181
|
-
* Completion directive flags (bitwise)
|
|
182
|
-
*/
|
|
183
|
-
const CompletionDirective = {
|
|
184
|
-
Default: 0,
|
|
185
|
-
NoSpace: 1,
|
|
186
|
-
NoFileCompletion: 2,
|
|
187
|
-
FilterPrefix: 4,
|
|
188
|
-
KeepOrder: 8,
|
|
189
|
-
FileCompletion: 16,
|
|
190
|
-
DirectoryCompletion: 32,
|
|
191
|
-
Error: 64
|
|
192
|
-
};
|
|
193
|
-
/**
|
|
194
|
-
* Generate completion candidates based on context
|
|
195
|
-
*/
|
|
196
|
-
function generateCandidates(context) {
|
|
197
|
-
const candidates = [];
|
|
198
|
-
let directive = CompletionDirective.Default;
|
|
199
|
-
switch (context.completionType) {
|
|
200
|
-
case "subcommand": return generateSubcommandCandidates(context);
|
|
201
|
-
case "option-name": return generateOptionNameCandidates(context);
|
|
202
|
-
case "option-value": return generateOptionValueCandidates(context);
|
|
203
|
-
case "positional": return generatePositionalCandidates(context);
|
|
204
|
-
default: return {
|
|
205
|
-
candidates,
|
|
206
|
-
directive
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Execute a shell command and return results as candidates
|
|
212
|
-
*/
|
|
213
|
-
function executeShellCommand(command) {
|
|
214
|
-
try {
|
|
215
|
-
return (0, node_child_process.execSync)(command, {
|
|
216
|
-
encoding: "utf-8",
|
|
217
|
-
timeout: 5e3
|
|
218
|
-
}).split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => ({
|
|
219
|
-
value: line,
|
|
220
|
-
type: "value"
|
|
221
|
-
}));
|
|
222
|
-
} catch {
|
|
223
|
-
return [];
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* Resolve value completion, executing shell commands and file lookups in JS
|
|
228
|
-
*/
|
|
229
|
-
function resolveValueCandidates(vc, candidates, _currentWord, description) {
|
|
230
|
-
let directive = CompletionDirective.FilterPrefix;
|
|
231
|
-
let fileExtensions;
|
|
232
|
-
switch (vc.type) {
|
|
233
|
-
case "choices":
|
|
234
|
-
if (vc.choices) for (const choice of vc.choices) candidates.push({
|
|
235
|
-
value: choice,
|
|
236
|
-
description,
|
|
237
|
-
type: "value"
|
|
238
|
-
});
|
|
239
|
-
directive |= CompletionDirective.NoFileCompletion;
|
|
240
|
-
break;
|
|
241
|
-
case "file":
|
|
242
|
-
if (vc.extensions && vc.extensions.length > 0) {
|
|
243
|
-
fileExtensions = Array.from(new Set(vc.extensions.map((ext) => ext.trim().replace(/^\./, "")).filter((ext) => ext.length > 0)));
|
|
244
|
-
if (fileExtensions.length === 0) {
|
|
245
|
-
fileExtensions = void 0;
|
|
246
|
-
directive |= CompletionDirective.FileCompletion;
|
|
247
|
-
}
|
|
248
|
-
} else directive |= CompletionDirective.FileCompletion;
|
|
249
|
-
break;
|
|
250
|
-
case "directory":
|
|
251
|
-
directive |= CompletionDirective.DirectoryCompletion;
|
|
252
|
-
break;
|
|
253
|
-
case "command":
|
|
254
|
-
if (vc.shellCommand) candidates.push(...executeShellCommand(vc.shellCommand));
|
|
255
|
-
directive |= CompletionDirective.NoFileCompletion;
|
|
256
|
-
break;
|
|
257
|
-
case "none":
|
|
258
|
-
directive |= CompletionDirective.NoFileCompletion;
|
|
259
|
-
break;
|
|
260
|
-
}
|
|
261
|
-
return {
|
|
262
|
-
directive,
|
|
263
|
-
fileExtensions
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* Generate subcommand candidates
|
|
268
|
-
*/
|
|
269
|
-
function generateSubcommandCandidates(context) {
|
|
270
|
-
const candidates = [];
|
|
271
|
-
let directive = CompletionDirective.FilterPrefix;
|
|
272
|
-
for (const name of context.subcommands) {
|
|
273
|
-
let description;
|
|
274
|
-
if (context.currentCommand.subCommands) {
|
|
275
|
-
const sub = context.currentCommand.subCommands[name];
|
|
276
|
-
if (sub && typeof sub !== "function") description = sub.description;
|
|
277
|
-
}
|
|
278
|
-
candidates.push({
|
|
279
|
-
value: name,
|
|
280
|
-
description,
|
|
281
|
-
type: "subcommand"
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
if (candidates.length === 0 || context.currentWord.startsWith("-")) {
|
|
285
|
-
const optionResult = generateOptionNameCandidates(context);
|
|
286
|
-
candidates.push(...optionResult.candidates);
|
|
287
|
-
}
|
|
288
|
-
return {
|
|
289
|
-
candidates,
|
|
290
|
-
directive
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* Generate option name candidates
|
|
295
|
-
*/
|
|
296
|
-
function generateOptionNameCandidates(context) {
|
|
297
|
-
const candidates = [];
|
|
298
|
-
const directive = CompletionDirective.FilterPrefix;
|
|
299
|
-
const availableOptions = context.options.filter((opt) => {
|
|
300
|
-
if (opt.valueType === "array") return true;
|
|
301
|
-
return !context.usedOptions.has(opt.cliName) && !context.usedOptions.has(opt.alias || "");
|
|
302
|
-
});
|
|
303
|
-
for (const opt of availableOptions) candidates.push({
|
|
304
|
-
value: `--${opt.cliName}`,
|
|
305
|
-
description: opt.description,
|
|
306
|
-
type: "option"
|
|
307
|
-
});
|
|
308
|
-
if (!context.usedOptions.has("help")) candidates.push({
|
|
309
|
-
value: "--help",
|
|
310
|
-
description: "Show help information",
|
|
311
|
-
type: "option"
|
|
312
|
-
});
|
|
313
|
-
return {
|
|
314
|
-
candidates,
|
|
315
|
-
directive
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
/**
|
|
319
|
-
* Generate option value candidates
|
|
320
|
-
*/
|
|
321
|
-
function generateOptionValueCandidates(context) {
|
|
322
|
-
const candidates = [];
|
|
323
|
-
if (!context.targetOption) return {
|
|
324
|
-
candidates,
|
|
325
|
-
directive: CompletionDirective.FilterPrefix
|
|
326
|
-
};
|
|
327
|
-
const vc = context.targetOption.valueCompletion;
|
|
328
|
-
if (!vc) return {
|
|
329
|
-
candidates,
|
|
330
|
-
directive: CompletionDirective.FilterPrefix
|
|
331
|
-
};
|
|
332
|
-
const { directive, fileExtensions } = resolveValueCandidates(vc, candidates, context.currentWord);
|
|
333
|
-
return {
|
|
334
|
-
candidates,
|
|
335
|
-
directive,
|
|
336
|
-
fileExtensions
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
/**
|
|
340
|
-
* Generate positional argument candidates
|
|
341
|
-
*/
|
|
342
|
-
function generatePositionalCandidates(context) {
|
|
343
|
-
const candidates = [];
|
|
344
|
-
const positionalIndex = context.positionalIndex ?? 0;
|
|
345
|
-
const positional = context.positionals[positionalIndex] ?? (context.positionals.at(-1)?.variadic ? context.positionals.at(-1) : void 0);
|
|
346
|
-
if (!positional) return {
|
|
347
|
-
candidates,
|
|
348
|
-
directive: CompletionDirective.FilterPrefix
|
|
349
|
-
};
|
|
350
|
-
const vc = positional.valueCompletion;
|
|
351
|
-
if (!vc) return {
|
|
352
|
-
candidates,
|
|
353
|
-
directive: CompletionDirective.FilterPrefix
|
|
354
|
-
};
|
|
355
|
-
const { directive, fileExtensions } = resolveValueCandidates(vc, candidates, context.currentWord, positional.description);
|
|
356
|
-
return {
|
|
357
|
-
candidates,
|
|
358
|
-
directive,
|
|
359
|
-
fileExtensions
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
//#endregion
|
|
364
|
-
//#region src/completion/value-completion-resolver.ts
|
|
365
|
-
/**
|
|
366
|
-
* Resolve value completion from field metadata
|
|
367
|
-
*
|
|
368
|
-
* Priority:
|
|
369
|
-
* 1. Explicit custom completion (choices or shellCommand)
|
|
370
|
-
* 2. Explicit completion type (file, directory, none)
|
|
371
|
-
* 3. Auto-detected enum values from schema
|
|
372
|
-
*/
|
|
373
|
-
function resolveValueCompletion(field) {
|
|
374
|
-
const meta = field.completion;
|
|
375
|
-
if (meta?.custom) {
|
|
376
|
-
if (meta.custom.choices && meta.custom.choices.length > 0) return {
|
|
377
|
-
type: "choices",
|
|
378
|
-
choices: meta.custom.choices
|
|
379
|
-
};
|
|
380
|
-
if (meta.custom.shellCommand) return {
|
|
381
|
-
type: "command",
|
|
382
|
-
shellCommand: meta.custom.shellCommand
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
if (meta?.type) {
|
|
386
|
-
if (meta.type === "file") return meta.extensions ? {
|
|
387
|
-
type: "file",
|
|
388
|
-
extensions: meta.extensions
|
|
389
|
-
} : { type: "file" };
|
|
390
|
-
if (meta.type === "directory") return { type: "directory" };
|
|
391
|
-
if (meta.type === "none") return { type: "none" };
|
|
392
|
-
}
|
|
393
|
-
if (field.enumValues && field.enumValues.length > 0) return {
|
|
394
|
-
type: "choices",
|
|
395
|
-
choices: field.enumValues
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
//#endregion
|
|
400
|
-
//#region src/completion/dynamic/context-parser.ts
|
|
401
|
-
/**
|
|
402
|
-
* Parse completion context from partial command line
|
|
403
|
-
*/
|
|
404
|
-
/**
|
|
405
|
-
* Extract options from a command
|
|
406
|
-
*/
|
|
407
|
-
function extractOptions$1(command) {
|
|
408
|
-
if (!command.args) return [];
|
|
409
|
-
return require_schema_extractor.extractFields(command.args).fields.filter((field) => !field.positional).map((field) => ({
|
|
410
|
-
name: field.name,
|
|
411
|
-
cliName: field.cliName,
|
|
412
|
-
alias: field.alias,
|
|
413
|
-
description: field.description,
|
|
414
|
-
takesValue: field.type !== "boolean",
|
|
415
|
-
valueType: field.type,
|
|
416
|
-
required: field.required,
|
|
417
|
-
valueCompletion: resolveValueCompletion(field)
|
|
418
|
-
}));
|
|
419
|
-
}
|
|
420
|
-
/**
|
|
421
|
-
* Extract positionals from a command
|
|
422
|
-
*/
|
|
423
|
-
function extractPositionalsForContext(command) {
|
|
424
|
-
if (!command.args) return [];
|
|
425
|
-
return require_schema_extractor.extractFields(command.args).fields.filter((field) => field.positional).map((field, index) => ({
|
|
426
|
-
name: field.name,
|
|
427
|
-
cliName: field.cliName,
|
|
428
|
-
position: index,
|
|
429
|
-
description: field.description,
|
|
430
|
-
required: field.required,
|
|
431
|
-
variadic: field.type === "array",
|
|
432
|
-
valueCompletion: resolveValueCompletion(field)
|
|
433
|
-
}));
|
|
434
|
-
}
|
|
435
|
-
/**
|
|
436
|
-
* Get subcommand names from a command
|
|
437
|
-
*/
|
|
438
|
-
function getSubcommandNames(command) {
|
|
439
|
-
if (!command.subCommands) return [];
|
|
440
|
-
return Object.keys(command.subCommands).filter((name) => !name.startsWith("__"));
|
|
441
|
-
}
|
|
442
|
-
/**
|
|
443
|
-
* Resolve subcommand by name
|
|
444
|
-
*/
|
|
445
|
-
function resolveSubcommand(command, name) {
|
|
446
|
-
if (!command.subCommands) return null;
|
|
447
|
-
const sub = command.subCommands[name];
|
|
448
|
-
if (!sub) return null;
|
|
449
|
-
if (typeof sub === "function") return null;
|
|
450
|
-
return sub;
|
|
451
|
-
}
|
|
452
|
-
/**
|
|
453
|
-
* Check if a word is an option (starts with - or --)
|
|
454
|
-
*/
|
|
455
|
-
function isOption(word) {
|
|
456
|
-
return word.startsWith("-");
|
|
457
|
-
}
|
|
458
|
-
/**
|
|
459
|
-
* Parse option name from word (e.g., "--foo=bar" -> "foo", "-v" -> "v")
|
|
460
|
-
*/
|
|
461
|
-
function parseOptionName(word) {
|
|
462
|
-
if (word.startsWith("--")) {
|
|
463
|
-
const withoutPrefix = word.slice(2);
|
|
464
|
-
const eqIndex = withoutPrefix.indexOf("=");
|
|
465
|
-
return eqIndex >= 0 ? withoutPrefix.slice(0, eqIndex) : withoutPrefix;
|
|
466
|
-
}
|
|
467
|
-
if (word.startsWith("-")) return word.slice(1, 2);
|
|
468
|
-
return word;
|
|
469
|
-
}
|
|
470
|
-
/**
|
|
471
|
-
* Check if option has inline value (e.g., "--foo=bar")
|
|
472
|
-
*/
|
|
473
|
-
function hasInlineValue(word) {
|
|
474
|
-
return word.includes("=");
|
|
475
|
-
}
|
|
476
|
-
/**
|
|
477
|
-
* Find option by name or alias
|
|
478
|
-
*/
|
|
479
|
-
function findOption(options, nameOrAlias) {
|
|
480
|
-
return options.find((opt) => opt.cliName === nameOrAlias || opt.alias === nameOrAlias);
|
|
481
|
-
}
|
|
482
|
-
/**
|
|
483
|
-
* Parse completion context from command line arguments
|
|
484
|
-
*
|
|
485
|
-
* @param argv - Arguments after the program name (e.g., ["build", "--fo"])
|
|
486
|
-
* @param rootCommand - The root command
|
|
487
|
-
* @returns Completion context
|
|
488
|
-
*/
|
|
489
|
-
function parseCompletionContext(argv, rootCommand) {
|
|
490
|
-
let currentCommand = rootCommand;
|
|
491
|
-
const subcommandPath = [];
|
|
492
|
-
const usedOptions = /* @__PURE__ */ new Set();
|
|
493
|
-
let positionalCount = 0;
|
|
494
|
-
let i = 0;
|
|
495
|
-
let options = extractOptions$1(currentCommand);
|
|
496
|
-
let afterDoubleDash = false;
|
|
497
|
-
while (i < argv.length - 1) {
|
|
498
|
-
const word = argv[i];
|
|
499
|
-
if (!afterDoubleDash && word === "--") {
|
|
500
|
-
afterDoubleDash = true;
|
|
501
|
-
i++;
|
|
502
|
-
continue;
|
|
503
|
-
}
|
|
504
|
-
if (!afterDoubleDash && isOption(word)) {
|
|
505
|
-
const optName = parseOptionName(word);
|
|
506
|
-
const opt = findOption(options, optName);
|
|
507
|
-
if (opt) {
|
|
508
|
-
usedOptions.add(opt.cliName);
|
|
509
|
-
if (opt.alias) usedOptions.add(opt.alias);
|
|
510
|
-
if (opt.takesValue && !hasInlineValue(word)) i++;
|
|
511
|
-
}
|
|
512
|
-
i++;
|
|
513
|
-
continue;
|
|
514
|
-
}
|
|
515
|
-
const subcommand = afterDoubleDash ? null : resolveSubcommand(currentCommand, word);
|
|
516
|
-
if (subcommand) {
|
|
517
|
-
subcommandPath.push(word);
|
|
518
|
-
currentCommand = subcommand;
|
|
519
|
-
options = extractOptions$1(currentCommand);
|
|
520
|
-
usedOptions.clear();
|
|
521
|
-
positionalCount = 0;
|
|
522
|
-
i++;
|
|
523
|
-
continue;
|
|
524
|
-
}
|
|
525
|
-
positionalCount++;
|
|
526
|
-
i++;
|
|
527
|
-
}
|
|
528
|
-
const currentWord = argv[argv.length - 1] ?? "";
|
|
529
|
-
const previousWord = argv[argv.length - 2] ?? "";
|
|
530
|
-
const positionals = extractPositionalsForContext(currentCommand);
|
|
531
|
-
const subcommands = getSubcommandNames(currentCommand);
|
|
532
|
-
let completionType;
|
|
533
|
-
let targetOption;
|
|
534
|
-
let positionalIndex;
|
|
535
|
-
if (!afterDoubleDash && previousWord && isOption(previousWord) && !hasInlineValue(previousWord)) {
|
|
536
|
-
const optName = parseOptionName(previousWord);
|
|
537
|
-
const opt = findOption(options, optName);
|
|
538
|
-
if (opt && opt.takesValue) {
|
|
539
|
-
completionType = "option-value";
|
|
540
|
-
targetOption = opt;
|
|
541
|
-
} else if (currentWord.startsWith("-")) completionType = "option-name";
|
|
542
|
-
else {
|
|
543
|
-
completionType = determineDefaultCompletionType(currentWord, subcommands, positionals, positionalCount);
|
|
544
|
-
if (completionType === "positional") positionalIndex = positionalCount;
|
|
545
|
-
}
|
|
546
|
-
} else if (!afterDoubleDash && currentWord.startsWith("--") && hasInlineValue(currentWord)) {
|
|
547
|
-
const optName = parseOptionName(currentWord);
|
|
548
|
-
const opt = findOption(options, optName);
|
|
549
|
-
if (opt && opt.takesValue) {
|
|
550
|
-
completionType = "option-value";
|
|
551
|
-
targetOption = opt;
|
|
552
|
-
} else completionType = "option-name";
|
|
553
|
-
} else if (!afterDoubleDash && currentWord.startsWith("-")) completionType = "option-name";
|
|
554
|
-
else {
|
|
555
|
-
completionType = determineDefaultCompletionType(currentWord, subcommands, positionals, positionalCount, afterDoubleDash);
|
|
556
|
-
if (completionType === "positional") positionalIndex = positionalCount;
|
|
557
|
-
}
|
|
558
|
-
return {
|
|
559
|
-
subcommandPath,
|
|
560
|
-
currentCommand,
|
|
561
|
-
currentWord,
|
|
562
|
-
previousWord,
|
|
563
|
-
completionType,
|
|
564
|
-
targetOption,
|
|
565
|
-
positionalIndex,
|
|
566
|
-
options,
|
|
567
|
-
subcommands,
|
|
568
|
-
positionals,
|
|
569
|
-
usedOptions,
|
|
570
|
-
providedPositionalCount: positionalCount
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
/**
|
|
574
|
-
* Determine default completion type when not completing an option
|
|
575
|
-
*/
|
|
576
|
-
function determineDefaultCompletionType(currentWord, subcommands, positionals, positionalCount, afterDoubleDash) {
|
|
577
|
-
if (afterDoubleDash) return "positional";
|
|
578
|
-
if (subcommands.length > 0) {
|
|
579
|
-
if (subcommands.filter((s) => s.startsWith(currentWord)).length > 0 || currentWord === "") return "subcommand";
|
|
580
|
-
}
|
|
581
|
-
if (positionalCount < positionals.length) return "positional";
|
|
582
|
-
if (positionals.length > 0 && positionals[positionals.length - 1].variadic) return "positional";
|
|
583
|
-
return "subcommand";
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
//#endregion
|
|
587
|
-
//#region src/completion/dynamic/shell-formatter.ts
|
|
588
|
-
/**
|
|
589
|
-
* Format completion candidates for the specified shell
|
|
590
|
-
*
|
|
591
|
-
* @returns Shell-ready output string (lines separated by newline, last line is :directive)
|
|
592
|
-
*/
|
|
593
|
-
function formatForShell(result, options) {
|
|
594
|
-
switch (options.shell) {
|
|
595
|
-
case "bash": return formatForBash(result, options);
|
|
596
|
-
case "zsh": return formatForZsh(result, options);
|
|
597
|
-
case "fish": return formatForFish(result, options);
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
/**
|
|
601
|
-
* Check if the FilterPrefix directive is set
|
|
602
|
-
*/
|
|
603
|
-
function shouldFilterPrefix(directive) {
|
|
604
|
-
return (directive & CompletionDirective.FilterPrefix) !== 0;
|
|
605
|
-
}
|
|
606
|
-
/**
|
|
607
|
-
* Filter candidates by prefix
|
|
608
|
-
*/
|
|
609
|
-
function filterByPrefix(candidates, prefix) {
|
|
610
|
-
if (!prefix) return candidates;
|
|
611
|
-
return candidates.filter((c) => c.value.startsWith(prefix));
|
|
612
|
-
}
|
|
613
|
-
/**
|
|
614
|
-
* Append extension metadata and directive to output lines
|
|
615
|
-
*/
|
|
616
|
-
function appendMetadata(lines, result) {
|
|
617
|
-
if (result.fileExtensions && result.fileExtensions.length > 0) lines.push(`@ext:${result.fileExtensions.join(",")}`);
|
|
618
|
-
lines.push(`:${result.directive}`);
|
|
619
|
-
}
|
|
620
|
-
/**
|
|
621
|
-
* Format for bash
|
|
622
|
-
*
|
|
623
|
-
* - Pre-filters candidates by currentWord prefix (replaces compgen -W)
|
|
624
|
-
* - Handles --opt=value inline values by prepending prefix
|
|
625
|
-
* - Outputs plain values only (no descriptions - bash COMPREPLY doesn't support them)
|
|
626
|
-
* - Last line: :directive
|
|
627
|
-
*/
|
|
628
|
-
function formatForBash(result, options) {
|
|
629
|
-
let { candidates } = result;
|
|
630
|
-
if (shouldFilterPrefix(result.directive)) candidates = filterByPrefix(candidates, options.currentWord);
|
|
631
|
-
const lines = candidates.map((c) => {
|
|
632
|
-
if (options.inlinePrefix) return `${options.inlinePrefix}${c.value}`;
|
|
633
|
-
return c.value;
|
|
634
|
-
});
|
|
635
|
-
appendMetadata(lines, result);
|
|
636
|
-
return lines.join("\n");
|
|
637
|
-
}
|
|
638
|
-
/**
|
|
639
|
-
* Format for zsh
|
|
640
|
-
*
|
|
641
|
-
* - Outputs value:description pairs for _describe
|
|
642
|
-
* - Colons in values/descriptions are escaped with backslash
|
|
643
|
-
* - Last line: :directive
|
|
644
|
-
*/
|
|
645
|
-
function formatForZsh(result, _options) {
|
|
646
|
-
const lines = result.candidates.map((c) => {
|
|
647
|
-
const escapedValue = c.value.replace(/:/g, "\\:");
|
|
648
|
-
if (c.description) return `${escapedValue}:${c.description.replace(/:/g, "\\:")}`;
|
|
649
|
-
return escapedValue;
|
|
650
|
-
});
|
|
651
|
-
appendMetadata(lines, result);
|
|
652
|
-
return lines.join("\n");
|
|
653
|
-
}
|
|
654
|
-
/**
|
|
655
|
-
* Format for fish
|
|
656
|
-
*
|
|
657
|
-
* - Outputs value\tdescription pairs
|
|
658
|
-
* - Last line: :directive
|
|
659
|
-
*/
|
|
660
|
-
function formatForFish(result, _options) {
|
|
661
|
-
const lines = result.candidates.map((c) => {
|
|
662
|
-
if (c.description) return `${c.value}\t${c.description}`;
|
|
663
|
-
return c.value;
|
|
664
|
-
});
|
|
665
|
-
appendMetadata(lines, result);
|
|
666
|
-
return lines.join("\n");
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
//#endregion
|
|
670
|
-
//#region src/completion/dynamic/complete-command.ts
|
|
671
|
-
/**
|
|
672
|
-
* Dynamic completion command implementation
|
|
673
|
-
*
|
|
674
|
-
* This creates a hidden `__complete` command that outputs completion candidates
|
|
675
|
-
* for shell scripts to consume. Usage:
|
|
676
|
-
*
|
|
677
|
-
* mycli __complete --shell bash -- build --fo
|
|
678
|
-
* mycli __complete --shell zsh -- plugin add
|
|
679
|
-
*
|
|
680
|
-
* Output format depends on the target shell:
|
|
681
|
-
* bash: plain values (pre-filtered by prefix), last line :directive
|
|
682
|
-
* zsh: value:description pairs, last line :directive
|
|
683
|
-
* fish: value\tdescription pairs, last line :directive
|
|
684
|
-
*/
|
|
685
|
-
/**
|
|
686
|
-
* Detect inline option-value prefix (e.g., "--format=" from "--format=json")
|
|
687
|
-
*/
|
|
688
|
-
function detectInlinePrefix(currentWord) {
|
|
689
|
-
if (currentWord.startsWith("--") && currentWord.includes("=")) return currentWord.slice(0, currentWord.indexOf("=") + 1);
|
|
690
|
-
}
|
|
691
|
-
/**
|
|
692
|
-
* Schema for the __complete command
|
|
693
|
-
*/
|
|
694
|
-
const completeArgsSchema = zod.z.object({
|
|
695
|
-
shell: require_schema_extractor.arg(zod.z.enum([
|
|
696
|
-
"bash",
|
|
697
|
-
"zsh",
|
|
698
|
-
"fish"
|
|
699
|
-
]), { description: "Target shell for output formatting" }),
|
|
700
|
-
args: require_schema_extractor.arg(zod.z.array(zod.z.string()).default([]), {
|
|
701
|
-
positional: true,
|
|
702
|
-
description: "Arguments to complete",
|
|
703
|
-
variadic: true
|
|
704
|
-
})
|
|
705
|
-
});
|
|
706
|
-
/**
|
|
707
|
-
* Create the dynamic completion command
|
|
708
|
-
*
|
|
709
|
-
* @param rootCommand - The root command to generate completions for
|
|
710
|
-
* @param programName - The program name (optional, defaults to rootCommand.name)
|
|
711
|
-
* @returns A command that outputs completion candidates
|
|
712
|
-
*/
|
|
713
|
-
function createDynamicCompleteCommand(rootCommand, _programName) {
|
|
714
|
-
return defineCommand({
|
|
715
|
-
name: "__complete",
|
|
716
|
-
args: completeArgsSchema,
|
|
717
|
-
run(args) {
|
|
718
|
-
const context = parseCompletionContext(args.args, rootCommand);
|
|
719
|
-
const result = generateCandidates(context);
|
|
720
|
-
const inlinePrefix = detectInlinePrefix(context.currentWord);
|
|
721
|
-
const output = formatForShell(result, {
|
|
722
|
-
shell: args.shell,
|
|
723
|
-
currentWord: inlinePrefix ? context.currentWord.slice(inlinePrefix.length) : context.currentWord,
|
|
724
|
-
inlinePrefix
|
|
725
|
-
});
|
|
726
|
-
console.log(output);
|
|
727
|
-
}
|
|
728
|
-
});
|
|
729
|
-
}
|
|
730
|
-
/**
|
|
731
|
-
* Check if a command tree contains the __complete command
|
|
732
|
-
*/
|
|
733
|
-
function hasCompleteCommand(command) {
|
|
734
|
-
return Boolean(command.subCommands?.["__complete"]);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
//#endregion
|
|
738
|
-
//#region src/completion/fish.ts
|
|
739
|
-
/**
|
|
740
|
-
* Generate fish completion script for a command
|
|
741
|
-
*
|
|
742
|
-
* Generates a minimal script that delegates all logic to the CLI's __complete command.
|
|
743
|
-
* The shell script only handles:
|
|
744
|
-
* - Getting current command line tokens
|
|
745
|
-
* - Calling __complete with --shell fish
|
|
746
|
-
* - Echoing output as completions
|
|
747
|
-
* - Falling back to native file/directory completion when directed
|
|
748
|
-
*/
|
|
749
|
-
function generateFishCompletion(_command, options) {
|
|
750
|
-
const programName = options.programName;
|
|
751
|
-
return {
|
|
752
|
-
script: `# Fish completion for ${programName}
|
|
753
|
-
# Generated by politty
|
|
754
|
-
|
|
755
|
-
function __fish_${programName}_complete
|
|
756
|
-
set -l args (commandline -opc)
|
|
757
|
-
set -e args[1]
|
|
758
|
-
set -l directive 0
|
|
759
|
-
set -l extensions ""
|
|
760
|
-
|
|
761
|
-
# commandline -opc excludes the current token; always include it
|
|
762
|
-
set -l ct (commandline -ct)
|
|
763
|
-
if test (count $ct) -eq 0
|
|
764
|
-
set -a args ""
|
|
765
|
-
else
|
|
766
|
-
set -a args $ct
|
|
767
|
-
end
|
|
768
|
-
|
|
769
|
-
for line in (${programName} __complete --shell fish -- $args 2>/dev/null)
|
|
770
|
-
if string match -q ':*' -- $line
|
|
771
|
-
set directive (string sub -s 2 -- $line)
|
|
772
|
-
else if string match -q '@ext:*' -- $line
|
|
773
|
-
set extensions (string sub -s 6 -- $line)
|
|
774
|
-
else if test -n "$line"
|
|
775
|
-
echo $line
|
|
776
|
-
end
|
|
777
|
-
end
|
|
778
|
-
|
|
779
|
-
# 16 = FileCompletion: delegate entirely to native file completion
|
|
780
|
-
if test (math "bitand($directive, 16)") -ne 0
|
|
781
|
-
__fish_complete_path
|
|
782
|
-
return
|
|
783
|
-
end
|
|
784
|
-
|
|
785
|
-
# Extension-filtered file completion: keep matching files + directories
|
|
786
|
-
if test -n "$extensions"
|
|
787
|
-
set -l cur (commandline -ct)
|
|
788
|
-
test (count $cur) -eq 0; and set cur ""
|
|
789
|
-
__fish_complete_directories "$cur"
|
|
790
|
-
for ext in (string split "," -- $extensions)
|
|
791
|
-
for f in "$cur"*.$ext
|
|
792
|
-
if test -f "$f"
|
|
793
|
-
echo $f
|
|
794
|
-
end
|
|
795
|
-
end
|
|
796
|
-
end
|
|
797
|
-
return
|
|
798
|
-
end
|
|
799
|
-
|
|
800
|
-
# 32 = DirectoryCompletion: add native directory matches
|
|
801
|
-
if test (math "bitand($directive, 32)") -ne 0
|
|
802
|
-
__fish_complete_directories
|
|
803
|
-
end
|
|
804
|
-
end
|
|
805
|
-
|
|
806
|
-
# Clear existing completions
|
|
807
|
-
complete -e -c ${programName}
|
|
808
|
-
|
|
809
|
-
# Register completion
|
|
810
|
-
complete -c ${programName} -f -a '(__fish_${programName}_complete)'
|
|
811
|
-
`,
|
|
812
|
-
shell: "fish",
|
|
813
|
-
installInstructions: `# To enable completions, run one of the following:
|
|
814
|
-
|
|
815
|
-
# Option 1: Source directly
|
|
816
|
-
${programName} completion fish | source
|
|
817
|
-
|
|
818
|
-
# Option 2: Save to the fish completions directory
|
|
819
|
-
${programName} completion fish > ~/.config/fish/completions/${programName}.fish
|
|
820
|
-
|
|
821
|
-
# The completion will be available immediately in new shell sessions.
|
|
822
|
-
# To use in the current session, run:
|
|
823
|
-
source ~/.config/fish/completions/${programName}.fish`
|
|
824
|
-
};
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
//#endregion
|
|
828
|
-
//#region src/completion/zsh.ts
|
|
829
|
-
/**
|
|
830
|
-
* Generate zsh completion script for a command
|
|
831
|
-
*
|
|
832
|
-
* Generates a minimal script that delegates all logic to the CLI's __complete command.
|
|
833
|
-
* The shell script only handles:
|
|
834
|
-
* - Getting current command line tokens
|
|
835
|
-
* - Calling __complete with --shell zsh
|
|
836
|
-
* - Passing output directly to _describe
|
|
837
|
-
* - Falling back to native file/directory completion when directed
|
|
838
|
-
*/
|
|
839
|
-
function generateZshCompletion(_command, options) {
|
|
840
|
-
const programName = options.programName;
|
|
841
|
-
return {
|
|
842
|
-
script: `#compdef ${programName}
|
|
843
|
-
|
|
844
|
-
# Zsh completion for ${programName}
|
|
845
|
-
# Generated by politty
|
|
846
|
-
|
|
847
|
-
_${programName}() {
|
|
848
|
-
local -a candidates
|
|
849
|
-
local line directive=0
|
|
850
|
-
local -a args=("\${words[@]:1}")
|
|
851
|
-
local -a output=("\${(@f)$(${programName} __complete --shell zsh -- "\${args[@]}" 2>/dev/null)}")
|
|
852
|
-
|
|
853
|
-
local extensions=""
|
|
854
|
-
for line in "\${output[@]}"; do
|
|
855
|
-
if [[ "$line" == :* ]]; then
|
|
856
|
-
directive="\${line:1}"
|
|
857
|
-
elif [[ "$line" == @ext:* ]]; then
|
|
858
|
-
extensions="\${line:5}"
|
|
859
|
-
elif [[ -n "$line" ]]; then
|
|
860
|
-
candidates+=("$line")
|
|
861
|
-
fi
|
|
862
|
-
done
|
|
863
|
-
|
|
864
|
-
# 16 = FileCompletion: delegate entirely to native file completion
|
|
865
|
-
if (( directive & 16 )); then
|
|
866
|
-
_files
|
|
867
|
-
return 0
|
|
868
|
-
fi
|
|
869
|
-
|
|
870
|
-
# Extension-filtered file completion: call _files -g per extension
|
|
871
|
-
if [[ -n "$extensions" ]]; then
|
|
872
|
-
local ext
|
|
873
|
-
for ext in \${(s:,:)extensions}; do
|
|
874
|
-
_files -g "*.$ext"
|
|
875
|
-
done
|
|
876
|
-
return 0
|
|
877
|
-
fi
|
|
878
|
-
|
|
879
|
-
if (( \${#candidates[@]} > 0 )); then
|
|
880
|
-
_describe 'completions' candidates
|
|
881
|
-
fi
|
|
882
|
-
|
|
883
|
-
# 32 = DirectoryCompletion: add native directory matches
|
|
884
|
-
if (( directive & 32 )); then
|
|
885
|
-
_files -/
|
|
886
|
-
fi
|
|
887
|
-
|
|
888
|
-
# 2 = NoFileCompletion, 32 = DirectoryCompletion:
|
|
889
|
-
# prevent fallback to default completers (e.g. file completion)
|
|
890
|
-
if (( directive & 2 )) || (( directive & 32 )); then
|
|
891
|
-
return 0
|
|
892
|
-
fi
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
# Prevent _files -g from falling back to showing all files when no pattern matches
|
|
896
|
-
zstyle ':completion:*:*:${programName}:*' file-patterns '%p:globbed-files *(-/):directories'
|
|
897
|
-
|
|
898
|
-
compdef _${programName} ${programName}
|
|
899
|
-
`,
|
|
900
|
-
shell: "zsh",
|
|
901
|
-
installInstructions: `# To enable completions, add the following to your ~/.zshrc:
|
|
902
|
-
|
|
903
|
-
# Option 1: Source directly (add before compinit)
|
|
904
|
-
eval "$(${programName} completion zsh)"
|
|
905
|
-
|
|
906
|
-
# Option 2: Save to a file in your fpath
|
|
907
|
-
${programName} completion zsh > ~/.zsh/completions/_${programName}
|
|
908
|
-
|
|
909
|
-
# Make sure your fpath includes the completions directory:
|
|
910
|
-
# fpath=(~/.zsh/completions $fpath)
|
|
911
|
-
# autoload -Uz compinit && compinit
|
|
912
|
-
|
|
913
|
-
# Then reload your shell or run:
|
|
914
|
-
source ~/.zshrc`
|
|
915
|
-
};
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
//#endregion
|
|
919
|
-
//#region src/completion/extractor.ts
|
|
920
|
-
/**
|
|
921
|
-
* Extract completion data from commands
|
|
922
|
-
*/
|
|
923
|
-
/**
|
|
924
|
-
* Convert a resolved field to a completable option
|
|
925
|
-
*/
|
|
926
|
-
function fieldToOption(field) {
|
|
927
|
-
return {
|
|
928
|
-
name: field.name,
|
|
929
|
-
cliName: field.cliName,
|
|
930
|
-
alias: field.alias,
|
|
931
|
-
description: field.description,
|
|
932
|
-
takesValue: field.type !== "boolean",
|
|
933
|
-
valueType: field.type,
|
|
934
|
-
required: field.required,
|
|
935
|
-
valueCompletion: resolveValueCompletion(field)
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
/**
|
|
939
|
-
* Extract options from a command's args schema
|
|
940
|
-
*/
|
|
941
|
-
function extractOptions(command) {
|
|
942
|
-
if (!command.args) return [];
|
|
943
|
-
return require_schema_extractor.extractFields(command.args).fields.filter((field) => !field.positional).map(fieldToOption);
|
|
944
|
-
}
|
|
945
|
-
/**
|
|
946
|
-
* Extract positional arguments from a command
|
|
947
|
-
*/
|
|
948
|
-
function extractPositionals(command) {
|
|
949
|
-
if (!command.args) return [];
|
|
950
|
-
return require_schema_extractor.extractFields(command.args).fields.filter((field) => field.positional);
|
|
951
|
-
}
|
|
952
|
-
/**
|
|
953
|
-
* Extract completable positional arguments from a command
|
|
954
|
-
*/
|
|
955
|
-
function extractCompletablePositionals(command) {
|
|
956
|
-
if (!command.args) return [];
|
|
957
|
-
return require_schema_extractor.extractFields(command.args).fields.filter((field) => field.positional).map((field, index) => ({
|
|
958
|
-
name: field.name,
|
|
959
|
-
cliName: field.cliName,
|
|
960
|
-
position: index,
|
|
961
|
-
description: field.description,
|
|
962
|
-
required: field.required,
|
|
963
|
-
valueCompletion: resolveValueCompletion(field)
|
|
964
|
-
}));
|
|
965
|
-
}
|
|
966
|
-
/**
|
|
967
|
-
* Extract a completable subcommand from a command
|
|
968
|
-
*/
|
|
969
|
-
function extractSubcommand(name, command) {
|
|
970
|
-
const subcommands = [];
|
|
971
|
-
if (command.subCommands) for (const [subName, subCommand] of Object.entries(command.subCommands)) if (typeof subCommand === "function") subcommands.push({
|
|
972
|
-
name: subName,
|
|
973
|
-
description: "(lazy loaded)",
|
|
974
|
-
subcommands: [],
|
|
975
|
-
options: [],
|
|
976
|
-
positionals: []
|
|
977
|
-
});
|
|
978
|
-
else subcommands.push(extractSubcommand(subName, subCommand));
|
|
979
|
-
return {
|
|
980
|
-
name,
|
|
981
|
-
description: command.description,
|
|
982
|
-
subcommands,
|
|
983
|
-
options: extractOptions(command),
|
|
984
|
-
positionals: extractCompletablePositionals(command)
|
|
985
|
-
};
|
|
986
|
-
}
|
|
987
|
-
/**
|
|
988
|
-
* Extract completion data from a command tree
|
|
989
|
-
*/
|
|
990
|
-
function extractCompletionData(command, programName) {
|
|
991
|
-
const rootSubcommand = extractSubcommand(programName, command);
|
|
992
|
-
return {
|
|
993
|
-
command: rootSubcommand,
|
|
994
|
-
programName,
|
|
995
|
-
globalOptions: rootSubcommand.options
|
|
996
|
-
};
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
//#endregion
|
|
1000
|
-
Object.defineProperty(exports, 'CompletionDirective', {
|
|
1001
|
-
enumerable: true,
|
|
1002
|
-
get: function () {
|
|
1003
|
-
return CompletionDirective;
|
|
1004
|
-
}
|
|
1005
|
-
});
|
|
1006
|
-
Object.defineProperty(exports, 'createDynamicCompleteCommand', {
|
|
1007
|
-
enumerable: true,
|
|
1008
|
-
get: function () {
|
|
1009
|
-
return createDynamicCompleteCommand;
|
|
1010
|
-
}
|
|
1011
|
-
});
|
|
1012
|
-
Object.defineProperty(exports, 'defineCommand', {
|
|
1013
|
-
enumerable: true,
|
|
1014
|
-
get: function () {
|
|
1015
|
-
return defineCommand;
|
|
1016
|
-
}
|
|
1017
|
-
});
|
|
1018
|
-
Object.defineProperty(exports, 'extractCompletionData', {
|
|
1019
|
-
enumerable: true,
|
|
1020
|
-
get: function () {
|
|
1021
|
-
return extractCompletionData;
|
|
1022
|
-
}
|
|
1023
|
-
});
|
|
1024
|
-
Object.defineProperty(exports, 'extractPositionals', {
|
|
1025
|
-
enumerable: true,
|
|
1026
|
-
get: function () {
|
|
1027
|
-
return extractPositionals;
|
|
1028
|
-
}
|
|
1029
|
-
});
|
|
1030
|
-
Object.defineProperty(exports, 'formatForShell', {
|
|
1031
|
-
enumerable: true,
|
|
1032
|
-
get: function () {
|
|
1033
|
-
return formatForShell;
|
|
1034
|
-
}
|
|
1035
|
-
});
|
|
1036
|
-
Object.defineProperty(exports, 'generateBashCompletion', {
|
|
1037
|
-
enumerable: true,
|
|
1038
|
-
get: function () {
|
|
1039
|
-
return generateBashCompletion;
|
|
1040
|
-
}
|
|
1041
|
-
});
|
|
1042
|
-
Object.defineProperty(exports, 'generateCandidates', {
|
|
1043
|
-
enumerable: true,
|
|
1044
|
-
get: function () {
|
|
1045
|
-
return generateCandidates;
|
|
1046
|
-
}
|
|
1047
|
-
});
|
|
1048
|
-
Object.defineProperty(exports, 'generateFishCompletion', {
|
|
1049
|
-
enumerable: true,
|
|
1050
|
-
get: function () {
|
|
1051
|
-
return generateFishCompletion;
|
|
1052
|
-
}
|
|
1053
|
-
});
|
|
1054
|
-
Object.defineProperty(exports, 'generateZshCompletion', {
|
|
1055
|
-
enumerable: true,
|
|
1056
|
-
get: function () {
|
|
1057
|
-
return generateZshCompletion;
|
|
1058
|
-
}
|
|
1059
|
-
});
|
|
1060
|
-
Object.defineProperty(exports, 'hasCompleteCommand', {
|
|
1061
|
-
enumerable: true,
|
|
1062
|
-
get: function () {
|
|
1063
|
-
return hasCompleteCommand;
|
|
1064
|
-
}
|
|
1065
|
-
});
|
|
1066
|
-
Object.defineProperty(exports, 'parseCompletionContext', {
|
|
1067
|
-
enumerable: true,
|
|
1068
|
-
get: function () {
|
|
1069
|
-
return parseCompletionContext;
|
|
1070
|
-
}
|
|
1071
|
-
});
|
|
1072
|
-
Object.defineProperty(exports, 'resolveValueCompletion', {
|
|
1073
|
-
enumerable: true,
|
|
1074
|
-
get: function () {
|
|
1075
|
-
return resolveValueCompletion;
|
|
1076
|
-
}
|
|
1077
|
-
});
|
|
1078
|
-
//# sourceMappingURL=extractor-cjruDqQ2.cjs.map
|