@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/primitives.js
CHANGED
|
@@ -6,9 +6,10 @@ import { dispatchByMode, dispatchIterableByMode, wrapForMode } from "./internal/
|
|
|
6
6
|
import { getDefaultValuesFunction, getDependencyIds, getSnapshottedDefaultDependencyValues, isDerivedValueParser, suggestWithDependency } from "./internal/dependency.js";
|
|
7
7
|
import { replayDerivedParser, replayDerivedParserAsync } from "./dependency-runtime.js";
|
|
8
8
|
import { getWrappedChildParseState, getWrappedChildState, isAnnotationWrappedInitialState, normalizeInjectedAnnotationState } from "./annotation-state.js";
|
|
9
|
+
import { hiddenCommandAliasesKey } from "./internal/command-alias.js";
|
|
9
10
|
import { mergeChildExec, withChildContext, withChildExecPath } from "./execution-context.js";
|
|
10
11
|
import { completeOrExtractPhase2Seed, extractPhase2SeedKey } from "./phase2-seed.js";
|
|
11
|
-
import { DEFAULT_FIND_SIMILAR_OPTIONS, createErrorWithSuggestions, createSuggestionMessage, findSimilar } from "./suggestion.js";
|
|
12
|
+
import { DEFAULT_FIND_SIMILAR_OPTIONS, createErrorWithSuggestions, createSuggestionMessage, expandCommandAliasSuggestions, findSimilar } from "./suggestion.js";
|
|
12
13
|
import { extractLeadingCommandNames } from "./usage-internals.js";
|
|
13
14
|
import { extractDependencyMetadata } from "./dependency-metadata.js";
|
|
14
15
|
import { isValueParser } from "./valueparser.js";
|
|
@@ -23,6 +24,9 @@ function hasParsedOptionValue(state, valueParser) {
|
|
|
23
24
|
if (valueParser != null) return state != null && typeof state === "object" && "success" in state && typeof state.success === "boolean";
|
|
24
25
|
return state != null && "success" in state && state.success && state.value === true;
|
|
25
26
|
}
|
|
27
|
+
function isTerminalValueState(state) {
|
|
28
|
+
return state != null && typeof state === "object" && "success" in state && typeof state.success === "boolean";
|
|
29
|
+
}
|
|
26
30
|
/**
|
|
27
31
|
* Helper function to create the stored state for an option or argument value.
|
|
28
32
|
*
|
|
@@ -117,6 +121,9 @@ function constant(value) {
|
|
|
117
121
|
leadingNames: EMPTY_LEADING_NAMES,
|
|
118
122
|
acceptingAnyToken: false,
|
|
119
123
|
initialState: value,
|
|
124
|
+
canSkip() {
|
|
125
|
+
return true;
|
|
126
|
+
},
|
|
120
127
|
parse(context) {
|
|
121
128
|
return {
|
|
122
129
|
success: true,
|
|
@@ -174,6 +181,9 @@ function fail() {
|
|
|
174
181
|
leadingNames: EMPTY_LEADING_NAMES,
|
|
175
182
|
acceptingAnyToken: false,
|
|
176
183
|
initialState: void 0,
|
|
184
|
+
canSkip() {
|
|
185
|
+
return false;
|
|
186
|
+
},
|
|
177
187
|
parse(_context) {
|
|
178
188
|
return {
|
|
179
189
|
success: false,
|
|
@@ -431,6 +441,9 @@ function option(...args) {
|
|
|
431
441
|
success: true,
|
|
432
442
|
value: false
|
|
433
443
|
} : void 0,
|
|
444
|
+
canSkip(state) {
|
|
445
|
+
return valueParser == null || isTerminalValueState(state);
|
|
446
|
+
},
|
|
434
447
|
parse(context) {
|
|
435
448
|
if (context.optionsTerminated) return {
|
|
436
449
|
success: false,
|
|
@@ -686,6 +699,7 @@ function option(...args) {
|
|
|
686
699
|
};
|
|
687
700
|
Object.defineProperty(result, "validateValue", {
|
|
688
701
|
value(v) {
|
|
702
|
+
if (typeof vp.validate === "function") return wrapForMode(mode, wrapParseResult(vp.validate(v)));
|
|
689
703
|
let stringified;
|
|
690
704
|
try {
|
|
691
705
|
stringified = vp.format(v);
|
|
@@ -782,6 +796,9 @@ function flag(...args) {
|
|
|
782
796
|
leadingNames: new Set(optionNames$1),
|
|
783
797
|
acceptingAnyToken: false,
|
|
784
798
|
initialState: void 0,
|
|
799
|
+
canSkip(state) {
|
|
800
|
+
return isTerminalValueState(state);
|
|
801
|
+
},
|
|
785
802
|
parse(context) {
|
|
786
803
|
if (context.optionsTerminated) return {
|
|
787
804
|
success: false,
|
|
@@ -1013,6 +1030,9 @@ function negatableFlag(names, options = {}) {
|
|
|
1013
1030
|
leadingNames: new Set(optionNames$1),
|
|
1014
1031
|
acceptingAnyToken: false,
|
|
1015
1032
|
initialState: void 0,
|
|
1033
|
+
canSkip(state) {
|
|
1034
|
+
return state != null;
|
|
1035
|
+
},
|
|
1016
1036
|
parse(context) {
|
|
1017
1037
|
if (context.optionsTerminated) return {
|
|
1018
1038
|
success: false,
|
|
@@ -1177,6 +1197,9 @@ function argument(valueParser, options = {}) {
|
|
|
1177
1197
|
leadingNames: EMPTY_LEADING_NAMES,
|
|
1178
1198
|
acceptingAnyToken: true,
|
|
1179
1199
|
initialState: void 0,
|
|
1200
|
+
canSkip(state) {
|
|
1201
|
+
return isTerminalValueState(state);
|
|
1202
|
+
},
|
|
1180
1203
|
parse(context) {
|
|
1181
1204
|
const localState = normalizeInjectedAnnotationState(context.state);
|
|
1182
1205
|
if (context.buffer.length < 1) return {
|
|
@@ -1312,6 +1335,7 @@ function argument(valueParser, options = {}) {
|
|
|
1312
1335
|
};
|
|
1313
1336
|
Object.defineProperty(result, "validateValue", {
|
|
1314
1337
|
value(v) {
|
|
1338
|
+
if (typeof vp.validate === "function") return wrapForMode(vpMode, wrapParseResult(vp.validate(v)));
|
|
1315
1339
|
let stringified;
|
|
1316
1340
|
try {
|
|
1317
1341
|
stringified = vp.format(v);
|
|
@@ -1368,25 +1392,56 @@ function appendCommandPath(exec, name) {
|
|
|
1368
1392
|
commandPath: [...exec.commandPath ?? [], name]
|
|
1369
1393
|
};
|
|
1370
1394
|
}
|
|
1371
|
-
function
|
|
1395
|
+
function getCommandNames(name, options) {
|
|
1396
|
+
return [
|
|
1397
|
+
name,
|
|
1398
|
+
...getVisibleCommandAliases(options),
|
|
1399
|
+
...getHiddenCommandAliases(options)
|
|
1400
|
+
];
|
|
1401
|
+
}
|
|
1402
|
+
function getVisibleCommandAliases(options) {
|
|
1403
|
+
const aliases = options.aliases;
|
|
1404
|
+
if (aliases == null) return [];
|
|
1405
|
+
if (!isNonEmptyStringArray(aliases)) throw new TypeError("Command aliases must be a non-empty array of strings.");
|
|
1406
|
+
return aliases;
|
|
1407
|
+
}
|
|
1408
|
+
function getHiddenCommandAliases(options) {
|
|
1409
|
+
const hiddenAliases = options[hiddenCommandAliasesKey];
|
|
1410
|
+
if (hiddenAliases == null) return [];
|
|
1411
|
+
if (!isNonEmptyStringArray(hiddenAliases)) throw new TypeError("Hidden command aliases must be a non-empty array of strings.");
|
|
1412
|
+
return hiddenAliases;
|
|
1413
|
+
}
|
|
1414
|
+
function isNonEmptyStringArray(value) {
|
|
1415
|
+
if (!Array.isArray(value) || value.length === 0) return false;
|
|
1416
|
+
for (let i = 0; i < value.length; i++) if (typeof value[i] !== "string") return false;
|
|
1417
|
+
return true;
|
|
1418
|
+
}
|
|
1419
|
+
function validateUniqueCommandNames(names) {
|
|
1420
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1421
|
+
for (const name of names) {
|
|
1422
|
+
if (seen.has(name)) throw new TypeError(`Command has a duplicate name: "${name}".`);
|
|
1423
|
+
seen.add(name);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
function* suggestCommandSync(context, prefix, name, aliases, parser, options) {
|
|
1372
1427
|
if (isSuggestionHidden(options.hidden)) return;
|
|
1373
1428
|
const state = normalizeCommandState(context.state);
|
|
1374
1429
|
if (state === void 0) {
|
|
1375
|
-
if (
|
|
1430
|
+
for (const commandName of [name, ...aliases]) if (commandName.startsWith(prefix)) yield {
|
|
1376
1431
|
kind: "literal",
|
|
1377
|
-
text:
|
|
1432
|
+
text: commandName,
|
|
1378
1433
|
...options.description && { description: options.description }
|
|
1379
1434
|
};
|
|
1380
1435
|
} else if (state[0] === "matched") yield* parser.suggest(withChildContext(context, name, getCommandChildState(context.state, parser.initialState, parser), parser.usage), prefix);
|
|
1381
1436
|
else if (state[0] === "parsing") yield* parser.suggest(withChildContext(context, name, getCommandChildState(context.state, state[1], parser), parser.usage), prefix);
|
|
1382
1437
|
}
|
|
1383
|
-
async function* suggestCommandAsync(context, prefix, name, parser, options) {
|
|
1438
|
+
async function* suggestCommandAsync(context, prefix, name, aliases, parser, options) {
|
|
1384
1439
|
if (isSuggestionHidden(options.hidden)) return;
|
|
1385
1440
|
const state = normalizeCommandState(context.state);
|
|
1386
1441
|
if (state === void 0) {
|
|
1387
|
-
if (
|
|
1442
|
+
for (const commandName of [name, ...aliases]) if (commandName.startsWith(prefix)) yield {
|
|
1388
1443
|
kind: "literal",
|
|
1389
|
-
text:
|
|
1444
|
+
text: commandName,
|
|
1390
1445
|
...options.description && { description: options.description }
|
|
1391
1446
|
};
|
|
1392
1447
|
} else if (state[0] === "matched") {
|
|
@@ -1414,7 +1469,11 @@ async function* suggestCommandAsync(context, prefix, name, parser, options) {
|
|
|
1414
1469
|
* embedded whitespace, or contains control characters.
|
|
1415
1470
|
*/
|
|
1416
1471
|
function command(name, parser, options = {}) {
|
|
1417
|
-
|
|
1472
|
+
const commandNames = getCommandNames(name, options);
|
|
1473
|
+
const aliases = getVisibleCommandAliases(options);
|
|
1474
|
+
const hiddenAliases = getHiddenCommandAliases(options);
|
|
1475
|
+
validateCommandNames(commandNames, "Command");
|
|
1476
|
+
validateUniqueCommandNames(commandNames);
|
|
1418
1477
|
const isAsync = parser.mode === "async";
|
|
1419
1478
|
const syncInnerParser = parser;
|
|
1420
1479
|
const asyncInnerParser = parser;
|
|
@@ -1427,12 +1486,20 @@ function command(name, parser, options = {}) {
|
|
|
1427
1486
|
usage: [{
|
|
1428
1487
|
type: "command",
|
|
1429
1488
|
name,
|
|
1489
|
+
...aliases.length > 0 && { aliases },
|
|
1490
|
+
...hiddenAliases.length > 0 && { hiddenAliases },
|
|
1430
1491
|
...options.usageLine != null && { usageLine: options.usageLine },
|
|
1431
1492
|
...options.hidden != null && { hidden: options.hidden }
|
|
1432
1493
|
}, ...parser.usage],
|
|
1433
|
-
leadingNames: new Set(
|
|
1494
|
+
leadingNames: new Set(commandNames),
|
|
1434
1495
|
acceptingAnyToken: false,
|
|
1435
1496
|
initialState: void 0,
|
|
1497
|
+
canSkip(state, exec) {
|
|
1498
|
+
const normalizedState = normalizeCommandState(state);
|
|
1499
|
+
if (normalizedState === void 0) return false;
|
|
1500
|
+
if (normalizedState[0] === "matched") return parser.canSkip?.(getCommandChildState(state, parser.initialState, parser), withChildExecPath(exec, name)) === true;
|
|
1501
|
+
return parser.canSkip?.(getCommandChildState(state, normalizedState[1], parser), withChildExecPath(exec, name)) === true;
|
|
1502
|
+
},
|
|
1436
1503
|
getSuggestRuntimeNodes(state, path) {
|
|
1437
1504
|
const normalizedState = normalizeCommandState(state);
|
|
1438
1505
|
if (normalizedState === void 0) return [];
|
|
@@ -1447,10 +1514,11 @@ function command(name, parser, options = {}) {
|
|
|
1447
1514
|
parse(context) {
|
|
1448
1515
|
const state = normalizeCommandState(context.state);
|
|
1449
1516
|
if (state === void 0) {
|
|
1450
|
-
if (context.buffer.length < 1 || context.buffer[0]
|
|
1517
|
+
if (context.buffer.length < 1 || !commandNames.includes(context.buffer[0])) {
|
|
1451
1518
|
const actual = context.buffer.length > 0 ? context.buffer[0] : null;
|
|
1452
1519
|
const leadingCmds = extractLeadingCommandNames(context.usage);
|
|
1453
|
-
const
|
|
1520
|
+
const rawSuggestions = actual ? findSimilar(actual, leadingCmds, DEFAULT_FIND_SIMILAR_OPTIONS) : [];
|
|
1521
|
+
const suggestions = expandCommandAliasSuggestions(context.usage, rawSuggestions);
|
|
1454
1522
|
if (options.errors?.notMatched) {
|
|
1455
1523
|
const errorMessage = options.errors.notMatched;
|
|
1456
1524
|
return {
|
|
@@ -1581,8 +1649,8 @@ function command(name, parser, options = {}) {
|
|
|
1581
1649
|
return wrapForMode(parser.mode, null);
|
|
1582
1650
|
},
|
|
1583
1651
|
suggest(context, prefix) {
|
|
1584
|
-
if (isAsync) return suggestCommandAsync(context, prefix, name, parser, options);
|
|
1585
|
-
return suggestCommandSync(context, prefix, name, parser, options);
|
|
1652
|
+
if (isAsync) return suggestCommandAsync(context, prefix, name, aliases, parser, options);
|
|
1653
|
+
return suggestCommandSync(context, prefix, name, aliases, parser, options);
|
|
1586
1654
|
},
|
|
1587
1655
|
getDocFragments(state, defaultValue) {
|
|
1588
1656
|
const commandState = state.kind === "available" ? normalizeCommandState(state.state) : void 0;
|
|
@@ -1696,6 +1764,9 @@ function passThrough(options = {}) {
|
|
|
1696
1764
|
leadingNames: EMPTY_LEADING_NAMES,
|
|
1697
1765
|
acceptingAnyToken: false,
|
|
1698
1766
|
initialState: [],
|
|
1767
|
+
canSkip() {
|
|
1768
|
+
return true;
|
|
1769
|
+
},
|
|
1699
1770
|
parse(context) {
|
|
1700
1771
|
if (context.buffer.length < 1) return {
|
|
1701
1772
|
success: false,
|
package/dist/suggestion.cjs
CHANGED
|
@@ -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 (require_usage.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 require_usage.extractOptionNames(usage)) candidates.add(name);
|
|
176
243
|
if (type === "command" || type === "both") for (const name of require_usage.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
|
require_message.text("\n\n"),
|
|
@@ -248,4 +316,6 @@ exports.DEFAULT_FIND_SIMILAR_OPTIONS = DEFAULT_FIND_SIMILAR_OPTIONS;
|
|
|
248
316
|
exports.createErrorWithSuggestions = createErrorWithSuggestions;
|
|
249
317
|
exports.createSuggestionMessage = createSuggestionMessage;
|
|
250
318
|
exports.deduplicateSuggestions = deduplicateSuggestions;
|
|
251
|
-
exports.
|
|
319
|
+
exports.expandCommandAliasSuggestions = expandCommandAliasSuggestions;
|
|
320
|
+
exports.findSimilar = findSimilar;
|
|
321
|
+
exports.levenshteinDistance = levenshteinDistance;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { Message } from "./message.cjs";
|
|
2
|
+
import { Usage } from "./usage.cjs";
|
|
3
|
+
import { Suggestion } from "./internal/parser.cjs";
|
|
4
|
+
|
|
5
|
+
//#region src/suggestion.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Calculates the Levenshtein distance between two strings.
|
|
9
|
+
*
|
|
10
|
+
* The Levenshtein distance is the minimum number of single-character edits
|
|
11
|
+
* (insertions, deletions, or substitutions) required to transform one string
|
|
12
|
+
* into another.
|
|
13
|
+
*
|
|
14
|
+
* @param source The source string
|
|
15
|
+
* @param target The target string
|
|
16
|
+
* @returns The edit distance (number of insertions, deletions, substitutions)
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* levenshteinDistance("kitten", "sitting"); // returns 3
|
|
21
|
+
* levenshteinDistance("--verbos", "--verbose"); // returns 1
|
|
22
|
+
* levenshteinDistance("hello", "hello"); // returns 0
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
declare function levenshteinDistance(source: string, target: string): number;
|
|
26
|
+
/**
|
|
27
|
+
* Options for finding similar strings.
|
|
28
|
+
*/
|
|
29
|
+
interface FindSimilarOptions {
|
|
30
|
+
/**
|
|
31
|
+
* Maximum edit distance to consider a match.
|
|
32
|
+
* Strings with a distance greater than this value will not be suggested.
|
|
33
|
+
* @default 3
|
|
34
|
+
*/
|
|
35
|
+
readonly maxDistance?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Maximum distance ratio (distance / input length).
|
|
38
|
+
* Prevents suggesting long strings for very short inputs.
|
|
39
|
+
* For example, with maxDistanceRatio=0.5, an input of length 2
|
|
40
|
+
* will only suggest strings within distance 1.
|
|
41
|
+
* @default 0.5
|
|
42
|
+
*/
|
|
43
|
+
readonly maxDistanceRatio?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Maximum number of suggestions to return.
|
|
46
|
+
* @default 3
|
|
47
|
+
*/
|
|
48
|
+
readonly maxSuggestions?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Case-sensitive comparison.
|
|
51
|
+
* If false, strings are compared case-insensitively.
|
|
52
|
+
* @default false
|
|
53
|
+
*/
|
|
54
|
+
readonly caseSensitive?: boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Default options for finding similar strings.
|
|
58
|
+
* These values are optimized for command-line option/command name suggestions.
|
|
59
|
+
*
|
|
60
|
+
* @since 0.7.0
|
|
61
|
+
*/
|
|
62
|
+
declare const DEFAULT_FIND_SIMILAR_OPTIONS: Required<FindSimilarOptions>;
|
|
63
|
+
/**
|
|
64
|
+
* Finds similar strings from a list of candidates.
|
|
65
|
+
*
|
|
66
|
+
* This function uses Levenshtein distance to find strings that are similar
|
|
67
|
+
* to the input string. Results are sorted by similarity (most similar first).
|
|
68
|
+
*
|
|
69
|
+
* @param input The input string to find matches for
|
|
70
|
+
* @param candidates List of candidate strings to compare against
|
|
71
|
+
* @param options Configuration options
|
|
72
|
+
* @returns Array of similar strings, sorted by similarity (most similar first)
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```typescript
|
|
76
|
+
* const candidates = ["--verbose", "--version", "--verify", "--help"];
|
|
77
|
+
* findSimilar("--verbos", candidates);
|
|
78
|
+
* // returns ["--verbose"]
|
|
79
|
+
*
|
|
80
|
+
* findSimilar("--ver", candidates, { maxDistance: 5 });
|
|
81
|
+
* // returns ["--verify", "--version", "--verbose"]
|
|
82
|
+
*
|
|
83
|
+
* findSimilar("--xyz", candidates);
|
|
84
|
+
* // returns [] (no similar matches)
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
declare function findSimilar(input: string, candidates: Iterable<string>, options?: FindSimilarOptions): string[];
|
|
88
|
+
/**
|
|
89
|
+
* Creates a suggestion message for a mismatched option/command.
|
|
90
|
+
*
|
|
91
|
+
* This function formats suggestions in a user-friendly way:
|
|
92
|
+
* - No suggestions: returns empty message
|
|
93
|
+
* - One suggestion: "Did you mean `option`?"
|
|
94
|
+
* - Multiple suggestions: "Did you mean one of these?\n option1\n option2"
|
|
95
|
+
*
|
|
96
|
+
* @param suggestions List of similar valid options/commands
|
|
97
|
+
* @returns A Message array with suggestion text
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```typescript
|
|
101
|
+
* createSuggestionMessage(["--verbose", "--version"]);
|
|
102
|
+
* // returns message parts for:
|
|
103
|
+
* // "Did you mean one of these?
|
|
104
|
+
* // --verbose
|
|
105
|
+
* // --version"
|
|
106
|
+
*
|
|
107
|
+
* createSuggestionMessage(["--verbose"]);
|
|
108
|
+
* // returns message parts for:
|
|
109
|
+
* // "Did you mean `--verbose`?"
|
|
110
|
+
*
|
|
111
|
+
* createSuggestionMessage([]);
|
|
112
|
+
* // returns []
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
declare function createSuggestionMessage(suggestions: readonly string[]): Message;
|
|
116
|
+
/**
|
|
117
|
+
* Expands command alias suggestions so an alias typo can point at both the
|
|
118
|
+
* canonical command and the alias that matched.
|
|
119
|
+
*
|
|
120
|
+
* @param usage Usage terms that define command aliases.
|
|
121
|
+
* @param suggestions Candidate suggestions returned by {@link findSimilar}.
|
|
122
|
+
* @returns Suggestions with alias hits expanded to canonical name + alias.
|
|
123
|
+
* @internal
|
|
124
|
+
*/
|
|
125
|
+
declare function expandCommandAliasSuggestions(usage: Usage, suggestions: readonly string[]): readonly string[];
|
|
126
|
+
/**
|
|
127
|
+
* Creates an error message with suggestions for similar options or commands.
|
|
128
|
+
*
|
|
129
|
+
* This is a convenience function that combines the functionality of
|
|
130
|
+
* `findSimilar()` and `createSuggestionMessage()` to generate user-friendly
|
|
131
|
+
* error messages with "Did you mean?" suggestions.
|
|
132
|
+
*
|
|
133
|
+
* @param baseError The base error message to display
|
|
134
|
+
* @param invalidInput The invalid option or command name that the user typed
|
|
135
|
+
* @param usage The usage information to extract available options/commands from
|
|
136
|
+
* @param type What type of names to suggest ("option", "command", or "both")
|
|
137
|
+
* @param customFormatter Optional custom function to format suggestions instead
|
|
138
|
+
* of using the default "Did you mean?" formatting
|
|
139
|
+
* @returns A message combining the base error with suggestions, or just the
|
|
140
|
+
* base error if no similar names are found
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```typescript
|
|
144
|
+
* const baseError = message`No matched option for ${optionName("--verbos")}.`;
|
|
145
|
+
* const error = createErrorWithSuggestions(
|
|
146
|
+
* baseError,
|
|
147
|
+
* "--verbos",
|
|
148
|
+
* context.usage,
|
|
149
|
+
* "option"
|
|
150
|
+
* );
|
|
151
|
+
* // Returns: "No matched option for `--verbos`.\nDid you mean `--verbose`?"
|
|
152
|
+
* ```
|
|
153
|
+
*
|
|
154
|
+
* @since 0.7.0
|
|
155
|
+
*/
|
|
156
|
+
declare function createErrorWithSuggestions(baseError: Message, invalidInput: string, usage: Usage, type?: "option" | "command" | "both", customFormatter?: (suggestions: readonly string[]) => Message): Message;
|
|
157
|
+
/**
|
|
158
|
+
* Removes duplicate suggestions from an array while preserving order.
|
|
159
|
+
*
|
|
160
|
+
* Suggestions are considered duplicates if they have the same key:
|
|
161
|
+
* - Literal suggestions: same text
|
|
162
|
+
* - File suggestions: same type, extensions, and pattern
|
|
163
|
+
*
|
|
164
|
+
* The first occurrence of each unique suggestion is kept. For file
|
|
165
|
+
* suggestions, `includeHidden` is merged across duplicates: if any
|
|
166
|
+
* duplicate has `includeHidden: true`, the kept suggestion is upgraded
|
|
167
|
+
* to `includeHidden: true` because it is a superset of the non-hidden
|
|
168
|
+
* variant.
|
|
169
|
+
*
|
|
170
|
+
* @param suggestions Array of suggestions that may contain duplicates
|
|
171
|
+
* @returns A new array with duplicates removed, preserving order of first occurrences
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* const suggestions = [
|
|
176
|
+
* { kind: "literal", text: "--verbose" },
|
|
177
|
+
* { kind: "literal", text: "--help" },
|
|
178
|
+
* { kind: "literal", text: "--verbose" }, // duplicate
|
|
179
|
+
* ];
|
|
180
|
+
* deduplicateSuggestions(suggestions);
|
|
181
|
+
* // returns [{ kind: "literal", text: "--verbose" }, { kind: "literal", text: "--help" }]
|
|
182
|
+
* ```
|
|
183
|
+
*
|
|
184
|
+
* @since 0.9.0
|
|
185
|
+
*/
|
|
186
|
+
declare function deduplicateSuggestions(suggestions: readonly Suggestion[]): Suggestion[];
|
|
187
|
+
//#endregion
|
|
188
|
+
export { DEFAULT_FIND_SIMILAR_OPTIONS, FindSimilarOptions, createErrorWithSuggestions, createSuggestionMessage, deduplicateSuggestions, expandCommandAliasSuggestions, findSimilar, levenshteinDistance };
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { Message } from "./message.js";
|
|
2
|
+
import { Usage } from "./usage.js";
|
|
3
|
+
import { Suggestion } from "./internal/parser.js";
|
|
4
|
+
|
|
5
|
+
//#region src/suggestion.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Calculates the Levenshtein distance between two strings.
|
|
9
|
+
*
|
|
10
|
+
* The Levenshtein distance is the minimum number of single-character edits
|
|
11
|
+
* (insertions, deletions, or substitutions) required to transform one string
|
|
12
|
+
* into another.
|
|
13
|
+
*
|
|
14
|
+
* @param source The source string
|
|
15
|
+
* @param target The target string
|
|
16
|
+
* @returns The edit distance (number of insertions, deletions, substitutions)
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* levenshteinDistance("kitten", "sitting"); // returns 3
|
|
21
|
+
* levenshteinDistance("--verbos", "--verbose"); // returns 1
|
|
22
|
+
* levenshteinDistance("hello", "hello"); // returns 0
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
declare function levenshteinDistance(source: string, target: string): number;
|
|
26
|
+
/**
|
|
27
|
+
* Options for finding similar strings.
|
|
28
|
+
*/
|
|
29
|
+
interface FindSimilarOptions {
|
|
30
|
+
/**
|
|
31
|
+
* Maximum edit distance to consider a match.
|
|
32
|
+
* Strings with a distance greater than this value will not be suggested.
|
|
33
|
+
* @default 3
|
|
34
|
+
*/
|
|
35
|
+
readonly maxDistance?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Maximum distance ratio (distance / input length).
|
|
38
|
+
* Prevents suggesting long strings for very short inputs.
|
|
39
|
+
* For example, with maxDistanceRatio=0.5, an input of length 2
|
|
40
|
+
* will only suggest strings within distance 1.
|
|
41
|
+
* @default 0.5
|
|
42
|
+
*/
|
|
43
|
+
readonly maxDistanceRatio?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Maximum number of suggestions to return.
|
|
46
|
+
* @default 3
|
|
47
|
+
*/
|
|
48
|
+
readonly maxSuggestions?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Case-sensitive comparison.
|
|
51
|
+
* If false, strings are compared case-insensitively.
|
|
52
|
+
* @default false
|
|
53
|
+
*/
|
|
54
|
+
readonly caseSensitive?: boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Default options for finding similar strings.
|
|
58
|
+
* These values are optimized for command-line option/command name suggestions.
|
|
59
|
+
*
|
|
60
|
+
* @since 0.7.0
|
|
61
|
+
*/
|
|
62
|
+
declare const DEFAULT_FIND_SIMILAR_OPTIONS: Required<FindSimilarOptions>;
|
|
63
|
+
/**
|
|
64
|
+
* Finds similar strings from a list of candidates.
|
|
65
|
+
*
|
|
66
|
+
* This function uses Levenshtein distance to find strings that are similar
|
|
67
|
+
* to the input string. Results are sorted by similarity (most similar first).
|
|
68
|
+
*
|
|
69
|
+
* @param input The input string to find matches for
|
|
70
|
+
* @param candidates List of candidate strings to compare against
|
|
71
|
+
* @param options Configuration options
|
|
72
|
+
* @returns Array of similar strings, sorted by similarity (most similar first)
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```typescript
|
|
76
|
+
* const candidates = ["--verbose", "--version", "--verify", "--help"];
|
|
77
|
+
* findSimilar("--verbos", candidates);
|
|
78
|
+
* // returns ["--verbose"]
|
|
79
|
+
*
|
|
80
|
+
* findSimilar("--ver", candidates, { maxDistance: 5 });
|
|
81
|
+
* // returns ["--verify", "--version", "--verbose"]
|
|
82
|
+
*
|
|
83
|
+
* findSimilar("--xyz", candidates);
|
|
84
|
+
* // returns [] (no similar matches)
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
declare function findSimilar(input: string, candidates: Iterable<string>, options?: FindSimilarOptions): string[];
|
|
88
|
+
/**
|
|
89
|
+
* Creates a suggestion message for a mismatched option/command.
|
|
90
|
+
*
|
|
91
|
+
* This function formats suggestions in a user-friendly way:
|
|
92
|
+
* - No suggestions: returns empty message
|
|
93
|
+
* - One suggestion: "Did you mean `option`?"
|
|
94
|
+
* - Multiple suggestions: "Did you mean one of these?\n option1\n option2"
|
|
95
|
+
*
|
|
96
|
+
* @param suggestions List of similar valid options/commands
|
|
97
|
+
* @returns A Message array with suggestion text
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```typescript
|
|
101
|
+
* createSuggestionMessage(["--verbose", "--version"]);
|
|
102
|
+
* // returns message parts for:
|
|
103
|
+
* // "Did you mean one of these?
|
|
104
|
+
* // --verbose
|
|
105
|
+
* // --version"
|
|
106
|
+
*
|
|
107
|
+
* createSuggestionMessage(["--verbose"]);
|
|
108
|
+
* // returns message parts for:
|
|
109
|
+
* // "Did you mean `--verbose`?"
|
|
110
|
+
*
|
|
111
|
+
* createSuggestionMessage([]);
|
|
112
|
+
* // returns []
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
declare function createSuggestionMessage(suggestions: readonly string[]): Message;
|
|
116
|
+
/**
|
|
117
|
+
* Expands command alias suggestions so an alias typo can point at both the
|
|
118
|
+
* canonical command and the alias that matched.
|
|
119
|
+
*
|
|
120
|
+
* @param usage Usage terms that define command aliases.
|
|
121
|
+
* @param suggestions Candidate suggestions returned by {@link findSimilar}.
|
|
122
|
+
* @returns Suggestions with alias hits expanded to canonical name + alias.
|
|
123
|
+
* @internal
|
|
124
|
+
*/
|
|
125
|
+
declare function expandCommandAliasSuggestions(usage: Usage, suggestions: readonly string[]): readonly string[];
|
|
126
|
+
/**
|
|
127
|
+
* Creates an error message with suggestions for similar options or commands.
|
|
128
|
+
*
|
|
129
|
+
* This is a convenience function that combines the functionality of
|
|
130
|
+
* `findSimilar()` and `createSuggestionMessage()` to generate user-friendly
|
|
131
|
+
* error messages with "Did you mean?" suggestions.
|
|
132
|
+
*
|
|
133
|
+
* @param baseError The base error message to display
|
|
134
|
+
* @param invalidInput The invalid option or command name that the user typed
|
|
135
|
+
* @param usage The usage information to extract available options/commands from
|
|
136
|
+
* @param type What type of names to suggest ("option", "command", or "both")
|
|
137
|
+
* @param customFormatter Optional custom function to format suggestions instead
|
|
138
|
+
* of using the default "Did you mean?" formatting
|
|
139
|
+
* @returns A message combining the base error with suggestions, or just the
|
|
140
|
+
* base error if no similar names are found
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```typescript
|
|
144
|
+
* const baseError = message`No matched option for ${optionName("--verbos")}.`;
|
|
145
|
+
* const error = createErrorWithSuggestions(
|
|
146
|
+
* baseError,
|
|
147
|
+
* "--verbos",
|
|
148
|
+
* context.usage,
|
|
149
|
+
* "option"
|
|
150
|
+
* );
|
|
151
|
+
* // Returns: "No matched option for `--verbos`.\nDid you mean `--verbose`?"
|
|
152
|
+
* ```
|
|
153
|
+
*
|
|
154
|
+
* @since 0.7.0
|
|
155
|
+
*/
|
|
156
|
+
declare function createErrorWithSuggestions(baseError: Message, invalidInput: string, usage: Usage, type?: "option" | "command" | "both", customFormatter?: (suggestions: readonly string[]) => Message): Message;
|
|
157
|
+
/**
|
|
158
|
+
* Removes duplicate suggestions from an array while preserving order.
|
|
159
|
+
*
|
|
160
|
+
* Suggestions are considered duplicates if they have the same key:
|
|
161
|
+
* - Literal suggestions: same text
|
|
162
|
+
* - File suggestions: same type, extensions, and pattern
|
|
163
|
+
*
|
|
164
|
+
* The first occurrence of each unique suggestion is kept. For file
|
|
165
|
+
* suggestions, `includeHidden` is merged across duplicates: if any
|
|
166
|
+
* duplicate has `includeHidden: true`, the kept suggestion is upgraded
|
|
167
|
+
* to `includeHidden: true` because it is a superset of the non-hidden
|
|
168
|
+
* variant.
|
|
169
|
+
*
|
|
170
|
+
* @param suggestions Array of suggestions that may contain duplicates
|
|
171
|
+
* @returns A new array with duplicates removed, preserving order of first occurrences
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* const suggestions = [
|
|
176
|
+
* { kind: "literal", text: "--verbose" },
|
|
177
|
+
* { kind: "literal", text: "--help" },
|
|
178
|
+
* { kind: "literal", text: "--verbose" }, // duplicate
|
|
179
|
+
* ];
|
|
180
|
+
* deduplicateSuggestions(suggestions);
|
|
181
|
+
* // returns [{ kind: "literal", text: "--verbose" }, { kind: "literal", text: "--help" }]
|
|
182
|
+
* ```
|
|
183
|
+
*
|
|
184
|
+
* @since 0.9.0
|
|
185
|
+
*/
|
|
186
|
+
declare function deduplicateSuggestions(suggestions: readonly Suggestion[]): Suggestion[];
|
|
187
|
+
//#endregion
|
|
188
|
+
export { DEFAULT_FIND_SIMILAR_OPTIONS, FindSimilarOptions, createErrorWithSuggestions, createSuggestionMessage, deduplicateSuggestions, expandCommandAliasSuggestions, findSimilar, levenshteinDistance };
|