politty 0.4.15 → 0.5.0
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-Dw0f11Zc.d.ts → arg-registry--NRaNFJM.d.cts} +238 -6
- package/dist/arg-registry--NRaNFJM.d.cts.map +1 -0
- package/dist/{arg-registry-CB5gGtzp.d.cts → arg-registry-6E0WHOh_.d.ts} +238 -6
- package/dist/arg-registry-6E0WHOh_.d.ts.map +1 -0
- package/dist/augment.d.cts +1 -1
- package/dist/augment.d.cts.map +1 -1
- package/dist/augment.d.ts +1 -1
- package/dist/augment.d.ts.map +1 -1
- package/dist/completion/index.cjs +2 -1
- package/dist/completion/index.d.cts +3 -2
- package/dist/completion/index.d.ts +3 -2
- package/dist/completion/index.js +2 -2
- package/dist/completion-BA5JMvVG.js +4067 -0
- package/dist/completion-BA5JMvVG.js.map +1 -0
- package/dist/completion-Cqs1Ja7C.cjs +4169 -0
- package/dist/completion-Cqs1Ja7C.cjs.map +1 -0
- package/dist/docs/index.cjs +89 -29
- package/dist/docs/index.cjs.map +1 -1
- package/dist/docs/index.d.cts +1 -1
- package/dist/docs/index.d.cts.map +1 -1
- package/dist/docs/index.d.ts +1 -1
- package/dist/docs/index.d.ts.map +1 -1
- package/dist/docs/index.js +92 -31
- package/dist/docs/index.js.map +1 -1
- package/dist/{index-C1gGgUeB.d.cts → index-DBMfKZ34.d.ts} +189 -17
- package/dist/index-DBMfKZ34.d.ts.map +1 -0
- package/dist/{index-Dg9Fpz0R.d.ts → index-DJp8k5Bq.d.cts} +189 -17
- package/dist/index-DJp8k5Bq.d.cts.map +1 -0
- package/dist/index.cjs +12 -10
- package/dist/index.d.cts +36 -4
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +36 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/log-collector-Cd2_mv87.cjs.map +1 -1
- package/dist/log-collector-Cu6MCtAx.js.map +1 -1
- package/dist/prompt/clack/index.cjs.map +1 -1
- package/dist/prompt/clack/index.d.cts +1 -1
- package/dist/prompt/clack/index.d.cts.map +1 -1
- package/dist/prompt/clack/index.d.ts +1 -1
- package/dist/prompt/clack/index.d.ts.map +1 -1
- package/dist/prompt/clack/index.js.map +1 -1
- package/dist/prompt/index.d.cts +1 -1
- package/dist/prompt/index.d.cts.map +1 -1
- package/dist/prompt/index.d.ts +1 -1
- package/dist/prompt/index.d.ts.map +1 -1
- package/dist/prompt/inquirer/index.cjs.map +1 -1
- package/dist/prompt/inquirer/index.d.cts +1 -1
- package/dist/prompt/inquirer/index.d.cts.map +1 -1
- package/dist/prompt/inquirer/index.d.ts +1 -1
- package/dist/prompt/inquirer/index.d.ts.map +1 -1
- package/dist/prompt/inquirer/index.js.map +1 -1
- package/dist/prompt-BKHqGrFw.js.map +1 -1
- package/dist/prompt-aXfSf27y.cjs.map +1 -1
- package/dist/{runner-DKAQBNNh.js → runner-BmSEiD9A.js} +319 -52
- package/dist/runner-BmSEiD9A.js.map +1 -0
- package/dist/{runner-CriXJlm4.cjs → runner-CRZ_7Y9i.cjs} +366 -87
- package/dist/runner-CRZ_7Y9i.cjs.map +1 -0
- package/dist/{subcommand-router-ENeCymvX.js → schema-extractor-C50R-1re.js} +175 -137
- package/dist/schema-extractor-C50R-1re.js.map +1 -0
- package/dist/{subcommand-router-CqZX3orq.cjs → schema-extractor-SLPgBNgZ.cjs} +174 -136
- package/dist/schema-extractor-SLPgBNgZ.cjs.map +1 -0
- package/package.json +16 -16
- package/dist/arg-registry-CB5gGtzp.d.cts.map +0 -1
- package/dist/arg-registry-Dw0f11Zc.d.ts.map +0 -1
- package/dist/completion-B5fgnUGm.cjs +0 -1940
- package/dist/completion-B5fgnUGm.cjs.map +0 -1
- package/dist/completion-Ca5ESJlG.js +0 -1844
- package/dist/completion-Ca5ESJlG.js.map +0 -1
- package/dist/index-C1gGgUeB.d.cts.map +0 -1
- package/dist/index-Dg9Fpz0R.d.ts.map +0 -1
- package/dist/runner-CriXJlm4.cjs.map +0 -1
- package/dist/runner-DKAQBNNh.js.map +0 -1
- package/dist/subcommand-router-CqZX3orq.cjs.map +0 -1
- package/dist/subcommand-router-ENeCymvX.js.map +0 -1
|
@@ -1,1940 +0,0 @@
|
|
|
1
|
-
const require_log_collector = require('./log-collector-Cd2_mv87.cjs');
|
|
2
|
-
const require_subcommand_router = require('./subcommand-router-CqZX3orq.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
|
-
aliases: config.aliases,
|
|
12
|
-
args: config.args,
|
|
13
|
-
subCommands: config.subCommands,
|
|
14
|
-
setup: config.setup,
|
|
15
|
-
run: config.run,
|
|
16
|
-
cleanup: config.cleanup,
|
|
17
|
-
notes: config.notes,
|
|
18
|
-
examples: config.examples
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Create a typed defineCommand factory with pre-bound global args type.
|
|
23
|
-
* This is the recommended pattern for type-safe global options.
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* ```ts
|
|
27
|
-
* // global-args.ts
|
|
28
|
-
* type GlobalArgsType = { verbose: boolean; config?: string };
|
|
29
|
-
* export const defineAppCommand = createDefineCommand<GlobalArgsType>();
|
|
30
|
-
*
|
|
31
|
-
* // commands/build.ts
|
|
32
|
-
* export const buildCommand = defineAppCommand({
|
|
33
|
-
* name: "build",
|
|
34
|
-
* args: z.object({ output: arg(z.string().default("dist")) }),
|
|
35
|
-
* run: (args) => {
|
|
36
|
-
* args.verbose; // typed via GlobalArgsType
|
|
37
|
-
* args.output; // typed via local args
|
|
38
|
-
* },
|
|
39
|
-
* });
|
|
40
|
-
* ```
|
|
41
|
-
*/
|
|
42
|
-
function createDefineCommand() {
|
|
43
|
-
return defineCommand;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
//#endregion
|
|
47
|
-
//#region src/completion/value-completion-resolver.ts
|
|
48
|
-
/**
|
|
49
|
-
* Resolve value completion from field metadata
|
|
50
|
-
*
|
|
51
|
-
* Priority:
|
|
52
|
-
* 1. Explicit custom completion (choices or shellCommand)
|
|
53
|
-
* 2. Explicit completion type (file, directory, none)
|
|
54
|
-
* 3. Auto-detected enum values from schema
|
|
55
|
-
*/
|
|
56
|
-
function resolveValueCompletion(field) {
|
|
57
|
-
const meta = field.completion;
|
|
58
|
-
if (meta?.custom) {
|
|
59
|
-
if (meta.custom.choices && meta.custom.choices.length > 0) return {
|
|
60
|
-
type: "choices",
|
|
61
|
-
choices: meta.custom.choices
|
|
62
|
-
};
|
|
63
|
-
if (meta.custom.shellCommand) return {
|
|
64
|
-
type: "command",
|
|
65
|
-
shellCommand: meta.custom.shellCommand
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
if (meta?.type) {
|
|
69
|
-
if (meta.type === "file") {
|
|
70
|
-
if (meta.matcher) return {
|
|
71
|
-
type: "file",
|
|
72
|
-
matcher: meta.matcher
|
|
73
|
-
};
|
|
74
|
-
if (meta.extensions) return {
|
|
75
|
-
type: "file",
|
|
76
|
-
extensions: meta.extensions
|
|
77
|
-
};
|
|
78
|
-
return { type: "file" };
|
|
79
|
-
}
|
|
80
|
-
if (meta.type === "directory") return { type: "directory" };
|
|
81
|
-
if (meta.type === "none") return { type: "none" };
|
|
82
|
-
}
|
|
83
|
-
if (field.enumValues && field.enumValues.length > 0) return {
|
|
84
|
-
type: "choices",
|
|
85
|
-
choices: field.enumValues
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
//#endregion
|
|
90
|
-
//#region src/completion/extractor.ts
|
|
91
|
-
/**
|
|
92
|
-
* Extract completion data from commands
|
|
93
|
-
*/
|
|
94
|
-
/**
|
|
95
|
-
* Sanitize a name for use as a shell function/variable identifier.
|
|
96
|
-
* Replaces any character that is not alphanumeric or underscore with underscore.
|
|
97
|
-
*
|
|
98
|
-
* Note: This is not injective -- distinct names may produce the same output
|
|
99
|
-
* (e.g., "foo-bar" and "foo_bar" both become "foo_bar"). When used for nested
|
|
100
|
-
* path encoding (`path.map(sanitize).join("_")`), cross-level collisions are
|
|
101
|
-
* theoretically possible (e.g., "foo-bar:baz" vs "foo:bar-baz") but extremely
|
|
102
|
-
* unlikely in real CLI designs. If collision-safety is needed, sanitize must be
|
|
103
|
-
* replaced with an injective encoding.
|
|
104
|
-
*/
|
|
105
|
-
function sanitize(name) {
|
|
106
|
-
return name.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Filter subcommands to only visible (non-internal) ones.
|
|
110
|
-
* Internal subcommands start with "__" and are hidden from completion/help.
|
|
111
|
-
*/
|
|
112
|
-
function getVisibleSubs(subs) {
|
|
113
|
-
return subs.filter((s) => !s.name.startsWith("__"));
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Get all completable subcommand names including aliases.
|
|
117
|
-
* Returns an array of { name, description } for all visible subcommands
|
|
118
|
-
* and their aliases.
|
|
119
|
-
*/
|
|
120
|
-
function getSubNamesWithAliases(subs) {
|
|
121
|
-
const result = [];
|
|
122
|
-
for (const sub of getVisibleSubs(subs)) {
|
|
123
|
-
result.push({
|
|
124
|
-
name: sub.name,
|
|
125
|
-
description: sub.description
|
|
126
|
-
});
|
|
127
|
-
if (sub.aliases) for (const alias of sub.aliases) result.push({
|
|
128
|
-
name: alias,
|
|
129
|
-
description: sub.description
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
return result;
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Convert a resolved field to a completable option
|
|
136
|
-
*/
|
|
137
|
-
function fieldToOption(field) {
|
|
138
|
-
return {
|
|
139
|
-
name: field.name,
|
|
140
|
-
cliName: field.cliName,
|
|
141
|
-
alias: field.alias,
|
|
142
|
-
description: field.description,
|
|
143
|
-
takesValue: field.type !== "boolean",
|
|
144
|
-
valueType: field.type,
|
|
145
|
-
required: field.required,
|
|
146
|
-
valueCompletion: resolveValueCompletion(field)
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Extract options from a command's args schema
|
|
151
|
-
*/
|
|
152
|
-
function extractOptions$1(command) {
|
|
153
|
-
if (!command.args) return [];
|
|
154
|
-
return require_subcommand_router.extractFields(command.args).fields.filter((field) => !field.positional).map(fieldToOption);
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Extract positional arguments from a command
|
|
158
|
-
*/
|
|
159
|
-
function extractPositionals(command) {
|
|
160
|
-
if (!command.args) return [];
|
|
161
|
-
return require_subcommand_router.extractFields(command.args).fields.filter((field) => field.positional);
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Extract completable positional arguments from a command
|
|
165
|
-
*/
|
|
166
|
-
function extractCompletablePositionals(command) {
|
|
167
|
-
if (!command.args) return [];
|
|
168
|
-
return require_subcommand_router.extractFields(command.args).fields.filter((field) => field.positional).map((field, index) => ({
|
|
169
|
-
name: field.name,
|
|
170
|
-
cliName: field.cliName,
|
|
171
|
-
position: index,
|
|
172
|
-
description: field.description,
|
|
173
|
-
required: field.required,
|
|
174
|
-
variadic: field.type === "array",
|
|
175
|
-
valueCompletion: resolveValueCompletion(field)
|
|
176
|
-
}));
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Extract a completable subcommand from a command
|
|
180
|
-
*/
|
|
181
|
-
function extractSubcommand(name, command) {
|
|
182
|
-
const subcommands = [];
|
|
183
|
-
if (command.subCommands) for (const [subName, subCommand] of Object.entries(command.subCommands)) {
|
|
184
|
-
const resolved = require_subcommand_router.resolveSubCommandMeta(subCommand);
|
|
185
|
-
if (resolved) subcommands.push(extractSubcommand(subName, resolved));
|
|
186
|
-
else subcommands.push({
|
|
187
|
-
name: subName,
|
|
188
|
-
description: "(lazy loaded)",
|
|
189
|
-
subcommands: [],
|
|
190
|
-
options: [],
|
|
191
|
-
positionals: []
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
return {
|
|
195
|
-
name,
|
|
196
|
-
description: command.description,
|
|
197
|
-
aliases: command.aliases,
|
|
198
|
-
subcommands,
|
|
199
|
-
options: extractOptions$1(command),
|
|
200
|
-
positionals: extractCompletablePositionals(command)
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
/** Join parent and child with a separator, omitting separator when parent is empty. */
|
|
204
|
-
function joinPrefix(parent, child, sep) {
|
|
205
|
-
return parent ? `${parent}${sep}${child}` : child;
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Collect opt-takes-value case entries for a subcommand tree.
|
|
209
|
-
* Used by bash and zsh generators (identical case syntax: `path:--opt) return 0 ;;`).
|
|
210
|
-
* parentPath is a colon-delimited path (e.g., "" for root, "workspace:user" for nested).
|
|
211
|
-
*/
|
|
212
|
-
function optTakesValueEntries(sub, parentPath) {
|
|
213
|
-
const lines = [];
|
|
214
|
-
for (const opt of sub.options) if (opt.takesValue) {
|
|
215
|
-
const patterns = [`${parentPath}:--${opt.cliName}`];
|
|
216
|
-
if (opt.alias) for (const a of opt.alias) patterns.push(`${parentPath}:${a.length === 1 ? `-${a}` : `--${a}`}`);
|
|
217
|
-
lines.push(` ${patterns.join("|")}) return 0 ;;`);
|
|
218
|
-
}
|
|
219
|
-
for (const child of getVisibleSubs(sub.subcommands)) {
|
|
220
|
-
lines.push(...optTakesValueEntries(child, joinPrefix(parentPath, child.name, ":")));
|
|
221
|
-
if (child.aliases) for (const alias of child.aliases) lines.push(...optTakesValueEntries(child, joinPrefix(parentPath, alias, ":")));
|
|
222
|
-
}
|
|
223
|
-
return lines;
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Recursively collect all subcommand route entries.
|
|
227
|
-
* Returns entries used by all shell generators for both dispatch routing
|
|
228
|
-
* and subcommand lookup (is_subcmd) tables.
|
|
229
|
-
* Aliases are mapped to the same handler as the canonical name.
|
|
230
|
-
*/
|
|
231
|
-
function collectRouteEntries(sub, parentPath = "", parentFunc = "") {
|
|
232
|
-
const entries = [];
|
|
233
|
-
for (const child of getVisibleSubs(sub.subcommands)) {
|
|
234
|
-
const pathStr = joinPrefix(parentPath, child.name, ":");
|
|
235
|
-
const funcSuffix = joinPrefix(parentFunc, sanitize(child.name), "_");
|
|
236
|
-
entries.push(...collectRouteEntries(child, pathStr, funcSuffix));
|
|
237
|
-
entries.push({
|
|
238
|
-
pathStr,
|
|
239
|
-
funcSuffix,
|
|
240
|
-
lookupPattern: `${parentPath}:${child.name}`
|
|
241
|
-
});
|
|
242
|
-
if (child.aliases) for (const alias of child.aliases) {
|
|
243
|
-
const aliasPathStr = joinPrefix(parentPath, alias, ":");
|
|
244
|
-
entries.push(...collectRouteEntries(child, aliasPathStr, funcSuffix));
|
|
245
|
-
entries.push({
|
|
246
|
-
pathStr: aliasPathStr,
|
|
247
|
-
funcSuffix,
|
|
248
|
-
lookupPattern: `${parentPath}:${alias}`
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
return entries;
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Generate is_subcmd case/switch body lines (bash/zsh case syntax).
|
|
256
|
-
* Returns lines for the case statement body only (caller wraps in function).
|
|
257
|
-
*/
|
|
258
|
-
function isSubcmdCaseLines(routeEntries) {
|
|
259
|
-
return routeEntries.map((r) => ` ${r.lookupPattern}) return 0 ;;`);
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Recursively merge global options into a subcommand and all its descendants.
|
|
263
|
-
* Avoids duplicates by checking existing option names.
|
|
264
|
-
*/
|
|
265
|
-
function propagateGlobalOptions(sub, globalOptions) {
|
|
266
|
-
const existingNames = new Set(sub.options.map((o) => o.name));
|
|
267
|
-
const newOpts = globalOptions.filter((o) => !existingNames.has(o.name));
|
|
268
|
-
sub.options = [...sub.options, ...newOpts];
|
|
269
|
-
for (const child of sub.subcommands) propagateGlobalOptions(child, globalOptions);
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Extract completion data from a command tree
|
|
273
|
-
*
|
|
274
|
-
* @param command - The root command
|
|
275
|
-
* @param programName - Program name for completion scripts
|
|
276
|
-
* @param globalArgsSchema - Optional global args schema. When provided, global options
|
|
277
|
-
* are derived from this schema instead of the root command's options.
|
|
278
|
-
*/
|
|
279
|
-
function extractCompletionData(command, programName, globalArgsSchema) {
|
|
280
|
-
const rootSubcommand = extractSubcommand(programName, command);
|
|
281
|
-
let globalOptions;
|
|
282
|
-
if (globalArgsSchema) {
|
|
283
|
-
globalOptions = require_subcommand_router.extractFields(globalArgsSchema).fields.filter((field) => !field.positional).map(fieldToOption);
|
|
284
|
-
propagateGlobalOptions(rootSubcommand, globalOptions);
|
|
285
|
-
} else globalOptions = rootSubcommand.options;
|
|
286
|
-
return {
|
|
287
|
-
command: rootSubcommand,
|
|
288
|
-
programName,
|
|
289
|
-
globalOptions
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
//#endregion
|
|
294
|
-
//#region src/completion/bash.ts
|
|
295
|
-
/** Escape a string for use inside bash double-quotes */
|
|
296
|
-
function escapeBashDQ(s) {
|
|
297
|
-
return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$").replace(/`/g, "\\`");
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Generate bash value completion code for a ValueCompletion spec.
|
|
301
|
-
* Returns an array of bash lines.
|
|
302
|
-
*/
|
|
303
|
-
function bashValueLines(vc, inline) {
|
|
304
|
-
if (!vc) return [];
|
|
305
|
-
switch (vc.type) {
|
|
306
|
-
case "choices": {
|
|
307
|
-
const items = vc.choices.map((c) => `"${escapeBashDQ(c)}"`).join(" ");
|
|
308
|
-
if (inline) return [
|
|
309
|
-
`local -a _choices=(${items})`,
|
|
310
|
-
`COMPREPLY=()`,
|
|
311
|
-
`local _c; for _c in "\${_choices[@]}"; do [[ "$_c" == "$_cur"* ]] && COMPREPLY+=("\${_inline_prefix}\${_c}"); done`,
|
|
312
|
-
`compopt -o nospace`,
|
|
313
|
-
`compopt +o default 2>/dev/null`
|
|
314
|
-
];
|
|
315
|
-
return [
|
|
316
|
-
`local -a _choices=(${items})`,
|
|
317
|
-
`COMPREPLY=()`,
|
|
318
|
-
`local _c; for _c in "\${_choices[@]}"; do [[ "$_c" == "$_cur"* ]] && COMPREPLY+=("$_c"); done`,
|
|
319
|
-
`compopt +o default 2>/dev/null`
|
|
320
|
-
];
|
|
321
|
-
}
|
|
322
|
-
case "file":
|
|
323
|
-
if (vc.matcher?.length) return bashFileFilter(vc.matcher.map((p) => `[[ "\${_f##*/}" == ${p} ]]`).join(" || "), inline);
|
|
324
|
-
if (vc.extensions?.length) return bashFileFilter(vc.extensions.map((ext) => `[[ "$_f" == *".${ext}" ]]`).join(" || "), inline);
|
|
325
|
-
if (inline) return [
|
|
326
|
-
`local -a _entries=($(compgen -f -- "$_cur"))`,
|
|
327
|
-
`COMPREPLY=("\${_entries[@]/#/$_inline_prefix}")`,
|
|
328
|
-
`compopt -o filenames`
|
|
329
|
-
];
|
|
330
|
-
return [`COMPREPLY=($(compgen -f -- "$_cur"))`, `compopt -o filenames`];
|
|
331
|
-
case "directory":
|
|
332
|
-
if (inline) return [
|
|
333
|
-
`local -a _dirs=($(compgen -d -- "$_cur"))`,
|
|
334
|
-
`COMPREPLY=("\${_dirs[@]/#/$_inline_prefix}")`,
|
|
335
|
-
`compopt -o filenames`
|
|
336
|
-
];
|
|
337
|
-
return [`COMPREPLY=($(compgen -d -- "$_cur"))`, `compopt -o filenames`];
|
|
338
|
-
case "command": {
|
|
339
|
-
const cmd = vc.shellCommand;
|
|
340
|
-
if (inline) return [`COMPREPLY=($(compgen -P "$_inline_prefix" -W "$(${cmd})" -- "$_cur"))`];
|
|
341
|
-
return [`COMPREPLY=($(compgen -W "$(${cmd})" -- "$_cur"))`];
|
|
342
|
-
}
|
|
343
|
-
case "none": return [`compopt +o default 2>/dev/null`];
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
function bashFileFilter(checks, inline) {
|
|
347
|
-
const prefix = inline ? `"\${_inline_prefix}$_f"` : `"$_f"`;
|
|
348
|
-
return [
|
|
349
|
-
`local -a _all_entries=($(compgen -f -- "$_cur"))`,
|
|
350
|
-
`for _f in "\${_all_entries[@]}"; do`,
|
|
351
|
-
` if [[ -d "$_f" ]]; then`,
|
|
352
|
-
` COMPREPLY+=(${prefix})`,
|
|
353
|
-
` elif ${checks}; then`,
|
|
354
|
-
` COMPREPLY+=(${prefix})`,
|
|
355
|
-
` fi`,
|
|
356
|
-
`done`,
|
|
357
|
-
`compopt -o filenames`,
|
|
358
|
-
`compopt +o default 2>/dev/null`
|
|
359
|
-
];
|
|
360
|
-
}
|
|
361
|
-
/** Collect value-taking option patterns for case matching */
|
|
362
|
-
function optionValueCases$2(options, inline) {
|
|
363
|
-
const lines = [];
|
|
364
|
-
for (const opt of options) {
|
|
365
|
-
if (!opt.takesValue || !opt.valueCompletion) continue;
|
|
366
|
-
const valLines = bashValueLines(opt.valueCompletion, inline);
|
|
367
|
-
if (valLines.length === 0) continue;
|
|
368
|
-
const patterns = [`--${opt.cliName}`];
|
|
369
|
-
if (opt.alias) for (const a of opt.alias) patterns.push(a.length === 1 ? `-${a}` : `--${a}`);
|
|
370
|
-
const patternStr = patterns.join("|");
|
|
371
|
-
lines.push(` ${patternStr})`);
|
|
372
|
-
for (const vl of valLines) lines.push(` ${vl}`);
|
|
373
|
-
lines.push(` return ;;`);
|
|
374
|
-
}
|
|
375
|
-
return lines;
|
|
376
|
-
}
|
|
377
|
-
/** Generate positional completion block */
|
|
378
|
-
function positionalBlock$2(positionals) {
|
|
379
|
-
if (positionals.length === 0) return [];
|
|
380
|
-
const lines = [];
|
|
381
|
-
lines.push(` case "$_pos_count" in`);
|
|
382
|
-
for (const pos of positionals) {
|
|
383
|
-
if (pos.variadic) lines.push(` ${pos.position}|*)`);
|
|
384
|
-
else lines.push(` ${pos.position})`);
|
|
385
|
-
for (const vl of bashValueLines(pos.valueCompletion, false)) lines.push(` ${vl}`);
|
|
386
|
-
lines.push(` ;;`);
|
|
387
|
-
}
|
|
388
|
-
lines.push(` esac`);
|
|
389
|
-
return lines;
|
|
390
|
-
}
|
|
391
|
-
/** Generate prev/inline value completion blocks for options */
|
|
392
|
-
function valueCompletionBlocks(options) {
|
|
393
|
-
if (!options.some((o) => o.takesValue && o.valueCompletion)) return [];
|
|
394
|
-
const lines = [];
|
|
395
|
-
const prevCases = optionValueCases$2(options, false);
|
|
396
|
-
if (prevCases.length > 0) {
|
|
397
|
-
lines.push(` if [[ -z "$_inline_prefix" ]]; then`);
|
|
398
|
-
lines.push(` case "$_prev" in`);
|
|
399
|
-
lines.push(...prevCases);
|
|
400
|
-
lines.push(` esac`);
|
|
401
|
-
lines.push(` fi`);
|
|
402
|
-
}
|
|
403
|
-
const inlineCases = optionValueCases$2(options, true);
|
|
404
|
-
if (inlineCases.length > 0) {
|
|
405
|
-
lines.push(` if [[ -n "$_inline_prefix" ]]; then`);
|
|
406
|
-
lines.push(` case "\${_inline_prefix%=}" in`);
|
|
407
|
-
lines.push(...inlineCases);
|
|
408
|
-
lines.push(` esac`);
|
|
409
|
-
lines.push(` fi`);
|
|
410
|
-
}
|
|
411
|
-
return lines;
|
|
412
|
-
}
|
|
413
|
-
/** Generate available-options list lines */
|
|
414
|
-
function availableOptionLines$2(options, fn) {
|
|
415
|
-
const lines = [];
|
|
416
|
-
for (const opt of options) if (opt.valueType === "array") lines.push(` _avail+=(--${opt.cliName})`);
|
|
417
|
-
else {
|
|
418
|
-
const patterns = [`"--${opt.cliName}"`];
|
|
419
|
-
if (opt.alias) for (const a of opt.alias) patterns.push(a.length === 1 ? `"-${a}"` : `"--${a}"`);
|
|
420
|
-
lines.push(` __${fn}_not_used ${patterns.join(" ")} && _avail+=(--${opt.cliName})`);
|
|
421
|
-
}
|
|
422
|
-
lines.push(` __${fn}_not_used "--help" && _avail+=(--help)`);
|
|
423
|
-
return lines;
|
|
424
|
-
}
|
|
425
|
-
/**
|
|
426
|
-
* Generate a per-subcommand completion function.
|
|
427
|
-
* Recursively generates functions for nested subcommands.
|
|
428
|
-
*/
|
|
429
|
-
function generateSubHandler$2(sub, fn, path) {
|
|
430
|
-
const fullPath = [...path, sub.name];
|
|
431
|
-
const funcName = `__${fn}_complete_${fullPath.map(sanitize).join("_")}`;
|
|
432
|
-
const visibleSubs = getVisibleSubs(sub.subcommands);
|
|
433
|
-
const lines = [];
|
|
434
|
-
for (const child of visibleSubs) lines.push(...generateSubHandler$2(child, fn, fullPath));
|
|
435
|
-
lines.push(`${funcName}() {`);
|
|
436
|
-
lines.push(...valueCompletionBlocks(sub.options));
|
|
437
|
-
const fullPathStr = fullPath.join(":");
|
|
438
|
-
lines.push(` if [[ -z "$_inline_prefix" ]] && __${fn}_opt_takes_value "${fullPathStr}" "$_prev"; then return; fi`);
|
|
439
|
-
lines.push(` if [[ -n "$_inline_prefix" ]] && __${fn}_opt_takes_value "${fullPathStr}" "\${_inline_prefix%=}"; then return; fi`);
|
|
440
|
-
if (sub.positionals.length > 0) {
|
|
441
|
-
lines.push(` if (( _after_dd )); then`);
|
|
442
|
-
lines.push(...positionalBlock$2(sub.positionals).map((l) => ` ${l}`));
|
|
443
|
-
lines.push(` return`);
|
|
444
|
-
lines.push(` fi`);
|
|
445
|
-
} else lines.push(` if (( _after_dd )); then return; fi`);
|
|
446
|
-
lines.push(` if [[ "$_cur" == -* ]]; then`);
|
|
447
|
-
lines.push(` local -a _avail=()`);
|
|
448
|
-
lines.push(...availableOptionLines$2(sub.options, fn));
|
|
449
|
-
lines.push(` COMPREPLY=($(compgen -W "\${_avail[*]}" -- "$_cur"))`);
|
|
450
|
-
lines.push(` compopt +o default 2>/dev/null`);
|
|
451
|
-
lines.push(` return`);
|
|
452
|
-
lines.push(` fi`);
|
|
453
|
-
if (visibleSubs.length > 0) {
|
|
454
|
-
const subNames = getSubNamesWithAliases(sub.subcommands).map((s) => s.name).join(" ");
|
|
455
|
-
lines.push(` COMPREPLY=($(compgen -W "${subNames}" -- "$_cur"))`);
|
|
456
|
-
lines.push(` compopt +o default 2>/dev/null`);
|
|
457
|
-
} else if (sub.positionals.length > 0) lines.push(...positionalBlock$2(sub.positionals));
|
|
458
|
-
lines.push(`}`);
|
|
459
|
-
lines.push(``);
|
|
460
|
-
return lines;
|
|
461
|
-
}
|
|
462
|
-
function generateBashCompletion(command, options) {
|
|
463
|
-
const { programName } = options;
|
|
464
|
-
const data = extractCompletionData(command, programName, options.globalArgsSchema);
|
|
465
|
-
const fn = sanitize(programName);
|
|
466
|
-
const root = data.command;
|
|
467
|
-
const visibleSubs = getVisibleSubs(root.subcommands);
|
|
468
|
-
const lines = [];
|
|
469
|
-
lines.push(`# Bash completion for ${programName}`);
|
|
470
|
-
lines.push(`# Generated by politty`);
|
|
471
|
-
lines.push(``);
|
|
472
|
-
lines.push(`__${fn}_not_used() {`);
|
|
473
|
-
lines.push(` for _u in "\${_used_opts[@]}"; do`);
|
|
474
|
-
lines.push(` for _chk in "$@"; do`);
|
|
475
|
-
lines.push(` [[ "$_u" == "$_chk" ]] && return 1`);
|
|
476
|
-
lines.push(` done`);
|
|
477
|
-
lines.push(` done`);
|
|
478
|
-
lines.push(` return 0`);
|
|
479
|
-
lines.push(`}`);
|
|
480
|
-
lines.push(``);
|
|
481
|
-
lines.push(`__${fn}_opt_takes_value() {`);
|
|
482
|
-
lines.push(` case "$1:$2" in`);
|
|
483
|
-
lines.push(...optTakesValueEntries(root, ""));
|
|
484
|
-
lines.push(` esac`);
|
|
485
|
-
lines.push(` return 1`);
|
|
486
|
-
lines.push(`}`);
|
|
487
|
-
lines.push(``);
|
|
488
|
-
const routeEntries = collectRouteEntries(root);
|
|
489
|
-
if (routeEntries.length > 0) {
|
|
490
|
-
lines.push(`__${fn}_is_subcmd() {`);
|
|
491
|
-
lines.push(` case "$1:$2" in`);
|
|
492
|
-
lines.push(...isSubcmdCaseLines(routeEntries));
|
|
493
|
-
lines.push(` esac`);
|
|
494
|
-
lines.push(` return 1`);
|
|
495
|
-
lines.push(`}`);
|
|
496
|
-
lines.push(``);
|
|
497
|
-
}
|
|
498
|
-
for (const sub of visibleSubs) lines.push(...generateSubHandler$2(sub, fn, []));
|
|
499
|
-
lines.push(`__${fn}_complete_root() {`);
|
|
500
|
-
lines.push(...valueCompletionBlocks(root.options));
|
|
501
|
-
lines.push(` if [[ -z "$_inline_prefix" ]] && __${fn}_opt_takes_value "" "$_prev"; then return; fi`);
|
|
502
|
-
lines.push(` if [[ -n "$_inline_prefix" ]] && __${fn}_opt_takes_value "" "\${_inline_prefix%=}"; then return; fi`);
|
|
503
|
-
if (root.positionals.length > 0) {
|
|
504
|
-
lines.push(` if (( _after_dd )); then`);
|
|
505
|
-
lines.push(...positionalBlock$2(root.positionals).map((l) => ` ${l}`));
|
|
506
|
-
lines.push(` return`);
|
|
507
|
-
lines.push(` fi`);
|
|
508
|
-
} else lines.push(` if (( _after_dd )); then return; fi`);
|
|
509
|
-
lines.push(` if [[ "$_cur" == -* ]]; then`);
|
|
510
|
-
lines.push(` local -a _avail=()`);
|
|
511
|
-
lines.push(...availableOptionLines$2(root.options, fn));
|
|
512
|
-
lines.push(` COMPREPLY=($(compgen -W "\${_avail[*]}" -- "$_cur"))`);
|
|
513
|
-
lines.push(` compopt +o default 2>/dev/null`);
|
|
514
|
-
if (visibleSubs.length > 0) {
|
|
515
|
-
lines.push(` else`);
|
|
516
|
-
const subNames = getSubNamesWithAliases(root.subcommands).map((s) => s.name).join(" ");
|
|
517
|
-
lines.push(` COMPREPLY=($(compgen -W "${subNames}" -- "$_cur"))`);
|
|
518
|
-
lines.push(` compopt +o default 2>/dev/null`);
|
|
519
|
-
} else if (root.positionals.length > 0) {
|
|
520
|
-
lines.push(` else`);
|
|
521
|
-
lines.push(...positionalBlock$2(root.positionals).map((l) => ` ${l}`));
|
|
522
|
-
}
|
|
523
|
-
lines.push(` fi`);
|
|
524
|
-
lines.push(`}`);
|
|
525
|
-
lines.push(``);
|
|
526
|
-
const subRouting = routeEntries.map((r) => ` ${r.pathStr}) __${fn}_complete_${r.funcSuffix} ;;`).join("\n");
|
|
527
|
-
lines.push(`_${fn}_completions() {`);
|
|
528
|
-
lines.push(` COMPREPLY=()`);
|
|
529
|
-
lines.push(``);
|
|
530
|
-
lines.push(` # Rejoin words split by '=' in COMP_WORDBREAKS`);
|
|
531
|
-
lines.push(` local -a _words=()`);
|
|
532
|
-
lines.push(` local _i=1`);
|
|
533
|
-
lines.push(` while (( _i <= COMP_CWORD )); do`);
|
|
534
|
-
lines.push(` if [[ "\${COMP_WORDS[_i]}" == "=" && \${#_words[@]} -gt 0 ]]; then`);
|
|
535
|
-
lines.push(` _words[\${#_words[@]}-1]+="=\${COMP_WORDS[_i+1]:-}"`);
|
|
536
|
-
lines.push(` (( _i += 2 ))`);
|
|
537
|
-
lines.push(` else`);
|
|
538
|
-
lines.push(` _words+=("\${COMP_WORDS[_i]}")`);
|
|
539
|
-
lines.push(` (( _i++ ))`);
|
|
540
|
-
lines.push(` fi`);
|
|
541
|
-
lines.push(` done`);
|
|
542
|
-
lines.push(``);
|
|
543
|
-
lines.push(` local _cur=""`);
|
|
544
|
-
lines.push(` (( \${#_words[@]} > 0 )) && _cur="\${_words[\${#_words[@]}-1]}"`);
|
|
545
|
-
lines.push(``);
|
|
546
|
-
lines.push(` local _inline_prefix=""`);
|
|
547
|
-
lines.push(` if [[ "$_cur" == --*=* ]]; then`);
|
|
548
|
-
lines.push(` _inline_prefix="\${_cur%%=*}="`);
|
|
549
|
-
lines.push(` _cur="\${_cur#*=}"`);
|
|
550
|
-
lines.push(` fi`);
|
|
551
|
-
lines.push(``);
|
|
552
|
-
lines.push(` local _prev=""`);
|
|
553
|
-
lines.push(` (( \${#_words[@]} > 1 )) && _prev="\${_words[\${#_words[@]}-2]}"`);
|
|
554
|
-
lines.push(``);
|
|
555
|
-
lines.push(` local _subcmd="" _after_dd=0 _pos_count=0 _skip_next=0`);
|
|
556
|
-
lines.push(` local -a _used_opts=()`);
|
|
557
|
-
lines.push(``);
|
|
558
|
-
lines.push(` local _j=0`);
|
|
559
|
-
lines.push(` while (( _j < \${#_words[@]} - 1 )); do`);
|
|
560
|
-
lines.push(` local _w="\${_words[_j]}"`);
|
|
561
|
-
lines.push(` if (( _skip_next )); then _skip_next=0; (( _j++ )); continue; fi`);
|
|
562
|
-
lines.push(` if [[ "$_w" == "--" ]]; then _after_dd=1; (( _j++ )); continue; fi`);
|
|
563
|
-
lines.push(` if (( _after_dd )); then (( _pos_count++ )); (( _j++ )); continue; fi`);
|
|
564
|
-
lines.push(` if [[ "$_w" == --*=* ]]; then _used_opts+=("\${_w%%=*}"); (( _j++ )); continue; fi`);
|
|
565
|
-
lines.push(` if [[ "$_w" == -* ]]; then`);
|
|
566
|
-
lines.push(` _used_opts+=("$_w")`);
|
|
567
|
-
lines.push(` __${fn}_opt_takes_value "$_subcmd" "$_w" && _skip_next=1`);
|
|
568
|
-
lines.push(` (( _j++ )); continue`);
|
|
569
|
-
lines.push(` fi`);
|
|
570
|
-
if (routeEntries.length > 0) lines.push(` if __${fn}_is_subcmd "$_subcmd" "$_w"; then _subcmd="\${_subcmd:+\${_subcmd}:}$_w"; _used_opts=(); _pos_count=0; else (( _pos_count++ )); fi`);
|
|
571
|
-
else lines.push(` (( _pos_count++ ))`);
|
|
572
|
-
lines.push(` (( _j++ ))`);
|
|
573
|
-
lines.push(` done`);
|
|
574
|
-
lines.push(``);
|
|
575
|
-
lines.push(` case "$_subcmd" in`);
|
|
576
|
-
lines.push(subRouting);
|
|
577
|
-
lines.push(` *) __${fn}_complete_root ;;`);
|
|
578
|
-
lines.push(` esac`);
|
|
579
|
-
lines.push(`}`);
|
|
580
|
-
lines.push(``);
|
|
581
|
-
lines.push(`complete -o default -F _${fn}_completions ${programName}`);
|
|
582
|
-
lines.push(``);
|
|
583
|
-
return {
|
|
584
|
-
script: lines.join("\n"),
|
|
585
|
-
shell: "bash",
|
|
586
|
-
installInstructions: `# To enable completions, add the following to your ~/.bashrc:
|
|
587
|
-
|
|
588
|
-
# Option 1: Source directly
|
|
589
|
-
eval "$(${programName} completion bash)"
|
|
590
|
-
|
|
591
|
-
# Option 2: Save to a file
|
|
592
|
-
${programName} completion bash > ~/.local/share/bash-completion/completions/${programName}
|
|
593
|
-
|
|
594
|
-
# Then reload your shell or run:
|
|
595
|
-
source ~/.bashrc`
|
|
596
|
-
};
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
//#endregion
|
|
600
|
-
//#region src/completion/dynamic/candidate-generator.ts
|
|
601
|
-
/**
|
|
602
|
-
* Generate completion candidates based on context
|
|
603
|
-
*/
|
|
604
|
-
/**
|
|
605
|
-
* Completion directive flags (bitwise)
|
|
606
|
-
*/
|
|
607
|
-
const CompletionDirective = {
|
|
608
|
-
Default: 0,
|
|
609
|
-
NoSpace: 1,
|
|
610
|
-
NoFileCompletion: 2,
|
|
611
|
-
FilterPrefix: 4,
|
|
612
|
-
KeepOrder: 8,
|
|
613
|
-
FileCompletion: 16,
|
|
614
|
-
DirectoryCompletion: 32,
|
|
615
|
-
Error: 64
|
|
616
|
-
};
|
|
617
|
-
/**
|
|
618
|
-
* Generate completion candidates based on context
|
|
619
|
-
*/
|
|
620
|
-
function generateCandidates(context) {
|
|
621
|
-
const candidates = [];
|
|
622
|
-
let directive = CompletionDirective.Default;
|
|
623
|
-
switch (context.completionType) {
|
|
624
|
-
case "subcommand": return generateSubcommandCandidates(context);
|
|
625
|
-
case "option-name": return generateOptionNameCandidates(context);
|
|
626
|
-
case "option-value": return generateOptionValueCandidates(context);
|
|
627
|
-
case "positional": return generatePositionalCandidates(context);
|
|
628
|
-
default: return {
|
|
629
|
-
candidates,
|
|
630
|
-
directive
|
|
631
|
-
};
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
/**
|
|
635
|
-
* Execute a shell command and return results as candidates
|
|
636
|
-
*/
|
|
637
|
-
function executeShellCommand(command) {
|
|
638
|
-
try {
|
|
639
|
-
return (0, node_child_process.execSync)(command, {
|
|
640
|
-
encoding: "utf-8",
|
|
641
|
-
timeout: 5e3
|
|
642
|
-
}).split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => ({
|
|
643
|
-
value: line,
|
|
644
|
-
type: "value"
|
|
645
|
-
}));
|
|
646
|
-
} catch {
|
|
647
|
-
return [];
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
/**
|
|
651
|
-
* Resolve value completion, executing shell commands and file lookups in JS
|
|
652
|
-
*/
|
|
653
|
-
function resolveValueCandidates(vc, candidates, _currentWord, description) {
|
|
654
|
-
let directive = CompletionDirective.FilterPrefix;
|
|
655
|
-
let fileExtensions;
|
|
656
|
-
let fileMatchers;
|
|
657
|
-
switch (vc.type) {
|
|
658
|
-
case "choices":
|
|
659
|
-
if (vc.choices) for (const choice of vc.choices) candidates.push({
|
|
660
|
-
value: choice,
|
|
661
|
-
description,
|
|
662
|
-
type: "value"
|
|
663
|
-
});
|
|
664
|
-
directive |= CompletionDirective.NoFileCompletion;
|
|
665
|
-
break;
|
|
666
|
-
case "file":
|
|
667
|
-
if (vc.matcher && vc.matcher.length > 0) {
|
|
668
|
-
fileMatchers = vc.matcher.filter((m) => m.trim().length > 0);
|
|
669
|
-
if (fileMatchers.length === 0) {
|
|
670
|
-
fileMatchers = void 0;
|
|
671
|
-
directive |= CompletionDirective.FileCompletion;
|
|
672
|
-
}
|
|
673
|
-
} else if (vc.extensions && vc.extensions.length > 0) {
|
|
674
|
-
fileExtensions = Array.from(new Set(vc.extensions.map((ext) => ext.trim().replace(/^\./, "")).filter((ext) => ext.length > 0)));
|
|
675
|
-
if (fileExtensions.length === 0) {
|
|
676
|
-
fileExtensions = void 0;
|
|
677
|
-
directive |= CompletionDirective.FileCompletion;
|
|
678
|
-
}
|
|
679
|
-
} else directive |= CompletionDirective.FileCompletion;
|
|
680
|
-
break;
|
|
681
|
-
case "directory":
|
|
682
|
-
directive |= CompletionDirective.DirectoryCompletion;
|
|
683
|
-
break;
|
|
684
|
-
case "command":
|
|
685
|
-
if (vc.shellCommand) candidates.push(...executeShellCommand(vc.shellCommand));
|
|
686
|
-
directive |= CompletionDirective.NoFileCompletion;
|
|
687
|
-
break;
|
|
688
|
-
case "none":
|
|
689
|
-
directive |= CompletionDirective.NoFileCompletion;
|
|
690
|
-
break;
|
|
691
|
-
}
|
|
692
|
-
return {
|
|
693
|
-
directive,
|
|
694
|
-
fileExtensions,
|
|
695
|
-
fileMatchers
|
|
696
|
-
};
|
|
697
|
-
}
|
|
698
|
-
/**
|
|
699
|
-
* Generate subcommand candidates
|
|
700
|
-
*/
|
|
701
|
-
function generateSubcommandCandidates(context) {
|
|
702
|
-
const candidates = [];
|
|
703
|
-
let directive = CompletionDirective.FilterPrefix;
|
|
704
|
-
for (const name of context.subcommands) {
|
|
705
|
-
let description;
|
|
706
|
-
const sub = context.currentCommand.subCommands?.[name];
|
|
707
|
-
if (sub) description = require_subcommand_router.resolveSubCommandMeta(sub)?.description;
|
|
708
|
-
else {
|
|
709
|
-
const canonical = require_subcommand_router.resolveSubCommandAlias(context.currentCommand, name);
|
|
710
|
-
if (canonical) {
|
|
711
|
-
const resolved = context.currentCommand.subCommands?.[canonical];
|
|
712
|
-
if (resolved) description = require_subcommand_router.resolveSubCommandMeta(resolved)?.description;
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
candidates.push({
|
|
716
|
-
value: name,
|
|
717
|
-
description,
|
|
718
|
-
type: "subcommand"
|
|
719
|
-
});
|
|
720
|
-
}
|
|
721
|
-
if (candidates.length === 0 || context.currentWord.startsWith("-")) {
|
|
722
|
-
const optionResult = generateOptionNameCandidates(context);
|
|
723
|
-
candidates.push(...optionResult.candidates);
|
|
724
|
-
}
|
|
725
|
-
return {
|
|
726
|
-
candidates,
|
|
727
|
-
directive
|
|
728
|
-
};
|
|
729
|
-
}
|
|
730
|
-
/**
|
|
731
|
-
* Generate option name candidates
|
|
732
|
-
*/
|
|
733
|
-
function generateOptionNameCandidates(context) {
|
|
734
|
-
const candidates = [];
|
|
735
|
-
const directive = CompletionDirective.FilterPrefix;
|
|
736
|
-
const availableOptions = context.options.filter((opt) => {
|
|
737
|
-
if (opt.valueType === "array") return true;
|
|
738
|
-
if (context.usedOptions.has(opt.cliName)) return false;
|
|
739
|
-
if (opt.alias && opt.alias.some((a) => context.usedOptions.has(a))) return false;
|
|
740
|
-
return true;
|
|
741
|
-
});
|
|
742
|
-
for (const opt of availableOptions) candidates.push({
|
|
743
|
-
value: `--${opt.cliName}`,
|
|
744
|
-
description: opt.description,
|
|
745
|
-
type: "option"
|
|
746
|
-
});
|
|
747
|
-
if (!context.usedOptions.has("help")) candidates.push({
|
|
748
|
-
value: "--help",
|
|
749
|
-
description: "Show help information",
|
|
750
|
-
type: "option"
|
|
751
|
-
});
|
|
752
|
-
return {
|
|
753
|
-
candidates,
|
|
754
|
-
directive
|
|
755
|
-
};
|
|
756
|
-
}
|
|
757
|
-
/**
|
|
758
|
-
* Generate option value candidates
|
|
759
|
-
*/
|
|
760
|
-
function generateOptionValueCandidates(context) {
|
|
761
|
-
const candidates = [];
|
|
762
|
-
if (!context.targetOption) return {
|
|
763
|
-
candidates,
|
|
764
|
-
directive: CompletionDirective.FilterPrefix
|
|
765
|
-
};
|
|
766
|
-
const vc = context.targetOption.valueCompletion;
|
|
767
|
-
if (!vc) return {
|
|
768
|
-
candidates,
|
|
769
|
-
directive: CompletionDirective.FilterPrefix
|
|
770
|
-
};
|
|
771
|
-
return {
|
|
772
|
-
candidates,
|
|
773
|
-
...resolveValueCandidates(vc, candidates, context.currentWord)
|
|
774
|
-
};
|
|
775
|
-
}
|
|
776
|
-
/**
|
|
777
|
-
* Generate positional argument candidates
|
|
778
|
-
*/
|
|
779
|
-
function generatePositionalCandidates(context) {
|
|
780
|
-
const candidates = [];
|
|
781
|
-
const positionalIndex = context.positionalIndex ?? 0;
|
|
782
|
-
const positional = context.positionals[positionalIndex] ?? (context.positionals.at(-1)?.variadic ? context.positionals.at(-1) : void 0);
|
|
783
|
-
if (!positional) return {
|
|
784
|
-
candidates,
|
|
785
|
-
directive: CompletionDirective.FilterPrefix
|
|
786
|
-
};
|
|
787
|
-
const vc = positional.valueCompletion;
|
|
788
|
-
if (!vc) return {
|
|
789
|
-
candidates,
|
|
790
|
-
directive: CompletionDirective.FilterPrefix
|
|
791
|
-
};
|
|
792
|
-
return {
|
|
793
|
-
candidates,
|
|
794
|
-
...resolveValueCandidates(vc, candidates, context.currentWord, positional.description)
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
//#endregion
|
|
799
|
-
//#region src/completion/dynamic/context-parser.ts
|
|
800
|
-
/**
|
|
801
|
-
* Parse completion context from partial command line
|
|
802
|
-
*/
|
|
803
|
-
/**
|
|
804
|
-
* Extract options from a command
|
|
805
|
-
*/
|
|
806
|
-
function extractOptions(command) {
|
|
807
|
-
if (!command.args) return [];
|
|
808
|
-
return require_subcommand_router.extractFields(command.args).fields.filter((field) => !field.positional).map((field) => ({
|
|
809
|
-
name: field.name,
|
|
810
|
-
cliName: field.cliName,
|
|
811
|
-
alias: field.alias,
|
|
812
|
-
description: field.description,
|
|
813
|
-
takesValue: field.type !== "boolean",
|
|
814
|
-
valueType: field.type,
|
|
815
|
-
required: field.required,
|
|
816
|
-
valueCompletion: resolveValueCompletion(field)
|
|
817
|
-
}));
|
|
818
|
-
}
|
|
819
|
-
/**
|
|
820
|
-
* Extract positionals from a command
|
|
821
|
-
*/
|
|
822
|
-
function extractPositionalsForContext(command) {
|
|
823
|
-
if (!command.args) return [];
|
|
824
|
-
return require_subcommand_router.extractFields(command.args).fields.filter((field) => field.positional).map((field, index) => ({
|
|
825
|
-
name: field.name,
|
|
826
|
-
cliName: field.cliName,
|
|
827
|
-
position: index,
|
|
828
|
-
description: field.description,
|
|
829
|
-
required: field.required,
|
|
830
|
-
variadic: field.type === "array",
|
|
831
|
-
valueCompletion: resolveValueCompletion(field)
|
|
832
|
-
}));
|
|
833
|
-
}
|
|
834
|
-
/**
|
|
835
|
-
* Get subcommand names from a command (including aliases)
|
|
836
|
-
*/
|
|
837
|
-
function getSubcommandNames(command) {
|
|
838
|
-
if (!command.subCommands) return [];
|
|
839
|
-
const names = [];
|
|
840
|
-
for (const [name, subCmd] of Object.entries(command.subCommands)) {
|
|
841
|
-
if (name.startsWith("__")) continue;
|
|
842
|
-
names.push(name);
|
|
843
|
-
const meta = require_subcommand_router.resolveSubCommandMeta(subCmd);
|
|
844
|
-
if (meta?.aliases) names.push(...meta.aliases);
|
|
845
|
-
}
|
|
846
|
-
return names;
|
|
847
|
-
}
|
|
848
|
-
/**
|
|
849
|
-
* Resolve subcommand by name (including alias lookup)
|
|
850
|
-
*/
|
|
851
|
-
function resolveSubcommand(command, name) {
|
|
852
|
-
if (!command.subCommands) return null;
|
|
853
|
-
const sub = command.subCommands[name];
|
|
854
|
-
if (sub) return require_subcommand_router.resolveSubCommandMeta(sub);
|
|
855
|
-
const canonical = require_subcommand_router.resolveSubCommandAlias(command, name);
|
|
856
|
-
if (canonical) return require_subcommand_router.resolveSubCommandMeta(command.subCommands[canonical]);
|
|
857
|
-
return null;
|
|
858
|
-
}
|
|
859
|
-
/**
|
|
860
|
-
* Check if a word is an option (starts with - or --)
|
|
861
|
-
*/
|
|
862
|
-
function isOption(word) {
|
|
863
|
-
return word.startsWith("-");
|
|
864
|
-
}
|
|
865
|
-
/**
|
|
866
|
-
* Parse option name from word (e.g., "--foo=bar" -> "foo", "-v" -> "v")
|
|
867
|
-
*/
|
|
868
|
-
function parseOptionName(word) {
|
|
869
|
-
if (word.startsWith("--")) {
|
|
870
|
-
const withoutPrefix = word.slice(2);
|
|
871
|
-
const eqIndex = withoutPrefix.indexOf("=");
|
|
872
|
-
return eqIndex >= 0 ? withoutPrefix.slice(0, eqIndex) : withoutPrefix;
|
|
873
|
-
}
|
|
874
|
-
if (word.startsWith("-")) return word.slice(1, 2);
|
|
875
|
-
return word;
|
|
876
|
-
}
|
|
877
|
-
/**
|
|
878
|
-
* Check if option has inline value (e.g., "--foo=bar")
|
|
879
|
-
*/
|
|
880
|
-
function hasInlineValue(word) {
|
|
881
|
-
return word.includes("=");
|
|
882
|
-
}
|
|
883
|
-
/**
|
|
884
|
-
* Find option by name or alias
|
|
885
|
-
*/
|
|
886
|
-
function findOption(options, nameOrAlias) {
|
|
887
|
-
return options.find((opt) => {
|
|
888
|
-
if (opt.cliName === nameOrAlias) return true;
|
|
889
|
-
if (opt.alias?.includes(nameOrAlias)) return true;
|
|
890
|
-
if (nameOrAlias.length > 1) {
|
|
891
|
-
if (opt.cliName.includes("-") && require_subcommand_router.toCamelCase(opt.cliName) === nameOrAlias) return true;
|
|
892
|
-
if (opt.alias?.some((a) => a.includes("-") && require_subcommand_router.toCamelCase(a) === nameOrAlias)) return true;
|
|
893
|
-
}
|
|
894
|
-
return false;
|
|
895
|
-
});
|
|
896
|
-
}
|
|
897
|
-
/**
|
|
898
|
-
* Parse completion context from command line arguments
|
|
899
|
-
*
|
|
900
|
-
* @param argv - Arguments after the program name (e.g., ["build", "--fo"])
|
|
901
|
-
* @param rootCommand - The root command
|
|
902
|
-
* @returns Completion context
|
|
903
|
-
*/
|
|
904
|
-
function parseCompletionContext(argv, rootCommand) {
|
|
905
|
-
let currentCommand = rootCommand;
|
|
906
|
-
const subcommandPath = [];
|
|
907
|
-
const usedOptions = /* @__PURE__ */ new Set();
|
|
908
|
-
let positionalCount = 0;
|
|
909
|
-
let i = 0;
|
|
910
|
-
let options = extractOptions(currentCommand);
|
|
911
|
-
let afterDoubleDash = false;
|
|
912
|
-
while (i < argv.length - 1) {
|
|
913
|
-
const word = argv[i];
|
|
914
|
-
if (!afterDoubleDash && word === "--") {
|
|
915
|
-
afterDoubleDash = true;
|
|
916
|
-
i++;
|
|
917
|
-
continue;
|
|
918
|
-
}
|
|
919
|
-
if (!afterDoubleDash && isOption(word)) {
|
|
920
|
-
const optName = parseOptionName(word);
|
|
921
|
-
const opt = findOption(options, optName);
|
|
922
|
-
if (opt) {
|
|
923
|
-
usedOptions.add(opt.cliName);
|
|
924
|
-
if (opt.alias) for (const a of opt.alias) usedOptions.add(a);
|
|
925
|
-
if (opt.takesValue && !hasInlineValue(word)) i++;
|
|
926
|
-
}
|
|
927
|
-
i++;
|
|
928
|
-
continue;
|
|
929
|
-
}
|
|
930
|
-
const subcommand = afterDoubleDash ? null : resolveSubcommand(currentCommand, word);
|
|
931
|
-
if (subcommand) {
|
|
932
|
-
subcommandPath.push(word);
|
|
933
|
-
currentCommand = subcommand;
|
|
934
|
-
options = extractOptions(currentCommand);
|
|
935
|
-
usedOptions.clear();
|
|
936
|
-
positionalCount = 0;
|
|
937
|
-
i++;
|
|
938
|
-
continue;
|
|
939
|
-
}
|
|
940
|
-
positionalCount++;
|
|
941
|
-
i++;
|
|
942
|
-
}
|
|
943
|
-
const currentWord = argv[argv.length - 1] ?? "";
|
|
944
|
-
const previousWord = argv[argv.length - 2] ?? "";
|
|
945
|
-
const positionals = extractPositionalsForContext(currentCommand);
|
|
946
|
-
const subcommands = getSubcommandNames(currentCommand);
|
|
947
|
-
let completionType;
|
|
948
|
-
let targetOption;
|
|
949
|
-
let positionalIndex;
|
|
950
|
-
if (!afterDoubleDash && previousWord && isOption(previousWord) && !hasInlineValue(previousWord)) {
|
|
951
|
-
const optName = parseOptionName(previousWord);
|
|
952
|
-
const opt = findOption(options, optName);
|
|
953
|
-
if (opt && opt.takesValue) {
|
|
954
|
-
completionType = "option-value";
|
|
955
|
-
targetOption = opt;
|
|
956
|
-
} else if (currentWord.startsWith("-")) completionType = "option-name";
|
|
957
|
-
else {
|
|
958
|
-
completionType = determineDefaultCompletionType(currentWord, subcommands, positionals, positionalCount);
|
|
959
|
-
if (completionType === "positional") positionalIndex = positionalCount;
|
|
960
|
-
}
|
|
961
|
-
} else if (!afterDoubleDash && currentWord.startsWith("--") && hasInlineValue(currentWord)) {
|
|
962
|
-
const optName = parseOptionName(currentWord);
|
|
963
|
-
const opt = findOption(options, optName);
|
|
964
|
-
if (opt && opt.takesValue) {
|
|
965
|
-
completionType = "option-value";
|
|
966
|
-
targetOption = opt;
|
|
967
|
-
} else completionType = "option-name";
|
|
968
|
-
} else if (!afterDoubleDash && currentWord.startsWith("-")) completionType = "option-name";
|
|
969
|
-
else {
|
|
970
|
-
completionType = determineDefaultCompletionType(currentWord, subcommands, positionals, positionalCount, afterDoubleDash);
|
|
971
|
-
if (completionType === "positional") positionalIndex = positionalCount;
|
|
972
|
-
}
|
|
973
|
-
return {
|
|
974
|
-
subcommandPath,
|
|
975
|
-
currentCommand,
|
|
976
|
-
currentWord,
|
|
977
|
-
previousWord,
|
|
978
|
-
completionType,
|
|
979
|
-
targetOption,
|
|
980
|
-
positionalIndex,
|
|
981
|
-
options,
|
|
982
|
-
subcommands,
|
|
983
|
-
positionals,
|
|
984
|
-
usedOptions,
|
|
985
|
-
providedPositionalCount: positionalCount
|
|
986
|
-
};
|
|
987
|
-
}
|
|
988
|
-
/**
|
|
989
|
-
* Determine default completion type when not completing an option
|
|
990
|
-
*/
|
|
991
|
-
function determineDefaultCompletionType(currentWord, subcommands, positionals, positionalCount, afterDoubleDash) {
|
|
992
|
-
if (afterDoubleDash) return "positional";
|
|
993
|
-
if (subcommands.length > 0) {
|
|
994
|
-
if (subcommands.filter((s) => s.startsWith(currentWord)).length > 0 || currentWord === "") return "subcommand";
|
|
995
|
-
}
|
|
996
|
-
if (positionalCount < positionals.length) return "positional";
|
|
997
|
-
if (positionals.length > 0 && positionals[positionals.length - 1].variadic) return "positional";
|
|
998
|
-
return "subcommand";
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
//#endregion
|
|
1002
|
-
//#region src/completion/dynamic/shell-formatter.ts
|
|
1003
|
-
/**
|
|
1004
|
-
* Format completion candidates for the specified shell
|
|
1005
|
-
*
|
|
1006
|
-
* @returns Shell-ready output string (lines separated by newline, last line is :directive)
|
|
1007
|
-
*/
|
|
1008
|
-
function formatForShell(result, options) {
|
|
1009
|
-
switch (options.shell) {
|
|
1010
|
-
case "bash": return formatForBash(result, options);
|
|
1011
|
-
case "zsh": return formatForZsh(result, options);
|
|
1012
|
-
case "fish": return formatForFish(result, options);
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
/**
|
|
1016
|
-
* Check if the FilterPrefix directive is set
|
|
1017
|
-
*/
|
|
1018
|
-
function shouldFilterPrefix(directive) {
|
|
1019
|
-
return (directive & CompletionDirective.FilterPrefix) !== 0;
|
|
1020
|
-
}
|
|
1021
|
-
/**
|
|
1022
|
-
* Filter candidates by prefix
|
|
1023
|
-
*/
|
|
1024
|
-
function filterByPrefix(candidates, prefix) {
|
|
1025
|
-
if (!prefix) return candidates;
|
|
1026
|
-
return candidates.filter((c) => c.value.startsWith(prefix));
|
|
1027
|
-
}
|
|
1028
|
-
/**
|
|
1029
|
-
* Append extension metadata and directive to output lines
|
|
1030
|
-
*/
|
|
1031
|
-
function appendMetadata(lines, result) {
|
|
1032
|
-
if (result.fileExtensions && result.fileExtensions.length > 0) lines.push(`@ext:${result.fileExtensions.join(",")}`);
|
|
1033
|
-
if (result.fileMatchers && result.fileMatchers.length > 0) lines.push(`@matcher:${result.fileMatchers.join(",")}`);
|
|
1034
|
-
lines.push(`:${result.directive}`);
|
|
1035
|
-
}
|
|
1036
|
-
/**
|
|
1037
|
-
* Format for bash
|
|
1038
|
-
*
|
|
1039
|
-
* - Pre-filters candidates by currentWord prefix (replaces compgen -W)
|
|
1040
|
-
* - Handles --opt=value inline values by prepending prefix
|
|
1041
|
-
* - Outputs plain values only (no descriptions - bash COMPREPLY doesn't support them)
|
|
1042
|
-
* - Last line: :directive
|
|
1043
|
-
*/
|
|
1044
|
-
function formatForBash(result, options) {
|
|
1045
|
-
let { candidates } = result;
|
|
1046
|
-
if (shouldFilterPrefix(result.directive)) candidates = filterByPrefix(candidates, options.currentWord);
|
|
1047
|
-
const lines = candidates.map((c) => {
|
|
1048
|
-
if (options.inlinePrefix) return `${options.inlinePrefix}${c.value}`;
|
|
1049
|
-
return c.value;
|
|
1050
|
-
});
|
|
1051
|
-
appendMetadata(lines, result);
|
|
1052
|
-
return lines.join("\n");
|
|
1053
|
-
}
|
|
1054
|
-
/**
|
|
1055
|
-
* Format for zsh
|
|
1056
|
-
*
|
|
1057
|
-
* - Outputs value:description pairs for _describe
|
|
1058
|
-
* - Colons in values/descriptions are escaped with backslash
|
|
1059
|
-
* - Last line: :directive
|
|
1060
|
-
*/
|
|
1061
|
-
function formatForZsh(result, _options) {
|
|
1062
|
-
const lines = result.candidates.map((c) => {
|
|
1063
|
-
const escapedValue = c.value.replace(/:/g, "\\:");
|
|
1064
|
-
if (c.description) return `${escapedValue}:${c.description.replace(/:/g, "\\:")}`;
|
|
1065
|
-
return escapedValue;
|
|
1066
|
-
});
|
|
1067
|
-
appendMetadata(lines, result);
|
|
1068
|
-
return lines.join("\n");
|
|
1069
|
-
}
|
|
1070
|
-
/**
|
|
1071
|
-
* Format for fish
|
|
1072
|
-
*
|
|
1073
|
-
* - Outputs value\tdescription pairs
|
|
1074
|
-
* - Last line: :directive
|
|
1075
|
-
*/
|
|
1076
|
-
function formatForFish(result, _options) {
|
|
1077
|
-
const lines = result.candidates.map((c) => {
|
|
1078
|
-
if (c.description) return `${c.value}\t${c.description}`;
|
|
1079
|
-
return c.value;
|
|
1080
|
-
});
|
|
1081
|
-
appendMetadata(lines, result);
|
|
1082
|
-
return lines.join("\n");
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
//#endregion
|
|
1086
|
-
//#region src/completion/dynamic/complete-command.ts
|
|
1087
|
-
/**
|
|
1088
|
-
* Dynamic completion command implementation
|
|
1089
|
-
*
|
|
1090
|
-
* This creates a hidden `__complete` command that outputs completion candidates
|
|
1091
|
-
* for shell scripts to consume. Usage:
|
|
1092
|
-
*
|
|
1093
|
-
* mycli __complete --shell bash -- build --fo
|
|
1094
|
-
* mycli __complete --shell zsh -- plugin add
|
|
1095
|
-
*
|
|
1096
|
-
* Output format depends on the target shell:
|
|
1097
|
-
* bash: plain values (pre-filtered by prefix), last line :directive
|
|
1098
|
-
* zsh: value:description pairs, last line :directive
|
|
1099
|
-
* fish: value\tdescription pairs, last line :directive
|
|
1100
|
-
*/
|
|
1101
|
-
/**
|
|
1102
|
-
* Detect inline option-value prefix (e.g., "--format=" from "--format=json")
|
|
1103
|
-
*/
|
|
1104
|
-
function detectInlinePrefix(currentWord) {
|
|
1105
|
-
if (currentWord.startsWith("--") && currentWord.includes("=")) return currentWord.slice(0, currentWord.indexOf("=") + 1);
|
|
1106
|
-
}
|
|
1107
|
-
/**
|
|
1108
|
-
* Schema for the __complete command
|
|
1109
|
-
*/
|
|
1110
|
-
const completeArgsSchema = zod.z.object({
|
|
1111
|
-
shell: require_subcommand_router.arg(zod.z.enum([
|
|
1112
|
-
"bash",
|
|
1113
|
-
"zsh",
|
|
1114
|
-
"fish"
|
|
1115
|
-
]), { description: "Target shell for output formatting" }),
|
|
1116
|
-
args: require_subcommand_router.arg(zod.z.array(zod.z.string()).default([]), {
|
|
1117
|
-
positional: true,
|
|
1118
|
-
description: "Arguments to complete",
|
|
1119
|
-
variadic: true
|
|
1120
|
-
})
|
|
1121
|
-
});
|
|
1122
|
-
/**
|
|
1123
|
-
* Create the dynamic completion command
|
|
1124
|
-
*
|
|
1125
|
-
* @param rootCommand - The root command to generate completions for
|
|
1126
|
-
* @param programName - The program name (optional, defaults to rootCommand.name)
|
|
1127
|
-
* @returns A command that outputs completion candidates
|
|
1128
|
-
*/
|
|
1129
|
-
function createDynamicCompleteCommand(rootCommand, _programName) {
|
|
1130
|
-
return defineCommand({
|
|
1131
|
-
name: "__complete",
|
|
1132
|
-
args: completeArgsSchema,
|
|
1133
|
-
run(args) {
|
|
1134
|
-
const context = parseCompletionContext(args.args, rootCommand);
|
|
1135
|
-
const result = generateCandidates(context);
|
|
1136
|
-
const inlinePrefix = detectInlinePrefix(context.currentWord);
|
|
1137
|
-
const output = formatForShell(result, {
|
|
1138
|
-
shell: args.shell,
|
|
1139
|
-
currentWord: inlinePrefix ? context.currentWord.slice(inlinePrefix.length) : context.currentWord,
|
|
1140
|
-
inlinePrefix
|
|
1141
|
-
});
|
|
1142
|
-
console.log(output);
|
|
1143
|
-
}
|
|
1144
|
-
});
|
|
1145
|
-
}
|
|
1146
|
-
/**
|
|
1147
|
-
* Check if a command tree contains the __complete command
|
|
1148
|
-
*/
|
|
1149
|
-
function hasCompleteCommand(command) {
|
|
1150
|
-
return Boolean(command.subCommands?.["__complete"]);
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
//#endregion
|
|
1154
|
-
//#region src/completion/fish.ts
|
|
1155
|
-
/** Escape shell-special characters for fish double-quoted strings */
|
|
1156
|
-
function escapeDesc$1(s) {
|
|
1157
|
-
return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$");
|
|
1158
|
-
}
|
|
1159
|
-
/**
|
|
1160
|
-
* Generate fish value completion lines for a ValueCompletion spec.
|
|
1161
|
-
* Each line outputs candidates via echo (tab-separated value\tdescription).
|
|
1162
|
-
*/
|
|
1163
|
-
function fishValueLines(vc) {
|
|
1164
|
-
if (!vc) return [];
|
|
1165
|
-
switch (vc.type) {
|
|
1166
|
-
case "choices": return vc.choices.map((c) => `echo "${escapeDesc$1(c)}"`);
|
|
1167
|
-
case "file":
|
|
1168
|
-
if (vc.matcher?.length) return fishMatcherLines(vc.matcher);
|
|
1169
|
-
if (vc.extensions?.length) return fishExtensionLines(vc.extensions);
|
|
1170
|
-
return [`__fish_complete_path "$_cur"`];
|
|
1171
|
-
case "directory": return [`__fish_complete_directories "$_cur"`];
|
|
1172
|
-
case "command": return [
|
|
1173
|
-
`for _v in (${vc.shellCommand})`,
|
|
1174
|
-
` echo "$_v"`,
|
|
1175
|
-
`end`
|
|
1176
|
-
];
|
|
1177
|
-
case "none": return [];
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
/** Generate fish matcher-filtered file completion */
|
|
1181
|
-
function fishMatcherLines(patterns) {
|
|
1182
|
-
return [
|
|
1183
|
-
`__fish_complete_directories "$_cur"`,
|
|
1184
|
-
`set -l _dir ""`,
|
|
1185
|
-
`if string match -q '*/*' "$_cur"`,
|
|
1186
|
-
` set _dir (string replace -r '[^/]*$' '' "$_cur")`,
|
|
1187
|
-
`end`,
|
|
1188
|
-
...patterns.flatMap((p) => [
|
|
1189
|
-
`for _f in "$_dir"${p}`,
|
|
1190
|
-
` test -f "$_f"; and string match -q "$_cur*" "$_f"; and echo "$_f"`,
|
|
1191
|
-
`end`
|
|
1192
|
-
])
|
|
1193
|
-
];
|
|
1194
|
-
}
|
|
1195
|
-
/** Generate fish extension-filtered file completion */
|
|
1196
|
-
function fishExtensionLines(extensions) {
|
|
1197
|
-
const lines = [];
|
|
1198
|
-
lines.push(`__fish_complete_directories "$_cur"`);
|
|
1199
|
-
for (const ext of extensions) {
|
|
1200
|
-
lines.push(`for _f in "$_cur"*.${ext}`);
|
|
1201
|
-
lines.push(` test -f "$_f"; and echo "$_f"`);
|
|
1202
|
-
lines.push(`end`);
|
|
1203
|
-
}
|
|
1204
|
-
return lines;
|
|
1205
|
-
}
|
|
1206
|
-
/** Generate option-value switch cases for fish */
|
|
1207
|
-
function optionValueCases$1(options) {
|
|
1208
|
-
const lines = [];
|
|
1209
|
-
for (const opt of options) {
|
|
1210
|
-
if (!opt.takesValue || !opt.valueCompletion) continue;
|
|
1211
|
-
const valLines = fishValueLines(opt.valueCompletion);
|
|
1212
|
-
if (valLines.length === 0) continue;
|
|
1213
|
-
const conditions = [`test "$_prev" = "--${opt.cliName}"`];
|
|
1214
|
-
if (opt.alias) for (const a of opt.alias) conditions.push(`test "$_prev" = "${a.length === 1 ? `-${a}` : `--${a}`}"`);
|
|
1215
|
-
const cond = conditions.join("; or ");
|
|
1216
|
-
lines.push(` if ${cond}`);
|
|
1217
|
-
for (const vl of valLines) lines.push(` ${vl}`);
|
|
1218
|
-
lines.push(` return`);
|
|
1219
|
-
lines.push(` end`);
|
|
1220
|
-
}
|
|
1221
|
-
return lines;
|
|
1222
|
-
}
|
|
1223
|
-
/** Generate positional completion block for fish */
|
|
1224
|
-
function positionalBlock$1(positionals) {
|
|
1225
|
-
if (positionals.length === 0) return [];
|
|
1226
|
-
const lines = [];
|
|
1227
|
-
for (const pos of positionals) {
|
|
1228
|
-
const valLines = fishValueLines(pos.valueCompletion);
|
|
1229
|
-
if (valLines.length === 0) continue;
|
|
1230
|
-
if (pos.variadic) lines.push(` if test $_pos_count -ge ${pos.position}`);
|
|
1231
|
-
else lines.push(` if test $_pos_count -eq ${pos.position}`);
|
|
1232
|
-
for (const vl of valLines) lines.push(` ${vl}`);
|
|
1233
|
-
lines.push(` return`);
|
|
1234
|
-
lines.push(` end`);
|
|
1235
|
-
}
|
|
1236
|
-
return lines;
|
|
1237
|
-
}
|
|
1238
|
-
/** Generate available-option echo lines for fish */
|
|
1239
|
-
function availableOptionLines$1(options, fn) {
|
|
1240
|
-
const lines = [];
|
|
1241
|
-
for (const opt of options) {
|
|
1242
|
-
const desc = escapeDesc$1(opt.description ?? "");
|
|
1243
|
-
if (opt.valueType === "array") lines.push(` echo "--${opt.cliName}\t${desc}"`);
|
|
1244
|
-
else {
|
|
1245
|
-
const checks = [`"--${opt.cliName}"`];
|
|
1246
|
-
if (opt.alias) for (const a of opt.alias) checks.push(a.length === 1 ? `"-${a}"` : `"--${a}"`);
|
|
1247
|
-
lines.push(` __${fn}_not_used ${checks.join(" ")}; and echo "--${opt.cliName}\t${desc}"`);
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
lines.push(` __${fn}_not_used "--help"; and echo "--help\tShow help"`);
|
|
1251
|
-
return lines;
|
|
1252
|
-
}
|
|
1253
|
-
/** Generate value-option completion block if any value-taking options exist */
|
|
1254
|
-
function valueCompletionBlock$1(options) {
|
|
1255
|
-
if (!options.some((o) => o.takesValue && o.valueCompletion)) return [];
|
|
1256
|
-
return optionValueCases$1(options);
|
|
1257
|
-
}
|
|
1258
|
-
/**
|
|
1259
|
-
* Generate a per-subcommand completion function for fish.
|
|
1260
|
-
* Recursively generates functions for nested subcommands.
|
|
1261
|
-
*/
|
|
1262
|
-
function generateSubHandler$1(sub, fn, path) {
|
|
1263
|
-
const fullPath = [...path, sub.name];
|
|
1264
|
-
const funcName = `__${fn}_complete_${fullPath.map(sanitize).join("_")}`;
|
|
1265
|
-
const visibleSubs = getVisibleSubs(sub.subcommands);
|
|
1266
|
-
const lines = [];
|
|
1267
|
-
for (const child of visibleSubs) lines.push(...generateSubHandler$1(child, fn, fullPath));
|
|
1268
|
-
lines.push(`function ${funcName} --no-scope-shadowing`);
|
|
1269
|
-
lines.push(...valueCompletionBlock$1(sub.options));
|
|
1270
|
-
const fullPathStr = fullPath.join(":");
|
|
1271
|
-
lines.push(` if __${fn}_opt_takes_value "${fullPathStr}" "$_prev"; return; end`);
|
|
1272
|
-
if (sub.positionals.length > 0) {
|
|
1273
|
-
lines.push(` if test $_after_dd -eq 1`);
|
|
1274
|
-
lines.push(...positionalBlock$1(sub.positionals).map((l) => ` ${l}`));
|
|
1275
|
-
lines.push(` return`);
|
|
1276
|
-
lines.push(` end`);
|
|
1277
|
-
} else lines.push(` if test $_after_dd -eq 1; return; end`);
|
|
1278
|
-
lines.push(` if string match -q -- '-*' "$_cur"`);
|
|
1279
|
-
lines.push(...availableOptionLines$1(sub.options, fn));
|
|
1280
|
-
lines.push(` return`);
|
|
1281
|
-
lines.push(` end`);
|
|
1282
|
-
if (visibleSubs.length > 0) for (const s of getSubNamesWithAliases(sub.subcommands)) {
|
|
1283
|
-
const desc = escapeDesc$1(s.description ?? "");
|
|
1284
|
-
lines.push(` echo "${s.name}\t${desc}"`);
|
|
1285
|
-
}
|
|
1286
|
-
else if (sub.positionals.length > 0) lines.push(...positionalBlock$1(sub.positionals));
|
|
1287
|
-
lines.push(`end`);
|
|
1288
|
-
lines.push(``);
|
|
1289
|
-
return lines;
|
|
1290
|
-
}
|
|
1291
|
-
/** Generate opt-takes-value entries for fish switch cases */
|
|
1292
|
-
function optTakesValueCases(sub, parentPath) {
|
|
1293
|
-
const lines = [];
|
|
1294
|
-
for (const opt of sub.options) if (opt.takesValue) {
|
|
1295
|
-
const patterns = [`"${parentPath}:--${opt.cliName}"`];
|
|
1296
|
-
if (opt.alias) for (const a of opt.alias) patterns.push(`"${parentPath}:${a.length === 1 ? `-${a}` : `--${a}`}"`);
|
|
1297
|
-
lines.push(` case ${patterns.join(" ")}`);
|
|
1298
|
-
lines.push(` return 0`);
|
|
1299
|
-
}
|
|
1300
|
-
for (const child of getVisibleSubs(sub.subcommands)) {
|
|
1301
|
-
const childPath = parentPath ? `${parentPath}:${child.name}` : child.name;
|
|
1302
|
-
lines.push(...optTakesValueCases(child, childPath));
|
|
1303
|
-
if (child.aliases) for (const alias of child.aliases) {
|
|
1304
|
-
const aliasPath = parentPath ? `${parentPath}:${alias}` : alias;
|
|
1305
|
-
lines.push(...optTakesValueCases(child, aliasPath));
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
return lines;
|
|
1309
|
-
}
|
|
1310
|
-
function generateFishCompletion(command, options) {
|
|
1311
|
-
const { programName } = options;
|
|
1312
|
-
const data = extractCompletionData(command, programName, options.globalArgsSchema);
|
|
1313
|
-
const fn = sanitize(programName);
|
|
1314
|
-
const root = data.command;
|
|
1315
|
-
const visibleSubs = getVisibleSubs(root.subcommands);
|
|
1316
|
-
const lines = [];
|
|
1317
|
-
lines.push(`# Fish completion for ${programName}`);
|
|
1318
|
-
lines.push(`# Generated by politty`);
|
|
1319
|
-
lines.push(``);
|
|
1320
|
-
lines.push(`function __${fn}_not_used --no-scope-shadowing`);
|
|
1321
|
-
lines.push(` for _chk in $argv`);
|
|
1322
|
-
lines.push(` if contains -- "$_chk" $_used_opts`);
|
|
1323
|
-
lines.push(` return 1`);
|
|
1324
|
-
lines.push(` end`);
|
|
1325
|
-
lines.push(` end`);
|
|
1326
|
-
lines.push(` return 0`);
|
|
1327
|
-
lines.push(`end`);
|
|
1328
|
-
lines.push(``);
|
|
1329
|
-
lines.push(`function __${fn}_opt_takes_value`);
|
|
1330
|
-
lines.push(` switch "$argv[1]:$argv[2]"`);
|
|
1331
|
-
lines.push(...optTakesValueCases(root, ""));
|
|
1332
|
-
lines.push(` end`);
|
|
1333
|
-
lines.push(` return 1`);
|
|
1334
|
-
lines.push(`end`);
|
|
1335
|
-
lines.push(``);
|
|
1336
|
-
const routeEntries = collectRouteEntries(root);
|
|
1337
|
-
if (routeEntries.length > 0) {
|
|
1338
|
-
lines.push(`function __${fn}_is_subcmd`);
|
|
1339
|
-
lines.push(` switch "$argv[1]:$argv[2]"`);
|
|
1340
|
-
for (const r of routeEntries) {
|
|
1341
|
-
lines.push(` case "${r.lookupPattern}"`);
|
|
1342
|
-
lines.push(` return 0`);
|
|
1343
|
-
}
|
|
1344
|
-
lines.push(` end`);
|
|
1345
|
-
lines.push(` return 1`);
|
|
1346
|
-
lines.push(`end`);
|
|
1347
|
-
lines.push(``);
|
|
1348
|
-
}
|
|
1349
|
-
for (const sub of visibleSubs) lines.push(...generateSubHandler$1(sub, fn, []));
|
|
1350
|
-
lines.push(`function __${fn}_complete_root --no-scope-shadowing`);
|
|
1351
|
-
lines.push(...valueCompletionBlock$1(root.options));
|
|
1352
|
-
lines.push(` if __${fn}_opt_takes_value "" "$_prev"; return; end`);
|
|
1353
|
-
if (root.positionals.length > 0) {
|
|
1354
|
-
lines.push(` if test $_after_dd -eq 1`);
|
|
1355
|
-
lines.push(...positionalBlock$1(root.positionals).map((l) => ` ${l}`));
|
|
1356
|
-
lines.push(` return`);
|
|
1357
|
-
lines.push(` end`);
|
|
1358
|
-
} else lines.push(` if test $_after_dd -eq 1; return; end`);
|
|
1359
|
-
lines.push(` if string match -q -- '-*' "$_cur"`);
|
|
1360
|
-
lines.push(...availableOptionLines$1(root.options, fn));
|
|
1361
|
-
if (visibleSubs.length > 0) {
|
|
1362
|
-
lines.push(` else`);
|
|
1363
|
-
for (const s of getSubNamesWithAliases(root.subcommands)) {
|
|
1364
|
-
const desc = escapeDesc$1(s.description ?? "");
|
|
1365
|
-
lines.push(` echo "${s.name}\t${desc}"`);
|
|
1366
|
-
}
|
|
1367
|
-
} else if (root.positionals.length > 0) {
|
|
1368
|
-
lines.push(` else`);
|
|
1369
|
-
lines.push(...positionalBlock$1(root.positionals));
|
|
1370
|
-
}
|
|
1371
|
-
lines.push(` end`);
|
|
1372
|
-
lines.push(`end`);
|
|
1373
|
-
lines.push(``);
|
|
1374
|
-
lines.push(`function __fish_${fn}_complete`);
|
|
1375
|
-
lines.push(` set -l _args (commandline -opc)`);
|
|
1376
|
-
lines.push(` set -e _args[1]`);
|
|
1377
|
-
lines.push(``);
|
|
1378
|
-
lines.push(` set -l _ct (commandline -ct)`);
|
|
1379
|
-
lines.push(` if test (count $_ct) -eq 0`);
|
|
1380
|
-
lines.push(` set -a _args ""`);
|
|
1381
|
-
lines.push(` else`);
|
|
1382
|
-
lines.push(` set -a _args $_ct`);
|
|
1383
|
-
lines.push(` end`);
|
|
1384
|
-
lines.push(``);
|
|
1385
|
-
lines.push(` set -l _cur ""`);
|
|
1386
|
-
lines.push(` if test (count $_args) -gt 0`);
|
|
1387
|
-
lines.push(` set _cur "$_args[-1]"`);
|
|
1388
|
-
lines.push(` end`);
|
|
1389
|
-
lines.push(``);
|
|
1390
|
-
lines.push(` set -l _prev ""`);
|
|
1391
|
-
lines.push(` if test (count $_args) -gt 1`);
|
|
1392
|
-
lines.push(` set _prev "$_args[-2]"`);
|
|
1393
|
-
lines.push(` end`);
|
|
1394
|
-
lines.push(``);
|
|
1395
|
-
lines.push(` set -l _subcmd "" ; set -l _after_dd 0 ; set -l _pos_count 0 ; set -l _skip_next 0`);
|
|
1396
|
-
lines.push(` set -l _used_opts`);
|
|
1397
|
-
lines.push(``);
|
|
1398
|
-
lines.push(` set -l _j 1`);
|
|
1399
|
-
lines.push(` set -l _limit (math (count $_args) - 1)`);
|
|
1400
|
-
lines.push(` while test $_j -le $_limit`);
|
|
1401
|
-
lines.push(` set -l _w "$_args[$_j]"`);
|
|
1402
|
-
lines.push(` if test $_skip_next -eq 1; set _skip_next 0; set _j (math $_j + 1); continue; end`);
|
|
1403
|
-
lines.push(` if test "$_w" = "--"; set _after_dd 1; set _j (math $_j + 1); continue; end`);
|
|
1404
|
-
lines.push(` if test $_after_dd -eq 1; set _pos_count (math $_pos_count + 1); set _j (math $_j + 1); continue; end`);
|
|
1405
|
-
lines.push(` if string match -q -- '--*=*' "$_w"; set -a _used_opts (string replace -r '=.*' '' -- "$_w"); set _j (math $_j + 1); continue; end`);
|
|
1406
|
-
lines.push(` if string match -q -- '-*' "$_w"`);
|
|
1407
|
-
lines.push(` set -a _used_opts "$_w"`);
|
|
1408
|
-
lines.push(` __${fn}_opt_takes_value "$_subcmd" "$_w"; and set _skip_next 1`);
|
|
1409
|
-
lines.push(` set _j (math $_j + 1); continue`);
|
|
1410
|
-
lines.push(` end`);
|
|
1411
|
-
if (routeEntries.length > 0) lines.push(` if __${fn}_is_subcmd "$_subcmd" "$_w"; test -n "$_subcmd"; and set _subcmd "$_subcmd:$_w"; or set _subcmd "$_w"; set _used_opts; set _pos_count 0; else; set _pos_count (math $_pos_count + 1); end`);
|
|
1412
|
-
else lines.push(` set _pos_count (math $_pos_count + 1)`);
|
|
1413
|
-
lines.push(` set _j (math $_j + 1)`);
|
|
1414
|
-
lines.push(` end`);
|
|
1415
|
-
lines.push(``);
|
|
1416
|
-
lines.push(` switch "$_subcmd"`);
|
|
1417
|
-
for (const r of routeEntries) lines.push(` case "${r.pathStr}"; __${fn}_complete_${r.funcSuffix}`);
|
|
1418
|
-
lines.push(` case '*'; __${fn}_complete_root`);
|
|
1419
|
-
lines.push(` end`);
|
|
1420
|
-
lines.push(`end`);
|
|
1421
|
-
lines.push(``);
|
|
1422
|
-
lines.push(`# Clear existing completions`);
|
|
1423
|
-
lines.push(`complete -e -c ${programName}`);
|
|
1424
|
-
lines.push(``);
|
|
1425
|
-
lines.push(`# Register completion`);
|
|
1426
|
-
lines.push(`complete -c ${programName} -f -a '(__fish_${fn}_complete)'`);
|
|
1427
|
-
lines.push(``);
|
|
1428
|
-
return {
|
|
1429
|
-
script: lines.join("\n"),
|
|
1430
|
-
shell: "fish",
|
|
1431
|
-
installInstructions: `# To enable completions, run one of the following:
|
|
1432
|
-
|
|
1433
|
-
# Option 1: Source directly
|
|
1434
|
-
${programName} completion fish | source
|
|
1435
|
-
|
|
1436
|
-
# Option 2: Save to the fish completions directory
|
|
1437
|
-
${programName} completion fish > ~/.config/fish/completions/${programName}.fish
|
|
1438
|
-
|
|
1439
|
-
# The completion will be available immediately in new shell sessions.
|
|
1440
|
-
# To use in the current session, run:
|
|
1441
|
-
source ~/.config/fish/completions/${programName}.fish`
|
|
1442
|
-
};
|
|
1443
|
-
}
|
|
1444
|
-
|
|
1445
|
-
//#endregion
|
|
1446
|
-
//#region src/completion/zsh.ts
|
|
1447
|
-
function escapeDesc(s) {
|
|
1448
|
-
return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$").replace(/`/g, "\\`").replace(/:/g, "\\:");
|
|
1449
|
-
}
|
|
1450
|
-
/**
|
|
1451
|
-
* Generate zsh value completion lines for a ValueCompletion spec.
|
|
1452
|
-
* Uses `_vals` array (must be declared in the calling function scope).
|
|
1453
|
-
*/
|
|
1454
|
-
function zshValueLines(vc, fn) {
|
|
1455
|
-
if (!vc) return [];
|
|
1456
|
-
switch (vc.type) {
|
|
1457
|
-
case "choices": return [`_vals=(${vc.choices.map((c) => `"${escapeDesc(c)}"`).join(" ")})`, `__${fn}_cdescribe 'completions' _vals`];
|
|
1458
|
-
case "file":
|
|
1459
|
-
if (vc.matcher?.length) return vc.matcher.map((p) => `_files -g "${p}"`);
|
|
1460
|
-
if (vc.extensions?.length) return vc.extensions.map((ext) => `_files -g "*.${ext}"`);
|
|
1461
|
-
return [`_files`];
|
|
1462
|
-
case "directory": return [`_files -/`];
|
|
1463
|
-
case "command": return [`_vals=("\${(@f)$(${vc.shellCommand})}")`, `__${fn}_cdescribe 'completions' _vals`];
|
|
1464
|
-
case "none": return [];
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
/** Generate option-value case branches */
|
|
1468
|
-
function optionValueCases(options, fn) {
|
|
1469
|
-
const lines = [];
|
|
1470
|
-
for (const opt of options) {
|
|
1471
|
-
if (!opt.takesValue || !opt.valueCompletion) continue;
|
|
1472
|
-
const valLines = zshValueLines(opt.valueCompletion, fn);
|
|
1473
|
-
if (valLines.length === 0) continue;
|
|
1474
|
-
const patterns = [`--${opt.cliName}`];
|
|
1475
|
-
if (opt.alias) for (const a of opt.alias) patterns.push(a.length === 1 ? `-${a}` : `--${a}`);
|
|
1476
|
-
lines.push(` ${patterns.join("|")})`);
|
|
1477
|
-
for (const vl of valLines) lines.push(` ${vl}`);
|
|
1478
|
-
lines.push(` return 0 ;;`);
|
|
1479
|
-
}
|
|
1480
|
-
return lines;
|
|
1481
|
-
}
|
|
1482
|
-
/** Generate positional completion block */
|
|
1483
|
-
function positionalBlock(positionals, fn) {
|
|
1484
|
-
if (positionals.length === 0) return [];
|
|
1485
|
-
const lines = [];
|
|
1486
|
-
lines.push(` case "$_pos_count" in`);
|
|
1487
|
-
for (const pos of positionals) {
|
|
1488
|
-
if (pos.variadic) lines.push(` ${pos.position}|*)`);
|
|
1489
|
-
else lines.push(` ${pos.position})`);
|
|
1490
|
-
const valLines = zshValueLines(pos.valueCompletion, fn);
|
|
1491
|
-
for (const vl of valLines) lines.push(` ${vl}`);
|
|
1492
|
-
lines.push(` ;;`);
|
|
1493
|
-
}
|
|
1494
|
-
lines.push(` esac`);
|
|
1495
|
-
return lines;
|
|
1496
|
-
}
|
|
1497
|
-
/** Generate prev-word value completion case block */
|
|
1498
|
-
function valueCompletionBlock(options, fn) {
|
|
1499
|
-
if (!options.some((o) => o.takesValue && o.valueCompletion)) return [];
|
|
1500
|
-
const prevCases = optionValueCases(options, fn);
|
|
1501
|
-
if (prevCases.length === 0) return [];
|
|
1502
|
-
return [
|
|
1503
|
-
` case "\${words[CURRENT-1]}" in`,
|
|
1504
|
-
...prevCases,
|
|
1505
|
-
` esac`
|
|
1506
|
-
];
|
|
1507
|
-
}
|
|
1508
|
-
/** Generate available-options list lines */
|
|
1509
|
-
function availableOptionLines(options, fn) {
|
|
1510
|
-
const lines = [];
|
|
1511
|
-
for (const opt of options) {
|
|
1512
|
-
const desc = opt.description ? `:${escapeDesc(opt.description)}` : "";
|
|
1513
|
-
if (opt.valueType === "array") lines.push(` _opts+=("--${opt.cliName}${desc}")`);
|
|
1514
|
-
else {
|
|
1515
|
-
const patterns = [`"--${opt.cliName}"`];
|
|
1516
|
-
if (opt.alias) for (const a of opt.alias) patterns.push(a.length === 1 ? `"-${a}"` : `"--${a}"`);
|
|
1517
|
-
lines.push(` __${fn}_not_used ${patterns.join(" ")} && _opts+=("--${opt.cliName}${desc}")`);
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
lines.push(` __${fn}_not_used "--help" && _opts+=("--help:Show help")`);
|
|
1521
|
-
return lines;
|
|
1522
|
-
}
|
|
1523
|
-
/**
|
|
1524
|
-
* Generate a per-subcommand completion function.
|
|
1525
|
-
* Recursively generates functions for nested subcommands.
|
|
1526
|
-
*/
|
|
1527
|
-
function generateSubHandler(sub, fn, path) {
|
|
1528
|
-
const fullPath = [...path, sub.name];
|
|
1529
|
-
const funcName = `__${fn}_complete_${fullPath.map(sanitize).join("_")}`;
|
|
1530
|
-
const visibleSubs = getVisibleSubs(sub.subcommands);
|
|
1531
|
-
const lines = [];
|
|
1532
|
-
for (const child of visibleSubs) lines.push(...generateSubHandler(child, fn, fullPath));
|
|
1533
|
-
lines.push(`${funcName}() {`);
|
|
1534
|
-
lines.push(` local -a _vals=()`);
|
|
1535
|
-
lines.push(...valueCompletionBlock(sub.options, fn));
|
|
1536
|
-
const fullPathStr = fullPath.join(":");
|
|
1537
|
-
lines.push(` if __${fn}_opt_takes_value "${fullPathStr}" "\${words[CURRENT-1]}"; then return 0; fi`);
|
|
1538
|
-
if (sub.positionals.length > 0) {
|
|
1539
|
-
lines.push(` if (( _after_dd )); then`);
|
|
1540
|
-
lines.push(...positionalBlock(sub.positionals, fn).map((l) => ` ${l}`));
|
|
1541
|
-
lines.push(` return 0`);
|
|
1542
|
-
lines.push(` fi`);
|
|
1543
|
-
} else lines.push(` if (( _after_dd )); then return 0; fi`);
|
|
1544
|
-
lines.push(` if [[ "\${words[CURRENT]}" == -* ]]; then`);
|
|
1545
|
-
lines.push(` local -a _opts=()`);
|
|
1546
|
-
lines.push(...availableOptionLines(sub.options, fn));
|
|
1547
|
-
lines.push(` __${fn}_cdescribe 'options' _opts`);
|
|
1548
|
-
lines.push(` return 0`);
|
|
1549
|
-
lines.push(` fi`);
|
|
1550
|
-
if (visibleSubs.length > 0) {
|
|
1551
|
-
const subItems = getSubNamesWithAliases(sub.subcommands).map((s) => {
|
|
1552
|
-
const desc = s.description ? `:${escapeDesc(s.description)}` : "";
|
|
1553
|
-
return `"${s.name}${desc}"`;
|
|
1554
|
-
}).join(" ");
|
|
1555
|
-
lines.push(` local -a _subs=(${subItems})`);
|
|
1556
|
-
lines.push(` __${fn}_cdescribe 'subcommands' _subs`);
|
|
1557
|
-
} else if (sub.positionals.length > 0) lines.push(...positionalBlock(sub.positionals, fn));
|
|
1558
|
-
lines.push(`}`);
|
|
1559
|
-
lines.push(``);
|
|
1560
|
-
return lines;
|
|
1561
|
-
}
|
|
1562
|
-
function generateZshCompletion(command, options) {
|
|
1563
|
-
const { programName } = options;
|
|
1564
|
-
const data = extractCompletionData(command, programName, options.globalArgsSchema);
|
|
1565
|
-
const fn = sanitize(programName);
|
|
1566
|
-
const root = data.command;
|
|
1567
|
-
const visibleSubs = getVisibleSubs(root.subcommands);
|
|
1568
|
-
const lines = [];
|
|
1569
|
-
lines.push(`#compdef ${programName}`);
|
|
1570
|
-
lines.push(``);
|
|
1571
|
-
lines.push(`# Zsh completion for ${programName}`);
|
|
1572
|
-
lines.push(`# Generated by politty`);
|
|
1573
|
-
lines.push(``);
|
|
1574
|
-
lines.push(`__${fn}_not_used() {`);
|
|
1575
|
-
lines.push(` local _u _chk`);
|
|
1576
|
-
lines.push(` for _u in "\${_used_opts[@]}"; do`);
|
|
1577
|
-
lines.push(` for _chk in "$@"; do`);
|
|
1578
|
-
lines.push(` [[ "$_u" == "$_chk" ]] && return 1`);
|
|
1579
|
-
lines.push(` done`);
|
|
1580
|
-
lines.push(` done`);
|
|
1581
|
-
lines.push(` return 0`);
|
|
1582
|
-
lines.push(`}`);
|
|
1583
|
-
lines.push(``);
|
|
1584
|
-
lines.push(`__${fn}_cdescribe() {`);
|
|
1585
|
-
lines.push(` _describe "$@" 2>/dev/null && return 0`);
|
|
1586
|
-
lines.push(` shift`);
|
|
1587
|
-
lines.push(` local -a _cd_vals=("\${(@)\${(P)1}%%:*}")`);
|
|
1588
|
-
lines.push(` compadd -a _cd_vals 2>/dev/null`);
|
|
1589
|
-
lines.push(` return 0`);
|
|
1590
|
-
lines.push(`}`);
|
|
1591
|
-
lines.push(``);
|
|
1592
|
-
lines.push(`__${fn}_opt_takes_value() {`);
|
|
1593
|
-
lines.push(` case "$1:$2" in`);
|
|
1594
|
-
lines.push(...optTakesValueEntries(root, ""));
|
|
1595
|
-
lines.push(` esac`);
|
|
1596
|
-
lines.push(` return 1`);
|
|
1597
|
-
lines.push(`}`);
|
|
1598
|
-
lines.push(``);
|
|
1599
|
-
const routeEntries = collectRouteEntries(root);
|
|
1600
|
-
if (routeEntries.length > 0) {
|
|
1601
|
-
lines.push(`__${fn}_is_subcmd() {`);
|
|
1602
|
-
lines.push(` case "$1:$2" in`);
|
|
1603
|
-
lines.push(...isSubcmdCaseLines(routeEntries));
|
|
1604
|
-
lines.push(` esac`);
|
|
1605
|
-
lines.push(` return 1`);
|
|
1606
|
-
lines.push(`}`);
|
|
1607
|
-
lines.push(``);
|
|
1608
|
-
}
|
|
1609
|
-
for (const sub of visibleSubs) lines.push(...generateSubHandler(sub, fn, []));
|
|
1610
|
-
lines.push(`__${fn}_complete_root() {`);
|
|
1611
|
-
lines.push(` local -a _vals=()`);
|
|
1612
|
-
lines.push(...valueCompletionBlock(root.options, fn));
|
|
1613
|
-
lines.push(` if __${fn}_opt_takes_value "" "\${words[CURRENT-1]}"; then return 0; fi`);
|
|
1614
|
-
if (root.positionals.length > 0) {
|
|
1615
|
-
lines.push(` if (( _after_dd )); then`);
|
|
1616
|
-
lines.push(...positionalBlock(root.positionals, fn).map((l) => ` ${l}`));
|
|
1617
|
-
lines.push(` return 0`);
|
|
1618
|
-
lines.push(` fi`);
|
|
1619
|
-
} else lines.push(` if (( _after_dd )); then return 0; fi`);
|
|
1620
|
-
lines.push(` if [[ "\${words[CURRENT]}" == -* ]]; then`);
|
|
1621
|
-
lines.push(` local -a _opts=()`);
|
|
1622
|
-
lines.push(...availableOptionLines(root.options, fn));
|
|
1623
|
-
lines.push(` __${fn}_cdescribe 'options' _opts`);
|
|
1624
|
-
if (visibleSubs.length > 0) {
|
|
1625
|
-
lines.push(` else`);
|
|
1626
|
-
const subItems = getSubNamesWithAliases(root.subcommands).map((s) => {
|
|
1627
|
-
const desc = s.description ? `:${escapeDesc(s.description)}` : "";
|
|
1628
|
-
return `"${s.name}${desc}"`;
|
|
1629
|
-
}).join(" ");
|
|
1630
|
-
lines.push(` local -a _subs=(${subItems})`);
|
|
1631
|
-
lines.push(` __${fn}_cdescribe 'subcommands' _subs`);
|
|
1632
|
-
} else if (root.positionals.length > 0) {
|
|
1633
|
-
lines.push(` else`);
|
|
1634
|
-
lines.push(...positionalBlock(root.positionals, fn).map((l) => ` ${l}`));
|
|
1635
|
-
}
|
|
1636
|
-
lines.push(` fi`);
|
|
1637
|
-
lines.push(`}`);
|
|
1638
|
-
lines.push(``);
|
|
1639
|
-
const subRouting = routeEntries.map((r) => ` ${r.pathStr}) __${fn}_complete_${r.funcSuffix} ;;`).join("\n");
|
|
1640
|
-
lines.push(`_${fn}() {`);
|
|
1641
|
-
lines.push(` (( CURRENT )) || CURRENT=\${#words}`);
|
|
1642
|
-
lines.push(``);
|
|
1643
|
-
lines.push(` local _subcmd="" _after_dd=0 _pos_count=0 _skip_next=0`);
|
|
1644
|
-
lines.push(` local -a _used_opts=()`);
|
|
1645
|
-
lines.push(``);
|
|
1646
|
-
lines.push(` local _j=2`);
|
|
1647
|
-
lines.push(` while (( _j < CURRENT )); do`);
|
|
1648
|
-
lines.push(` local _w="\${words[_j]}"`);
|
|
1649
|
-
lines.push(` if (( _skip_next )); then _skip_next=0; (( _j++ )); continue; fi`);
|
|
1650
|
-
lines.push(` if [[ "$_w" == "--" ]]; then _after_dd=1; (( _j++ )); continue; fi`);
|
|
1651
|
-
lines.push(` if (( _after_dd )); then (( _pos_count++ )); (( _j++ )); continue; fi`);
|
|
1652
|
-
lines.push(` if [[ "$_w" == --*=* ]]; then _used_opts+=("\${_w%%=*}"); (( _j++ )); continue; fi`);
|
|
1653
|
-
lines.push(` if [[ "$_w" == -* ]]; then`);
|
|
1654
|
-
lines.push(` _used_opts+=("$_w")`);
|
|
1655
|
-
lines.push(` __${fn}_opt_takes_value "$_subcmd" "$_w" && _skip_next=1`);
|
|
1656
|
-
lines.push(` (( _j++ )); continue`);
|
|
1657
|
-
lines.push(` fi`);
|
|
1658
|
-
if (routeEntries.length > 0) lines.push(` if __${fn}_is_subcmd "$_subcmd" "$_w"; then _subcmd="\${_subcmd:+\${_subcmd}:}$_w"; _used_opts=(); _pos_count=0; else (( _pos_count++ )); fi`);
|
|
1659
|
-
else lines.push(` (( _pos_count++ ))`);
|
|
1660
|
-
lines.push(` (( _j++ ))`);
|
|
1661
|
-
lines.push(` done`);
|
|
1662
|
-
lines.push(``);
|
|
1663
|
-
lines.push(` case "$_subcmd" in`);
|
|
1664
|
-
lines.push(subRouting);
|
|
1665
|
-
lines.push(` *) __${fn}_complete_root ;;`);
|
|
1666
|
-
lines.push(` esac`);
|
|
1667
|
-
lines.push(`}`);
|
|
1668
|
-
lines.push(``);
|
|
1669
|
-
lines.push(`zstyle ':completion:*:*:${programName}:*' file-patterns '%p:globbed-files *(-/):directories'`);
|
|
1670
|
-
lines.push(``);
|
|
1671
|
-
lines.push(`compdef _${fn} ${programName}`);
|
|
1672
|
-
lines.push(``);
|
|
1673
|
-
return {
|
|
1674
|
-
script: lines.join("\n"),
|
|
1675
|
-
shell: "zsh",
|
|
1676
|
-
installInstructions: `# To enable completions, add the following to your ~/.zshrc:
|
|
1677
|
-
|
|
1678
|
-
# Option 1: Source directly (add before compinit)
|
|
1679
|
-
eval "$(${programName} completion zsh)"
|
|
1680
|
-
|
|
1681
|
-
# Option 2: Save to a file in your fpath
|
|
1682
|
-
${programName} completion zsh > ~/.zsh/completions/_${programName}
|
|
1683
|
-
|
|
1684
|
-
# Make sure your fpath includes the completions directory:
|
|
1685
|
-
# fpath=(~/.zsh/completions $fpath)
|
|
1686
|
-
# autoload -Uz compinit && compinit
|
|
1687
|
-
|
|
1688
|
-
# Then reload your shell or run:
|
|
1689
|
-
source ~/.zshrc`
|
|
1690
|
-
};
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
|
-
//#endregion
|
|
1694
|
-
//#region src/completion/index.ts
|
|
1695
|
-
/**
|
|
1696
|
-
* Shell completion generation module
|
|
1697
|
-
*
|
|
1698
|
-
* Provides utilities to generate shell completion scripts for bash, zsh, and fish.
|
|
1699
|
-
*
|
|
1700
|
-
* @example
|
|
1701
|
-
* ```typescript
|
|
1702
|
-
* import { generateCompletion, createCompletionCommand } from "politty/completion";
|
|
1703
|
-
*
|
|
1704
|
-
* // Generate completion script directly
|
|
1705
|
-
* const result = generateCompletion(myCommand, {
|
|
1706
|
-
* shell: "bash",
|
|
1707
|
-
* programName: "mycli"
|
|
1708
|
-
* });
|
|
1709
|
-
* console.log(result.script);
|
|
1710
|
-
*
|
|
1711
|
-
* // Or add a completion subcommand to your CLI
|
|
1712
|
-
* const mainCommand = withCompletionCommand(
|
|
1713
|
-
* defineCommand({
|
|
1714
|
-
* name: "mycli",
|
|
1715
|
-
* subCommands: { ... },
|
|
1716
|
-
* }),
|
|
1717
|
-
* );
|
|
1718
|
-
* ```
|
|
1719
|
-
*/
|
|
1720
|
-
/**
|
|
1721
|
-
* Generate completion script for the specified shell
|
|
1722
|
-
*/
|
|
1723
|
-
function generateCompletion(command, options) {
|
|
1724
|
-
switch (options.shell) {
|
|
1725
|
-
case "bash": return generateBashCompletion(command, options);
|
|
1726
|
-
case "zsh": return generateZshCompletion(command, options);
|
|
1727
|
-
case "fish": return generateFishCompletion(command, options);
|
|
1728
|
-
default: throw new Error(`Unsupported shell: ${options.shell}`);
|
|
1729
|
-
}
|
|
1730
|
-
}
|
|
1731
|
-
/**
|
|
1732
|
-
* Get the list of supported shells
|
|
1733
|
-
*/
|
|
1734
|
-
function getSupportedShells() {
|
|
1735
|
-
return [
|
|
1736
|
-
"bash",
|
|
1737
|
-
"zsh",
|
|
1738
|
-
"fish"
|
|
1739
|
-
];
|
|
1740
|
-
}
|
|
1741
|
-
/**
|
|
1742
|
-
* Detect the current shell from environment
|
|
1743
|
-
*/
|
|
1744
|
-
function detectShell() {
|
|
1745
|
-
const shellName = (process.env.SHELL || "").split("/").pop()?.toLowerCase() || "";
|
|
1746
|
-
if (shellName.includes("bash")) return "bash";
|
|
1747
|
-
if (shellName.includes("zsh")) return "zsh";
|
|
1748
|
-
if (shellName.includes("fish")) return "fish";
|
|
1749
|
-
return null;
|
|
1750
|
-
}
|
|
1751
|
-
/**
|
|
1752
|
-
* Schema for the completion command arguments
|
|
1753
|
-
*/
|
|
1754
|
-
const completionArgsSchema = zod.z.object({
|
|
1755
|
-
shell: require_subcommand_router.arg(zod.z.enum([
|
|
1756
|
-
"bash",
|
|
1757
|
-
"zsh",
|
|
1758
|
-
"fish"
|
|
1759
|
-
]).optional().describe("Shell type (auto-detected if not specified)"), {
|
|
1760
|
-
positional: true,
|
|
1761
|
-
description: "Shell type (bash, zsh, or fish)",
|
|
1762
|
-
placeholder: "SHELL"
|
|
1763
|
-
}),
|
|
1764
|
-
instructions: require_subcommand_router.arg(zod.z.boolean().default(false), {
|
|
1765
|
-
alias: "i",
|
|
1766
|
-
description: "Show installation instructions"
|
|
1767
|
-
})
|
|
1768
|
-
});
|
|
1769
|
-
/**
|
|
1770
|
-
* Create a completion subcommand for your CLI
|
|
1771
|
-
*
|
|
1772
|
-
* This creates a ready-to-use subcommand that generates completion scripts.
|
|
1773
|
-
*
|
|
1774
|
-
* @example
|
|
1775
|
-
* ```typescript
|
|
1776
|
-
* const mainCommand = defineCommand({
|
|
1777
|
-
* name: "mycli",
|
|
1778
|
-
* subCommands: {
|
|
1779
|
-
* completion: createCompletionCommand(mainCommand)
|
|
1780
|
-
* }
|
|
1781
|
-
* });
|
|
1782
|
-
* ```
|
|
1783
|
-
*/
|
|
1784
|
-
function createCompletionCommand(rootCommand, programName, globalArgsSchema) {
|
|
1785
|
-
const resolvedProgramName = programName ?? rootCommand.name;
|
|
1786
|
-
if (!rootCommand.subCommands?.__complete) rootCommand.subCommands = {
|
|
1787
|
-
...rootCommand.subCommands,
|
|
1788
|
-
__complete: createDynamicCompleteCommand(rootCommand, resolvedProgramName)
|
|
1789
|
-
};
|
|
1790
|
-
return defineCommand({
|
|
1791
|
-
name: "completion",
|
|
1792
|
-
description: "Generate shell completion script",
|
|
1793
|
-
args: completionArgsSchema,
|
|
1794
|
-
run(args) {
|
|
1795
|
-
const shellType = args.shell || detectShell();
|
|
1796
|
-
if (!shellType) {
|
|
1797
|
-
console.error("Could not detect shell type. Please specify one of: bash, zsh, fish");
|
|
1798
|
-
process.exitCode = 1;
|
|
1799
|
-
return;
|
|
1800
|
-
}
|
|
1801
|
-
const result = generateCompletion(rootCommand, {
|
|
1802
|
-
shell: shellType,
|
|
1803
|
-
programName: resolvedProgramName,
|
|
1804
|
-
includeDescriptions: true,
|
|
1805
|
-
...globalArgsSchema !== void 0 && { globalArgsSchema }
|
|
1806
|
-
});
|
|
1807
|
-
if (args.instructions) console.log(result.installInstructions);
|
|
1808
|
-
else console.log(result.script);
|
|
1809
|
-
}
|
|
1810
|
-
});
|
|
1811
|
-
}
|
|
1812
|
-
/**
|
|
1813
|
-
* Wrap a command with a completion subcommand
|
|
1814
|
-
*
|
|
1815
|
-
* This avoids circular references that occur when a command references itself
|
|
1816
|
-
* in its subCommands (e.g., for completion generation).
|
|
1817
|
-
*
|
|
1818
|
-
* @param command - The command to wrap
|
|
1819
|
-
* @param options - Options including programName
|
|
1820
|
-
* @returns A new command with the completion subcommand added
|
|
1821
|
-
*
|
|
1822
|
-
* @example
|
|
1823
|
-
* ```typescript
|
|
1824
|
-
* const mainCommand = withCompletionCommand(
|
|
1825
|
-
* defineCommand({
|
|
1826
|
-
* name: "mycli",
|
|
1827
|
-
* subCommands: { ... },
|
|
1828
|
-
* }),
|
|
1829
|
-
* );
|
|
1830
|
-
* ```
|
|
1831
|
-
*/
|
|
1832
|
-
function withCompletionCommand(command, options) {
|
|
1833
|
-
const { programName, globalArgsSchema } = typeof options === "string" ? { programName: options } : options ?? {};
|
|
1834
|
-
const wrappedCommand = { ...command };
|
|
1835
|
-
wrappedCommand.subCommands = {
|
|
1836
|
-
...command.subCommands,
|
|
1837
|
-
completion: createCompletionCommand(wrappedCommand, programName, globalArgsSchema),
|
|
1838
|
-
__complete: createDynamicCompleteCommand(wrappedCommand, programName)
|
|
1839
|
-
};
|
|
1840
|
-
return wrappedCommand;
|
|
1841
|
-
}
|
|
1842
|
-
|
|
1843
|
-
//#endregion
|
|
1844
|
-
Object.defineProperty(exports, 'CompletionDirective', {
|
|
1845
|
-
enumerable: true,
|
|
1846
|
-
get: function () {
|
|
1847
|
-
return CompletionDirective;
|
|
1848
|
-
}
|
|
1849
|
-
});
|
|
1850
|
-
Object.defineProperty(exports, 'createCompletionCommand', {
|
|
1851
|
-
enumerable: true,
|
|
1852
|
-
get: function () {
|
|
1853
|
-
return createCompletionCommand;
|
|
1854
|
-
}
|
|
1855
|
-
});
|
|
1856
|
-
Object.defineProperty(exports, 'createDefineCommand', {
|
|
1857
|
-
enumerable: true,
|
|
1858
|
-
get: function () {
|
|
1859
|
-
return createDefineCommand;
|
|
1860
|
-
}
|
|
1861
|
-
});
|
|
1862
|
-
Object.defineProperty(exports, 'createDynamicCompleteCommand', {
|
|
1863
|
-
enumerable: true,
|
|
1864
|
-
get: function () {
|
|
1865
|
-
return createDynamicCompleteCommand;
|
|
1866
|
-
}
|
|
1867
|
-
});
|
|
1868
|
-
Object.defineProperty(exports, 'defineCommand', {
|
|
1869
|
-
enumerable: true,
|
|
1870
|
-
get: function () {
|
|
1871
|
-
return defineCommand;
|
|
1872
|
-
}
|
|
1873
|
-
});
|
|
1874
|
-
Object.defineProperty(exports, 'detectShell', {
|
|
1875
|
-
enumerable: true,
|
|
1876
|
-
get: function () {
|
|
1877
|
-
return detectShell;
|
|
1878
|
-
}
|
|
1879
|
-
});
|
|
1880
|
-
Object.defineProperty(exports, 'extractCompletionData', {
|
|
1881
|
-
enumerable: true,
|
|
1882
|
-
get: function () {
|
|
1883
|
-
return extractCompletionData;
|
|
1884
|
-
}
|
|
1885
|
-
});
|
|
1886
|
-
Object.defineProperty(exports, 'extractPositionals', {
|
|
1887
|
-
enumerable: true,
|
|
1888
|
-
get: function () {
|
|
1889
|
-
return extractPositionals;
|
|
1890
|
-
}
|
|
1891
|
-
});
|
|
1892
|
-
Object.defineProperty(exports, 'formatForShell', {
|
|
1893
|
-
enumerable: true,
|
|
1894
|
-
get: function () {
|
|
1895
|
-
return formatForShell;
|
|
1896
|
-
}
|
|
1897
|
-
});
|
|
1898
|
-
Object.defineProperty(exports, 'generateCandidates', {
|
|
1899
|
-
enumerable: true,
|
|
1900
|
-
get: function () {
|
|
1901
|
-
return generateCandidates;
|
|
1902
|
-
}
|
|
1903
|
-
});
|
|
1904
|
-
Object.defineProperty(exports, 'generateCompletion', {
|
|
1905
|
-
enumerable: true,
|
|
1906
|
-
get: function () {
|
|
1907
|
-
return generateCompletion;
|
|
1908
|
-
}
|
|
1909
|
-
});
|
|
1910
|
-
Object.defineProperty(exports, 'getSupportedShells', {
|
|
1911
|
-
enumerable: true,
|
|
1912
|
-
get: function () {
|
|
1913
|
-
return getSupportedShells;
|
|
1914
|
-
}
|
|
1915
|
-
});
|
|
1916
|
-
Object.defineProperty(exports, 'hasCompleteCommand', {
|
|
1917
|
-
enumerable: true,
|
|
1918
|
-
get: function () {
|
|
1919
|
-
return hasCompleteCommand;
|
|
1920
|
-
}
|
|
1921
|
-
});
|
|
1922
|
-
Object.defineProperty(exports, 'parseCompletionContext', {
|
|
1923
|
-
enumerable: true,
|
|
1924
|
-
get: function () {
|
|
1925
|
-
return parseCompletionContext;
|
|
1926
|
-
}
|
|
1927
|
-
});
|
|
1928
|
-
Object.defineProperty(exports, 'resolveValueCompletion', {
|
|
1929
|
-
enumerable: true,
|
|
1930
|
-
get: function () {
|
|
1931
|
-
return resolveValueCompletion;
|
|
1932
|
-
}
|
|
1933
|
-
});
|
|
1934
|
-
Object.defineProperty(exports, 'withCompletionCommand', {
|
|
1935
|
-
enumerable: true,
|
|
1936
|
-
get: function () {
|
|
1937
|
-
return withCompletionCommand;
|
|
1938
|
-
}
|
|
1939
|
-
});
|
|
1940
|
-
//# sourceMappingURL=completion-B5fgnUGm.cjs.map
|