@optique/core 1.1.0-dev.2087 → 1.1.0-dev.2146
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/annotation-state.cjs +26 -26
- package/dist/annotation-state.d.cts +133 -1
- package/dist/annotation-state.d.ts +133 -1
- package/dist/annotations.cjs +2 -2
- package/dist/constructs.cjs +873 -73
- package/dist/constructs.d.cts +72 -1
- package/dist/constructs.d.ts +72 -1
- package/dist/constructs.js +808 -9
- package/dist/dependency-metadata.cjs +12 -12
- package/dist/dependency-metadata.d.cts +34 -3
- package/dist/dependency-metadata.d.ts +34 -3
- package/dist/dependency-runtime.cjs +37 -13
- package/dist/dependency-runtime.d.cts +197 -2
- package/dist/dependency-runtime.d.ts +197 -2
- package/dist/dependency-runtime.js +22 -1
- package/dist/dependency.cjs +7 -7
- package/dist/displaywidth.d.cts +12 -0
- package/dist/displaywidth.d.ts +12 -0
- package/dist/doc.cjs +3 -0
- package/dist/doc.js +3 -0
- package/dist/execution-context.d.cts +23 -0
- package/dist/execution-context.d.ts +23 -0
- package/dist/extension.cjs +14 -14
- package/dist/facade.cjs +49 -37
- package/dist/facade.js +34 -22
- package/dist/index.cjs +23 -21
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -4
- package/dist/input-trace.d.cts +2 -1
- package/dist/input-trace.d.ts +2 -1
- package/dist/internal/annotations.cjs +3 -0
- package/dist/internal/annotations.d.cts +47 -5
- package/dist/internal/annotations.d.ts +47 -5
- package/dist/internal/annotations.js +1 -1
- package/dist/internal/command-alias.cjs +16 -0
- package/dist/internal/command-alias.js +14 -0
- package/dist/internal/dependency.cjs +131 -0
- package/dist/internal/dependency.d.cts +311 -2
- package/dist/internal/dependency.d.ts +311 -2
- package/dist/internal/dependency.js +119 -1
- package/dist/internal/parser.cjs +108 -23
- package/dist/internal/parser.d.cts +58 -3
- package/dist/internal/parser.d.ts +58 -3
- package/dist/internal/parser.js +101 -16
- package/dist/modifiers.cjs +74 -44
- package/dist/modifiers.js +34 -4
- package/dist/parser.cjs +11 -11
- package/dist/phase2-seed.cjs +2 -2
- package/dist/phase2-seed.d.cts +50 -0
- package/dist/phase2-seed.d.ts +50 -0
- package/dist/primitives.cjs +104 -33
- package/dist/primitives.d.cts +10 -0
- package/dist/primitives.d.ts +10 -0
- package/dist/primitives.js +84 -13
- package/dist/suggestion.cjs +72 -2
- package/dist/suggestion.d.cts +188 -0
- package/dist/suggestion.d.ts +188 -0
- package/dist/suggestion.js +71 -3
- package/dist/usage-internals.cjs +14 -6
- package/dist/usage-internals.js +14 -6
- package/dist/usage.cjs +33 -8
- package/dist/usage.d.cts +31 -0
- package/dist/usage.d.ts +31 -0
- package/dist/usage.js +33 -8
- package/dist/validate.cjs +1 -0
- package/dist/validate.d.cts +99 -0
- package/dist/validate.d.ts +99 -0
- package/dist/validate.js +1 -1
- package/dist/valueparser.cjs +333 -79
- package/dist/valueparser.d.cts +197 -1
- package/dist/valueparser.d.ts +197 -1
- package/dist/valueparser.js +334 -81
- package/package.json +19 -4
package/dist/suggestion.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { message, optionName, text } from "./message.js";
|
|
2
|
-
import { extractCommandNames, extractOptionNames } from "./usage.js";
|
|
2
|
+
import { extractCommandNames, extractOptionNames, isSuggestionHidden } from "./usage.js";
|
|
3
3
|
|
|
4
4
|
//#region src/suggestion.ts
|
|
5
5
|
/**
|
|
@@ -141,6 +141,73 @@ function createSuggestionMessage(suggestions) {
|
|
|
141
141
|
return messageParts;
|
|
142
142
|
}
|
|
143
143
|
/**
|
|
144
|
+
* Expands command alias suggestions so an alias typo can point at both the
|
|
145
|
+
* canonical command and the alias that matched.
|
|
146
|
+
*
|
|
147
|
+
* @param usage Usage terms that define command aliases.
|
|
148
|
+
* @param suggestions Candidate suggestions returned by {@link findSimilar}.
|
|
149
|
+
* @returns Suggestions with alias hits expanded to canonical name + alias.
|
|
150
|
+
* @internal
|
|
151
|
+
*/
|
|
152
|
+
function expandCommandAliasSuggestions(usage, suggestions) {
|
|
153
|
+
if (suggestions.length === 0) return suggestions;
|
|
154
|
+
const commandAliasTargets = collectCommandAliasTargets(usage);
|
|
155
|
+
const expanded = [];
|
|
156
|
+
const seen = /* @__PURE__ */ new Set();
|
|
157
|
+
for (const suggestion of suggestions) {
|
|
158
|
+
const targets = commandAliasTargets.get(suggestion) ?? [suggestion];
|
|
159
|
+
for (const target of targets) {
|
|
160
|
+
if (seen.has(target)) continue;
|
|
161
|
+
seen.add(target);
|
|
162
|
+
expanded.push(target);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return expanded;
|
|
166
|
+
}
|
|
167
|
+
function collectCommandAliasTargets(usage) {
|
|
168
|
+
const targets = /* @__PURE__ */ new Map();
|
|
169
|
+
function traverse(terms) {
|
|
170
|
+
if (!terms || !Array.isArray(terms)) return true;
|
|
171
|
+
for (const term of terms) {
|
|
172
|
+
if (term.type === "option") continue;
|
|
173
|
+
if (term.type === "argument") return false;
|
|
174
|
+
if (term.type === "command") {
|
|
175
|
+
if (isSuggestionHidden(term.hidden)) return false;
|
|
176
|
+
if (!targets.has(term.name)) targets.set(term.name, [term.name]);
|
|
177
|
+
for (const alias of term.aliases ?? []) if (!targets.has(alias)) targets.set(alias, [term.name, alias]);
|
|
178
|
+
for (const alias of term.hiddenAliases ?? []) if (!targets.has(alias)) targets.set(alias, [term.name]);
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
if (term.type === "optional") {
|
|
182
|
+
traverse(term.terms);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (term.type === "multiple") {
|
|
186
|
+
const termsSkippable = traverse(term.terms);
|
|
187
|
+
if (term.min === 0 || termsSkippable) continue;
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
if (term.type === "sequence") {
|
|
191
|
+
if (traverse(term.terms)) continue;
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
if (term.type === "exclusive") {
|
|
195
|
+
let anySkippable = false;
|
|
196
|
+
for (const branch of term.terms) {
|
|
197
|
+
const branchSkippable = traverse(branch);
|
|
198
|
+
anySkippable = anySkippable || branchSkippable;
|
|
199
|
+
}
|
|
200
|
+
if (anySkippable) continue;
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
traverse(usage);
|
|
208
|
+
return targets;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
144
211
|
* Creates an error message with suggestions for similar options or commands.
|
|
145
212
|
*
|
|
146
213
|
* This is a convenience function that combines the functionality of
|
|
@@ -175,7 +242,8 @@ function createErrorWithSuggestions(baseError, invalidInput, usage, type = "both
|
|
|
175
242
|
if (type === "option" || type === "both") for (const name of extractOptionNames(usage)) candidates.add(name);
|
|
176
243
|
if (type === "command" || type === "both") for (const name of extractCommandNames(usage)) candidates.add(name);
|
|
177
244
|
const suggestions = findSimilar(invalidInput, candidates, DEFAULT_FIND_SIMILAR_OPTIONS);
|
|
178
|
-
const
|
|
245
|
+
const displaySuggestions = type === "option" ? suggestions : expandCommandAliasSuggestions(usage, suggestions);
|
|
246
|
+
const suggestionMsg = customFormatter ? customFormatter(displaySuggestions) : createSuggestionMessage(displaySuggestions);
|
|
179
247
|
return suggestionMsg.length > 0 ? [
|
|
180
248
|
...baseError,
|
|
181
249
|
text("\n\n"),
|
|
@@ -244,4 +312,4 @@ function deduplicateSuggestions(suggestions) {
|
|
|
244
312
|
}
|
|
245
313
|
|
|
246
314
|
//#endregion
|
|
247
|
-
export { DEFAULT_FIND_SIMILAR_OPTIONS, createErrorWithSuggestions, createSuggestionMessage, deduplicateSuggestions, findSimilar };
|
|
315
|
+
export { DEFAULT_FIND_SIMILAR_OPTIONS, createErrorWithSuggestions, createSuggestionMessage, deduplicateSuggestions, expandCommandAliasSuggestions, findSimilar, levenshteinDistance };
|
package/dist/usage-internals.cjs
CHANGED
|
@@ -16,31 +16,39 @@ const require_usage = require('./usage.cjs');
|
|
|
16
16
|
* @returns `true` if every term in `terms` is skippable (i.e., the caller
|
|
17
17
|
* may continue scanning the next sibling term), `false` otherwise.
|
|
18
18
|
*/
|
|
19
|
-
function collectLeadingCandidates(terms, optionNames, commandNames) {
|
|
19
|
+
function collectLeadingCandidates(terms, optionNames, commandNames, includeHidden = false) {
|
|
20
20
|
if (!terms || !Array.isArray(terms)) return true;
|
|
21
21
|
for (const term of terms) {
|
|
22
22
|
if (term.type === "option") {
|
|
23
|
-
if (!require_usage.isSuggestionHidden(term.hidden)) for (const name of term.names) optionNames.add(name);
|
|
23
|
+
if (includeHidden || !require_usage.isSuggestionHidden(term.hidden)) for (const name of term.names) optionNames.add(name);
|
|
24
24
|
return false;
|
|
25
25
|
}
|
|
26
26
|
if (term.type === "command") {
|
|
27
|
-
if (!require_usage.isSuggestionHidden(term.hidden))
|
|
27
|
+
if (includeHidden || !require_usage.isSuggestionHidden(term.hidden)) {
|
|
28
|
+
commandNames.add(term.name);
|
|
29
|
+
for (const alias of term.aliases ?? []) commandNames.add(alias);
|
|
30
|
+
for (const alias of term.hiddenAliases ?? []) commandNames.add(alias);
|
|
31
|
+
}
|
|
28
32
|
return false;
|
|
29
33
|
}
|
|
30
34
|
if (term.type === "argument") return false;
|
|
31
35
|
if (term.type === "optional") {
|
|
32
|
-
collectLeadingCandidates(term.terms, optionNames, commandNames);
|
|
36
|
+
collectLeadingCandidates(term.terms, optionNames, commandNames, includeHidden);
|
|
33
37
|
continue;
|
|
34
38
|
}
|
|
35
39
|
if (term.type === "multiple") {
|
|
36
|
-
collectLeadingCandidates(term.terms, optionNames, commandNames);
|
|
40
|
+
collectLeadingCandidates(term.terms, optionNames, commandNames, includeHidden);
|
|
37
41
|
if (term.min === 0) continue;
|
|
38
42
|
return false;
|
|
39
43
|
}
|
|
44
|
+
if (term.type === "sequence") {
|
|
45
|
+
if (collectLeadingCandidates(term.terms, optionNames, commandNames, includeHidden)) continue;
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
40
48
|
if (term.type === "exclusive") {
|
|
41
49
|
let allSkippable = true;
|
|
42
50
|
for (const branch of term.terms) {
|
|
43
|
-
const branchSkippable = collectLeadingCandidates(branch, optionNames, commandNames);
|
|
51
|
+
const branchSkippable = collectLeadingCandidates(branch, optionNames, commandNames, includeHidden);
|
|
44
52
|
allSkippable = allSkippable && branchSkippable;
|
|
45
53
|
}
|
|
46
54
|
if (allSkippable) continue;
|
package/dist/usage-internals.js
CHANGED
|
@@ -16,31 +16,39 @@ import { isSuggestionHidden } from "./usage.js";
|
|
|
16
16
|
* @returns `true` if every term in `terms` is skippable (i.e., the caller
|
|
17
17
|
* may continue scanning the next sibling term), `false` otherwise.
|
|
18
18
|
*/
|
|
19
|
-
function collectLeadingCandidates(terms, optionNames, commandNames) {
|
|
19
|
+
function collectLeadingCandidates(terms, optionNames, commandNames, includeHidden = false) {
|
|
20
20
|
if (!terms || !Array.isArray(terms)) return true;
|
|
21
21
|
for (const term of terms) {
|
|
22
22
|
if (term.type === "option") {
|
|
23
|
-
if (!isSuggestionHidden(term.hidden)) for (const name of term.names) optionNames.add(name);
|
|
23
|
+
if (includeHidden || !isSuggestionHidden(term.hidden)) for (const name of term.names) optionNames.add(name);
|
|
24
24
|
return false;
|
|
25
25
|
}
|
|
26
26
|
if (term.type === "command") {
|
|
27
|
-
if (!isSuggestionHidden(term.hidden))
|
|
27
|
+
if (includeHidden || !isSuggestionHidden(term.hidden)) {
|
|
28
|
+
commandNames.add(term.name);
|
|
29
|
+
for (const alias of term.aliases ?? []) commandNames.add(alias);
|
|
30
|
+
for (const alias of term.hiddenAliases ?? []) commandNames.add(alias);
|
|
31
|
+
}
|
|
28
32
|
return false;
|
|
29
33
|
}
|
|
30
34
|
if (term.type === "argument") return false;
|
|
31
35
|
if (term.type === "optional") {
|
|
32
|
-
collectLeadingCandidates(term.terms, optionNames, commandNames);
|
|
36
|
+
collectLeadingCandidates(term.terms, optionNames, commandNames, includeHidden);
|
|
33
37
|
continue;
|
|
34
38
|
}
|
|
35
39
|
if (term.type === "multiple") {
|
|
36
|
-
collectLeadingCandidates(term.terms, optionNames, commandNames);
|
|
40
|
+
collectLeadingCandidates(term.terms, optionNames, commandNames, includeHidden);
|
|
37
41
|
if (term.min === 0) continue;
|
|
38
42
|
return false;
|
|
39
43
|
}
|
|
44
|
+
if (term.type === "sequence") {
|
|
45
|
+
if (collectLeadingCandidates(term.terms, optionNames, commandNames, includeHidden)) continue;
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
40
48
|
if (term.type === "exclusive") {
|
|
41
49
|
let allSkippable = true;
|
|
42
50
|
for (const branch of term.terms) {
|
|
43
|
-
const branchSkippable = collectLeadingCandidates(branch, optionNames, commandNames);
|
|
51
|
+
const branchSkippable = collectLeadingCandidates(branch, optionNames, commandNames, includeHidden);
|
|
44
52
|
allSkippable = allSkippable && branchSkippable;
|
|
45
53
|
}
|
|
46
54
|
if (allSkippable) continue;
|
package/dist/usage.cjs
CHANGED
|
@@ -63,7 +63,7 @@ function extractOptionNames(usage, includeHidden) {
|
|
|
63
63
|
for (const term of terms) if (term.type === "option") {
|
|
64
64
|
if (!includeHidden && isSuggestionHidden(term.hidden)) continue;
|
|
65
65
|
for (const name of term.names) names.add(name);
|
|
66
|
-
} else if (term.type === "optional" || term.type === "multiple") traverseUsage(term.terms);
|
|
66
|
+
} else if (term.type === "optional" || term.type === "multiple" || term.type === "sequence") traverseUsage(term.terms);
|
|
67
67
|
else if (term.type === "exclusive") for (const exclusiveUsage of term.terms) traverseUsage(exclusiveUsage);
|
|
68
68
|
}
|
|
69
69
|
traverseUsage(usage);
|
|
@@ -98,7 +98,9 @@ function extractCommandNames(usage, includeHidden) {
|
|
|
98
98
|
for (const term of terms) if (term.type === "command") {
|
|
99
99
|
if (!includeHidden && isSuggestionHidden(term.hidden)) continue;
|
|
100
100
|
names.add(term.name);
|
|
101
|
-
|
|
101
|
+
for (const alias of term.aliases ?? []) names.add(alias);
|
|
102
|
+
if (includeHidden) for (const alias of term.hiddenAliases ?? []) names.add(alias);
|
|
103
|
+
} else if (term.type === "optional" || term.type === "multiple" || term.type === "sequence") traverseUsage(term.terms);
|
|
102
104
|
else if (term.type === "exclusive") for (const exclusiveUsage of term.terms) traverseUsage(exclusiveUsage);
|
|
103
105
|
}
|
|
104
106
|
traverseUsage(usage);
|
|
@@ -121,7 +123,7 @@ function extractLiteralValues(usage) {
|
|
|
121
123
|
function traverseUsage(terms) {
|
|
122
124
|
if (!terms || !Array.isArray(terms)) return;
|
|
123
125
|
for (const term of terms) if (term.type === "literal") values.add(term.value);
|
|
124
|
-
else if (term.type === "optional" || term.type === "multiple") traverseUsage(term.terms);
|
|
126
|
+
else if (term.type === "optional" || term.type === "multiple" || term.type === "sequence") traverseUsage(term.terms);
|
|
125
127
|
else if (term.type === "exclusive") for (const branch of term.terms) traverseUsage(branch);
|
|
126
128
|
}
|
|
127
129
|
traverseUsage(usage);
|
|
@@ -155,7 +157,7 @@ function extractArgumentMetavars(usage) {
|
|
|
155
157
|
for (const term of terms) if (term.type === "argument") {
|
|
156
158
|
if (isSuggestionHidden(term.hidden)) continue;
|
|
157
159
|
metavars.add(term.metavar);
|
|
158
|
-
} else if (term.type === "optional" || term.type === "multiple") traverseUsage(term.terms);
|
|
160
|
+
} else if (term.type === "optional" || term.type === "multiple" || term.type === "sequence") traverseUsage(term.terms);
|
|
159
161
|
else if (term.type === "exclusive") for (const exclusiveUsage of term.terms) traverseUsage(exclusiveUsage);
|
|
160
162
|
}
|
|
161
163
|
traverseUsage(usage);
|
|
@@ -282,6 +284,10 @@ function normalizeUsageTerm(term) {
|
|
|
282
284
|
terms: normalizeUsage(term.terms),
|
|
283
285
|
min: term.min
|
|
284
286
|
};
|
|
287
|
+
else if (term.type === "sequence") return {
|
|
288
|
+
type: "sequence",
|
|
289
|
+
terms: term.terms.map(normalizeUsageTerm).filter(isNonDegenerateTerm)
|
|
290
|
+
};
|
|
285
291
|
else if (term.type === "exclusive") {
|
|
286
292
|
const terms = [];
|
|
287
293
|
for (const usage of term.terms) {
|
|
@@ -314,7 +320,7 @@ function isNonDegenerateTerm(term) {
|
|
|
314
320
|
if (term.type === "option") return term.names.length > 0;
|
|
315
321
|
if (term.type === "command") return term.name !== "";
|
|
316
322
|
if (term.type === "argument") return term.metavar.length > 0;
|
|
317
|
-
if (term.type === "optional" || term.type === "multiple" || term.type === "exclusive") return term.terms.length > 0;
|
|
323
|
+
if (term.type === "optional" || term.type === "multiple" || term.type === "exclusive" || term.type === "sequence") return term.terms.length > 0;
|
|
318
324
|
return true;
|
|
319
325
|
}
|
|
320
326
|
function containsMalformedLeaf(usage) {
|
|
@@ -322,7 +328,7 @@ function containsMalformedLeaf(usage) {
|
|
|
322
328
|
if (term.type === "option" && term.names.length === 0) return true;
|
|
323
329
|
if (term.type === "command" && term.name === "") return true;
|
|
324
330
|
if (term.type === "argument" && term.metavar.length === 0) return true;
|
|
325
|
-
if (term.type === "optional" || term.type === "multiple") {
|
|
331
|
+
if (term.type === "optional" || term.type === "multiple" || term.type === "sequence") {
|
|
326
332
|
if (containsMalformedLeaf(term.terms)) return true;
|
|
327
333
|
}
|
|
328
334
|
if (term.type === "exclusive") {
|
|
@@ -350,9 +356,15 @@ function cloneUsageTerm(term) {
|
|
|
350
356
|
names: [...term.names]
|
|
351
357
|
};
|
|
352
358
|
case "command": {
|
|
353
|
-
if (term.usageLine == null || typeof term.usageLine === "function") return {
|
|
359
|
+
if (term.usageLine == null || typeof term.usageLine === "function") return {
|
|
360
|
+
...term,
|
|
361
|
+
...term.aliases != null ? { aliases: [...term.aliases] } : {},
|
|
362
|
+
...term.hiddenAliases != null ? { hiddenAliases: [...term.hiddenAliases] } : {}
|
|
363
|
+
};
|
|
354
364
|
return {
|
|
355
365
|
...term,
|
|
366
|
+
...term.aliases != null ? { aliases: [...term.aliases] } : {},
|
|
367
|
+
...term.hiddenAliases != null ? { hiddenAliases: [...term.hiddenAliases] } : {},
|
|
356
368
|
usageLine: term.usageLine.map(cloneUsageTerm)
|
|
357
369
|
};
|
|
358
370
|
}
|
|
@@ -369,6 +381,10 @@ function cloneUsageTerm(term) {
|
|
|
369
381
|
type: "exclusive",
|
|
370
382
|
terms: term.terms.map((u) => u.map(cloneUsageTerm))
|
|
371
383
|
};
|
|
384
|
+
case "sequence": return {
|
|
385
|
+
type: "sequence",
|
|
386
|
+
terms: term.terms.map(cloneUsageTerm)
|
|
387
|
+
};
|
|
372
388
|
case "literal":
|
|
373
389
|
case "passthrough":
|
|
374
390
|
case "ellipsis": return { ...term };
|
|
@@ -421,6 +437,14 @@ function filterUsageForDisplay(usage, isHidden = isUsageHidden) {
|
|
|
421
437
|
});
|
|
422
438
|
continue;
|
|
423
439
|
}
|
|
440
|
+
if (term.type === "sequence") {
|
|
441
|
+
const filtered = filterUsageForDisplay(term.terms, isHidden);
|
|
442
|
+
if (filtered.length > 0) terms.push({
|
|
443
|
+
type: "sequence",
|
|
444
|
+
terms: filtered
|
|
445
|
+
});
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
424
448
|
terms.push(term);
|
|
425
449
|
}
|
|
426
450
|
return terms;
|
|
@@ -541,7 +565,8 @@ function* formatUsageTermInternal(term, options) {
|
|
|
541
565
|
text: options?.colors ? `\x1b[2m)\x1b[0m` : ")",
|
|
542
566
|
width: 1
|
|
543
567
|
};
|
|
544
|
-
} else if (term.type === "
|
|
568
|
+
} else if (term.type === "sequence") yield* formatUsageTerms(term.terms, options);
|
|
569
|
+
else if (term.type === "multiple") {
|
|
545
570
|
if (term.min < 1) yield {
|
|
546
571
|
text: options?.colors ? `\x1b[2m[\x1b[0m` : "[",
|
|
547
572
|
width: 1
|
package/dist/usage.d.cts
CHANGED
|
@@ -104,6 +104,20 @@ type UsageTerm =
|
|
|
104
104
|
* in the command-line usage.
|
|
105
105
|
*/
|
|
106
106
|
readonly name: string;
|
|
107
|
+
/**
|
|
108
|
+
* Additional command names that invoke the same parser.
|
|
109
|
+
* These aliases participate in parsing, completion, and typo
|
|
110
|
+
* suggestions, but are not rendered in usage or documentation output.
|
|
111
|
+
* @since 1.1.0
|
|
112
|
+
*/
|
|
113
|
+
readonly aliases?: readonly string[];
|
|
114
|
+
/**
|
|
115
|
+
* Additional command names that invoke the same parser but are not
|
|
116
|
+
* rendered or suggested. They are still available to parsers and
|
|
117
|
+
* suggestion matchers so alias typos can resolve to the canonical command.
|
|
118
|
+
* @since 1.1.0
|
|
119
|
+
*/
|
|
120
|
+
readonly hiddenAliases?: readonly string[];
|
|
107
121
|
/**
|
|
108
122
|
* Optional usage line override for this command's own help page.
|
|
109
123
|
* This affects help/documentation rendering only.
|
|
@@ -164,6 +178,23 @@ type UsageTerm =
|
|
|
164
178
|
*/
|
|
165
179
|
readonly terms: readonly Usage[];
|
|
166
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* A sequence term, which preserves the declaration order of its child
|
|
183
|
+
* terms through usage normalization.
|
|
184
|
+
*
|
|
185
|
+
* This is used by ordered parser combinators where argument/command/option
|
|
186
|
+
* order is part of the accepted grammar.
|
|
187
|
+
* @since 1.1.0
|
|
188
|
+
*/ | {
|
|
189
|
+
/**
|
|
190
|
+
* The type of the term, which is always `"sequence"` for this term.
|
|
191
|
+
*/
|
|
192
|
+
readonly type: "sequence";
|
|
193
|
+
/**
|
|
194
|
+
* Terms that must be displayed in the given order.
|
|
195
|
+
*/
|
|
196
|
+
readonly terms: Usage;
|
|
197
|
+
}
|
|
167
198
|
/**
|
|
168
199
|
* A literal term, which represents a fixed string value in the command-line
|
|
169
200
|
* usage. Unlike metavars which are placeholders for user-provided values,
|
package/dist/usage.d.ts
CHANGED
|
@@ -104,6 +104,20 @@ type UsageTerm =
|
|
|
104
104
|
* in the command-line usage.
|
|
105
105
|
*/
|
|
106
106
|
readonly name: string;
|
|
107
|
+
/**
|
|
108
|
+
* Additional command names that invoke the same parser.
|
|
109
|
+
* These aliases participate in parsing, completion, and typo
|
|
110
|
+
* suggestions, but are not rendered in usage or documentation output.
|
|
111
|
+
* @since 1.1.0
|
|
112
|
+
*/
|
|
113
|
+
readonly aliases?: readonly string[];
|
|
114
|
+
/**
|
|
115
|
+
* Additional command names that invoke the same parser but are not
|
|
116
|
+
* rendered or suggested. They are still available to parsers and
|
|
117
|
+
* suggestion matchers so alias typos can resolve to the canonical command.
|
|
118
|
+
* @since 1.1.0
|
|
119
|
+
*/
|
|
120
|
+
readonly hiddenAliases?: readonly string[];
|
|
107
121
|
/**
|
|
108
122
|
* Optional usage line override for this command's own help page.
|
|
109
123
|
* This affects help/documentation rendering only.
|
|
@@ -164,6 +178,23 @@ type UsageTerm =
|
|
|
164
178
|
*/
|
|
165
179
|
readonly terms: readonly Usage[];
|
|
166
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* A sequence term, which preserves the declaration order of its child
|
|
183
|
+
* terms through usage normalization.
|
|
184
|
+
*
|
|
185
|
+
* This is used by ordered parser combinators where argument/command/option
|
|
186
|
+
* order is part of the accepted grammar.
|
|
187
|
+
* @since 1.1.0
|
|
188
|
+
*/ | {
|
|
189
|
+
/**
|
|
190
|
+
* The type of the term, which is always `"sequence"` for this term.
|
|
191
|
+
*/
|
|
192
|
+
readonly type: "sequence";
|
|
193
|
+
/**
|
|
194
|
+
* Terms that must be displayed in the given order.
|
|
195
|
+
*/
|
|
196
|
+
readonly terms: Usage;
|
|
197
|
+
}
|
|
167
198
|
/**
|
|
168
199
|
* A literal term, which represents a fixed string value in the command-line
|
|
169
200
|
* usage. Unlike metavars which are placeholders for user-provided values,
|
package/dist/usage.js
CHANGED
|
@@ -63,7 +63,7 @@ function extractOptionNames(usage, includeHidden) {
|
|
|
63
63
|
for (const term of terms) if (term.type === "option") {
|
|
64
64
|
if (!includeHidden && isSuggestionHidden(term.hidden)) continue;
|
|
65
65
|
for (const name of term.names) names.add(name);
|
|
66
|
-
} else if (term.type === "optional" || term.type === "multiple") traverseUsage(term.terms);
|
|
66
|
+
} else if (term.type === "optional" || term.type === "multiple" || term.type === "sequence") traverseUsage(term.terms);
|
|
67
67
|
else if (term.type === "exclusive") for (const exclusiveUsage of term.terms) traverseUsage(exclusiveUsage);
|
|
68
68
|
}
|
|
69
69
|
traverseUsage(usage);
|
|
@@ -98,7 +98,9 @@ function extractCommandNames(usage, includeHidden) {
|
|
|
98
98
|
for (const term of terms) if (term.type === "command") {
|
|
99
99
|
if (!includeHidden && isSuggestionHidden(term.hidden)) continue;
|
|
100
100
|
names.add(term.name);
|
|
101
|
-
|
|
101
|
+
for (const alias of term.aliases ?? []) names.add(alias);
|
|
102
|
+
if (includeHidden) for (const alias of term.hiddenAliases ?? []) names.add(alias);
|
|
103
|
+
} else if (term.type === "optional" || term.type === "multiple" || term.type === "sequence") traverseUsage(term.terms);
|
|
102
104
|
else if (term.type === "exclusive") for (const exclusiveUsage of term.terms) traverseUsage(exclusiveUsage);
|
|
103
105
|
}
|
|
104
106
|
traverseUsage(usage);
|
|
@@ -121,7 +123,7 @@ function extractLiteralValues(usage) {
|
|
|
121
123
|
function traverseUsage(terms) {
|
|
122
124
|
if (!terms || !Array.isArray(terms)) return;
|
|
123
125
|
for (const term of terms) if (term.type === "literal") values.add(term.value);
|
|
124
|
-
else if (term.type === "optional" || term.type === "multiple") traverseUsage(term.terms);
|
|
126
|
+
else if (term.type === "optional" || term.type === "multiple" || term.type === "sequence") traverseUsage(term.terms);
|
|
125
127
|
else if (term.type === "exclusive") for (const branch of term.terms) traverseUsage(branch);
|
|
126
128
|
}
|
|
127
129
|
traverseUsage(usage);
|
|
@@ -155,7 +157,7 @@ function extractArgumentMetavars(usage) {
|
|
|
155
157
|
for (const term of terms) if (term.type === "argument") {
|
|
156
158
|
if (isSuggestionHidden(term.hidden)) continue;
|
|
157
159
|
metavars.add(term.metavar);
|
|
158
|
-
} else if (term.type === "optional" || term.type === "multiple") traverseUsage(term.terms);
|
|
160
|
+
} else if (term.type === "optional" || term.type === "multiple" || term.type === "sequence") traverseUsage(term.terms);
|
|
159
161
|
else if (term.type === "exclusive") for (const exclusiveUsage of term.terms) traverseUsage(exclusiveUsage);
|
|
160
162
|
}
|
|
161
163
|
traverseUsage(usage);
|
|
@@ -282,6 +284,10 @@ function normalizeUsageTerm(term) {
|
|
|
282
284
|
terms: normalizeUsage(term.terms),
|
|
283
285
|
min: term.min
|
|
284
286
|
};
|
|
287
|
+
else if (term.type === "sequence") return {
|
|
288
|
+
type: "sequence",
|
|
289
|
+
terms: term.terms.map(normalizeUsageTerm).filter(isNonDegenerateTerm)
|
|
290
|
+
};
|
|
285
291
|
else if (term.type === "exclusive") {
|
|
286
292
|
const terms = [];
|
|
287
293
|
for (const usage of term.terms) {
|
|
@@ -314,7 +320,7 @@ function isNonDegenerateTerm(term) {
|
|
|
314
320
|
if (term.type === "option") return term.names.length > 0;
|
|
315
321
|
if (term.type === "command") return term.name !== "";
|
|
316
322
|
if (term.type === "argument") return term.metavar.length > 0;
|
|
317
|
-
if (term.type === "optional" || term.type === "multiple" || term.type === "exclusive") return term.terms.length > 0;
|
|
323
|
+
if (term.type === "optional" || term.type === "multiple" || term.type === "exclusive" || term.type === "sequence") return term.terms.length > 0;
|
|
318
324
|
return true;
|
|
319
325
|
}
|
|
320
326
|
function containsMalformedLeaf(usage) {
|
|
@@ -322,7 +328,7 @@ function containsMalformedLeaf(usage) {
|
|
|
322
328
|
if (term.type === "option" && term.names.length === 0) return true;
|
|
323
329
|
if (term.type === "command" && term.name === "") return true;
|
|
324
330
|
if (term.type === "argument" && term.metavar.length === 0) return true;
|
|
325
|
-
if (term.type === "optional" || term.type === "multiple") {
|
|
331
|
+
if (term.type === "optional" || term.type === "multiple" || term.type === "sequence") {
|
|
326
332
|
if (containsMalformedLeaf(term.terms)) return true;
|
|
327
333
|
}
|
|
328
334
|
if (term.type === "exclusive") {
|
|
@@ -350,9 +356,15 @@ function cloneUsageTerm(term) {
|
|
|
350
356
|
names: [...term.names]
|
|
351
357
|
};
|
|
352
358
|
case "command": {
|
|
353
|
-
if (term.usageLine == null || typeof term.usageLine === "function") return {
|
|
359
|
+
if (term.usageLine == null || typeof term.usageLine === "function") return {
|
|
360
|
+
...term,
|
|
361
|
+
...term.aliases != null ? { aliases: [...term.aliases] } : {},
|
|
362
|
+
...term.hiddenAliases != null ? { hiddenAliases: [...term.hiddenAliases] } : {}
|
|
363
|
+
};
|
|
354
364
|
return {
|
|
355
365
|
...term,
|
|
366
|
+
...term.aliases != null ? { aliases: [...term.aliases] } : {},
|
|
367
|
+
...term.hiddenAliases != null ? { hiddenAliases: [...term.hiddenAliases] } : {},
|
|
356
368
|
usageLine: term.usageLine.map(cloneUsageTerm)
|
|
357
369
|
};
|
|
358
370
|
}
|
|
@@ -369,6 +381,10 @@ function cloneUsageTerm(term) {
|
|
|
369
381
|
type: "exclusive",
|
|
370
382
|
terms: term.terms.map((u) => u.map(cloneUsageTerm))
|
|
371
383
|
};
|
|
384
|
+
case "sequence": return {
|
|
385
|
+
type: "sequence",
|
|
386
|
+
terms: term.terms.map(cloneUsageTerm)
|
|
387
|
+
};
|
|
372
388
|
case "literal":
|
|
373
389
|
case "passthrough":
|
|
374
390
|
case "ellipsis": return { ...term };
|
|
@@ -421,6 +437,14 @@ function filterUsageForDisplay(usage, isHidden = isUsageHidden) {
|
|
|
421
437
|
});
|
|
422
438
|
continue;
|
|
423
439
|
}
|
|
440
|
+
if (term.type === "sequence") {
|
|
441
|
+
const filtered = filterUsageForDisplay(term.terms, isHidden);
|
|
442
|
+
if (filtered.length > 0) terms.push({
|
|
443
|
+
type: "sequence",
|
|
444
|
+
terms: filtered
|
|
445
|
+
});
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
424
448
|
terms.push(term);
|
|
425
449
|
}
|
|
426
450
|
return terms;
|
|
@@ -541,7 +565,8 @@ function* formatUsageTermInternal(term, options) {
|
|
|
541
565
|
text: options?.colors ? `\x1b[2m)\x1b[0m` : ")",
|
|
542
566
|
width: 1
|
|
543
567
|
};
|
|
544
|
-
} else if (term.type === "
|
|
568
|
+
} else if (term.type === "sequence") yield* formatUsageTerms(term.terms, options);
|
|
569
|
+
else if (term.type === "multiple") {
|
|
545
570
|
if (term.min < 1) yield {
|
|
546
571
|
text: options?.colors ? `\x1b[2m[\x1b[0m` : "[",
|
|
547
572
|
width: 1
|
package/dist/validate.cjs
CHANGED
|
@@ -162,6 +162,7 @@ function validateContextIds(contexts) {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
//#endregion
|
|
165
|
+
exports.escapeControlChars = escapeControlChars;
|
|
165
166
|
exports.validateCommandNames = validateCommandNames;
|
|
166
167
|
exports.validateContextIds = validateContextIds;
|
|
167
168
|
exports.validateLabel = validateLabel;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
//#region src/validate.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Escapes control characters in a string for readable error messages.
|
|
4
|
+
*
|
|
5
|
+
* @param value The string to escape.
|
|
6
|
+
* @returns The escaped string with control characters replaced by escape
|
|
7
|
+
* sequences.
|
|
8
|
+
*/
|
|
9
|
+
declare function escapeControlChars(value: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Validates option names at runtime.
|
|
12
|
+
*
|
|
13
|
+
* @param names The option names to validate.
|
|
14
|
+
* @param label A human-readable label for error messages (e.g.,
|
|
15
|
+
* `"Option"`, `"Flag"`, `"Help option"`).
|
|
16
|
+
* @throws {TypeError} If the names array is empty, or any name is empty,
|
|
17
|
+
* lacks a valid prefix, or contains whitespace or control characters.
|
|
18
|
+
*/
|
|
19
|
+
declare function validateOptionNames(names: readonly string[], label: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* Validates command names at runtime.
|
|
22
|
+
*
|
|
23
|
+
* @param names The command names to validate.
|
|
24
|
+
* @param label A human-readable label for error messages (e.g.,
|
|
25
|
+
* `"Help command"`).
|
|
26
|
+
* @throws {TypeError} If the names array is empty, or any name is empty,
|
|
27
|
+
* whitespace-only, or contains whitespace or control characters.
|
|
28
|
+
*/
|
|
29
|
+
declare function validateCommandNames(names: readonly string[], label: string): void;
|
|
30
|
+
/**
|
|
31
|
+
* A meta entry describes one active meta feature for collision checking.
|
|
32
|
+
*
|
|
33
|
+
* The tuple elements are:
|
|
34
|
+
*
|
|
35
|
+
* 1. `kind` — `"command"` if this meta feature matches at `args[0]` only,
|
|
36
|
+
* or `"option"` if a lenient scanner matches the name anywhere in `argv`.
|
|
37
|
+
* 2. `label` — human-readable label for error messages (e.g., `"help option"`).
|
|
38
|
+
* 3. `names` — the configured name(s) for this meta feature.
|
|
39
|
+
* 4. `prefixMatch` — when `true`, the runtime also intercepts tokens
|
|
40
|
+
* starting with `name=` (e.g., `--completion=bash`). Only the
|
|
41
|
+
* completion option uses this form; help/version use exact matching.
|
|
42
|
+
*
|
|
43
|
+
* @since 1.0.0
|
|
44
|
+
*/
|
|
45
|
+
type MetaEntry = readonly [kind: "command" | "option", label: string, names: readonly string[], prefixMatch?: boolean];
|
|
46
|
+
/**
|
|
47
|
+
* Validates that there are no name collisions among active meta features
|
|
48
|
+
* (help, version, completion).
|
|
49
|
+
*
|
|
50
|
+
* User parser names are accepted even when they overlap with meta names.
|
|
51
|
+
* Runtime parsing resolves those cases parser-first so ordinary parser data
|
|
52
|
+
* can shadow built-in meta behavior.
|
|
53
|
+
*
|
|
54
|
+
* Meta-vs-meta collisions are always checked in a unified namespace,
|
|
55
|
+
* because a meta command named `"--help"` and a meta option named
|
|
56
|
+
* `"--help"` both compete for the same token.
|
|
57
|
+
*
|
|
58
|
+
* @param metaEntries Active meta feature entries annotated with their kind.
|
|
59
|
+
* @throws {TypeError} If any meta/meta collision or duplicate is detected.
|
|
60
|
+
* @since 1.0.0
|
|
61
|
+
*/
|
|
62
|
+
declare function validateMetaNameCollisions(metaEntries: readonly MetaEntry[]): void;
|
|
63
|
+
/**
|
|
64
|
+
* Validates a program name at runtime.
|
|
65
|
+
*
|
|
66
|
+
* Program names may contain spaces (e.g., file paths), but must not be empty,
|
|
67
|
+
* whitespace-only, or contain control characters.
|
|
68
|
+
*
|
|
69
|
+
* @param programName The program name to validate.
|
|
70
|
+
* @throws {TypeError} If the value is not a string, is empty,
|
|
71
|
+
* whitespace-only, or contains control characters.
|
|
72
|
+
*/
|
|
73
|
+
declare function validateProgramName(programName: string): void;
|
|
74
|
+
/**
|
|
75
|
+
* Validates a label at runtime.
|
|
76
|
+
*
|
|
77
|
+
* Labels are used as section titles in documentation output. They may contain
|
|
78
|
+
* spaces (e.g., "Connection options"), but must not be empty, whitespace-only,
|
|
79
|
+
* or contain control characters.
|
|
80
|
+
*
|
|
81
|
+
* @param label The label to validate.
|
|
82
|
+
* @throws {TypeError} If the label is not a string, is empty,
|
|
83
|
+
* whitespace-only, or contains control characters.
|
|
84
|
+
* @since 1.0.0
|
|
85
|
+
*/
|
|
86
|
+
declare function validateLabel(label: string): void;
|
|
87
|
+
/**
|
|
88
|
+
* Validates that all source contexts have unique
|
|
89
|
+
* {@link import("./context.ts").SourceContext.id | id} values.
|
|
90
|
+
*
|
|
91
|
+
* @param contexts The source contexts to validate.
|
|
92
|
+
* @throws {TypeError} If two or more contexts share the same id.
|
|
93
|
+
* @since 1.0.0
|
|
94
|
+
*/
|
|
95
|
+
declare function validateContextIds(contexts: readonly {
|
|
96
|
+
readonly id: symbol;
|
|
97
|
+
}[]): void;
|
|
98
|
+
//#endregion
|
|
99
|
+
export { MetaEntry, escapeControlChars, validateCommandNames, validateContextIds, validateLabel, validateMetaNameCollisions, validateOptionNames, validateProgramName };
|