@optique/core 0.8.10 → 0.8.12
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/constructs.cjs +63 -12
- package/dist/constructs.js +65 -14
- package/dist/facade.cjs +22 -5
- package/dist/facade.js +22 -5
- package/dist/suggestion.cjs +1 -0
- package/dist/suggestion.js +1 -1
- package/package.json +1 -1
package/dist/constructs.cjs
CHANGED
|
@@ -4,6 +4,57 @@ const require_suggestion = require('./suggestion.cjs');
|
|
|
4
4
|
|
|
5
5
|
//#region src/constructs.ts
|
|
6
6
|
/**
|
|
7
|
+
* Collects option names and command names that are valid at the current
|
|
8
|
+
* parse position by walking the usage tree. Only "leading" candidates
|
|
9
|
+
* (those reachable before a required positional argument) are collected.
|
|
10
|
+
*/
|
|
11
|
+
function collectLeadingCandidates(terms, optionNames, commandNames) {
|
|
12
|
+
if (!terms || !Array.isArray(terms)) return true;
|
|
13
|
+
for (const term of terms) {
|
|
14
|
+
if (term.type === "option") {
|
|
15
|
+
for (const name of term.names) optionNames.add(name);
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (term.type === "command") {
|
|
19
|
+
commandNames.add(term.name);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if (term.type === "argument") return false;
|
|
23
|
+
if (term.type === "optional") {
|
|
24
|
+
collectLeadingCandidates(term.terms, optionNames, commandNames);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (term.type === "multiple") {
|
|
28
|
+
collectLeadingCandidates(term.terms, optionNames, commandNames);
|
|
29
|
+
if (term.min === 0) continue;
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (term.type === "exclusive") {
|
|
33
|
+
let allAlternativesSkippable = true;
|
|
34
|
+
for (const exclusiveUsage of term.terms) {
|
|
35
|
+
const alternativeSkippable = collectLeadingCandidates(exclusiveUsage, optionNames, commandNames);
|
|
36
|
+
allAlternativesSkippable = allAlternativesSkippable && alternativeSkippable;
|
|
37
|
+
}
|
|
38
|
+
if (allAlternativesSkippable) continue;
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
function createUnexpectedInputErrorWithScopedSuggestions(baseError, invalidInput, parsers, customFormatter) {
|
|
45
|
+
const options = /* @__PURE__ */ new Set();
|
|
46
|
+
const commands = /* @__PURE__ */ new Set();
|
|
47
|
+
for (const parser of parsers) collectLeadingCandidates(parser.usage, options, commands);
|
|
48
|
+
const candidates = new Set([...options, ...commands]);
|
|
49
|
+
const suggestions = require_suggestion.findSimilar(invalidInput, candidates, require_suggestion.DEFAULT_FIND_SIMILAR_OPTIONS);
|
|
50
|
+
const suggestionMsg = customFormatter ? customFormatter(suggestions) : require_suggestion.createSuggestionMessage(suggestions);
|
|
51
|
+
return suggestionMsg.length > 0 ? [
|
|
52
|
+
...baseError,
|
|
53
|
+
require_message.text("\n\n"),
|
|
54
|
+
...suggestionMsg
|
|
55
|
+
] : baseError;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
7
58
|
* Checks if the given token is an option name that requires a value
|
|
8
59
|
* (i.e., has a metavar) within the given usage terms.
|
|
9
60
|
* @param usage The usage terms to search through.
|
|
@@ -168,16 +219,6 @@ function getNoMatchError(options, noMatchContext) {
|
|
|
168
219
|
return customNoMatch ? typeof customNoMatch === "function" ? customNoMatch(noMatchContext) : customNoMatch : generateNoMatchError(noMatchContext);
|
|
169
220
|
}
|
|
170
221
|
/**
|
|
171
|
-
* Creates default error for parse() method when buffer is not empty.
|
|
172
|
-
* Shared by or() and longestMatch().
|
|
173
|
-
* @internal
|
|
174
|
-
*/
|
|
175
|
-
function createUnexpectedInputError(token, usage, options) {
|
|
176
|
-
const defaultMsg = require_message.message`Unexpected option or subcommand: ${require_message.optionName(token)}.`;
|
|
177
|
-
if (options?.errors?.unexpectedInput != null) return typeof options.errors.unexpectedInput === "function" ? options.errors.unexpectedInput(token) : options.errors.unexpectedInput;
|
|
178
|
-
return require_suggestion.createErrorWithSuggestions(defaultMsg, token, usage, "both", options?.errors?.suggestions);
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
222
|
* @since 0.5.0
|
|
182
223
|
*/
|
|
183
224
|
function or(...args) {
|
|
@@ -204,7 +245,12 @@ function or(...args) {
|
|
|
204
245
|
parse(context) {
|
|
205
246
|
let error = {
|
|
206
247
|
consumed: 0,
|
|
207
|
-
error: context.buffer.length < 1 ? getNoMatchError(options, noMatchContext) :
|
|
248
|
+
error: context.buffer.length < 1 ? getNoMatchError(options, noMatchContext) : (() => {
|
|
249
|
+
const token = context.buffer[0];
|
|
250
|
+
const defaultMsg = require_message.message`Unexpected option or subcommand: ${require_message.optionName(token)}.`;
|
|
251
|
+
if (options?.errors?.unexpectedInput != null) return typeof options.errors.unexpectedInput === "function" ? options.errors.unexpectedInput(token) : options.errors.unexpectedInput;
|
|
252
|
+
return createUnexpectedInputErrorWithScopedSuggestions(defaultMsg, token, parsers, options?.errors?.suggestions);
|
|
253
|
+
})()
|
|
208
254
|
};
|
|
209
255
|
const orderedParsers = parsers.map((p, i) => [p, i]);
|
|
210
256
|
orderedParsers.sort(([_, a], [__, b]) => context.state?.[0] === a ? -1 : context.state?.[0] === b ? 1 : a - b);
|
|
@@ -305,7 +351,12 @@ function longestMatch(...args) {
|
|
|
305
351
|
let bestMatch = null;
|
|
306
352
|
let error = {
|
|
307
353
|
consumed: 0,
|
|
308
|
-
error: context.buffer.length < 1 ? getNoMatchError(options, noMatchContext) :
|
|
354
|
+
error: context.buffer.length < 1 ? getNoMatchError(options, noMatchContext) : (() => {
|
|
355
|
+
const token = context.buffer[0];
|
|
356
|
+
const defaultMsg = require_message.message`Unexpected option or subcommand: ${require_message.optionName(token)}.`;
|
|
357
|
+
if (options?.errors?.unexpectedInput != null) return typeof options.errors.unexpectedInput === "function" ? options.errors.unexpectedInput(token) : options.errors.unexpectedInput;
|
|
358
|
+
return createUnexpectedInputErrorWithScopedSuggestions(defaultMsg, token, parsers, options?.errors?.suggestions);
|
|
359
|
+
})()
|
|
309
360
|
};
|
|
310
361
|
for (let i = 0; i < parsers.length; i++) {
|
|
311
362
|
const parser = parsers[i];
|
package/dist/constructs.js
CHANGED
|
@@ -1,9 +1,60 @@
|
|
|
1
|
-
import { message, optionName, values } from "./message.js";
|
|
1
|
+
import { message, optionName, text, values } from "./message.js";
|
|
2
2
|
import { extractArgumentMetavars, extractCommandNames, extractOptionNames } from "./usage.js";
|
|
3
|
-
import { createErrorWithSuggestions, deduplicateSuggestions } from "./suggestion.js";
|
|
3
|
+
import { DEFAULT_FIND_SIMILAR_OPTIONS, createErrorWithSuggestions, createSuggestionMessage, deduplicateSuggestions, findSimilar } from "./suggestion.js";
|
|
4
4
|
|
|
5
5
|
//#region src/constructs.ts
|
|
6
6
|
/**
|
|
7
|
+
* Collects option names and command names that are valid at the current
|
|
8
|
+
* parse position by walking the usage tree. Only "leading" candidates
|
|
9
|
+
* (those reachable before a required positional argument) are collected.
|
|
10
|
+
*/
|
|
11
|
+
function collectLeadingCandidates(terms, optionNames, commandNames) {
|
|
12
|
+
if (!terms || !Array.isArray(terms)) return true;
|
|
13
|
+
for (const term of terms) {
|
|
14
|
+
if (term.type === "option") {
|
|
15
|
+
for (const name of term.names) optionNames.add(name);
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (term.type === "command") {
|
|
19
|
+
commandNames.add(term.name);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if (term.type === "argument") return false;
|
|
23
|
+
if (term.type === "optional") {
|
|
24
|
+
collectLeadingCandidates(term.terms, optionNames, commandNames);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (term.type === "multiple") {
|
|
28
|
+
collectLeadingCandidates(term.terms, optionNames, commandNames);
|
|
29
|
+
if (term.min === 0) continue;
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (term.type === "exclusive") {
|
|
33
|
+
let allAlternativesSkippable = true;
|
|
34
|
+
for (const exclusiveUsage of term.terms) {
|
|
35
|
+
const alternativeSkippable = collectLeadingCandidates(exclusiveUsage, optionNames, commandNames);
|
|
36
|
+
allAlternativesSkippable = allAlternativesSkippable && alternativeSkippable;
|
|
37
|
+
}
|
|
38
|
+
if (allAlternativesSkippable) continue;
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
function createUnexpectedInputErrorWithScopedSuggestions(baseError, invalidInput, parsers, customFormatter) {
|
|
45
|
+
const options = /* @__PURE__ */ new Set();
|
|
46
|
+
const commands = /* @__PURE__ */ new Set();
|
|
47
|
+
for (const parser of parsers) collectLeadingCandidates(parser.usage, options, commands);
|
|
48
|
+
const candidates = new Set([...options, ...commands]);
|
|
49
|
+
const suggestions = findSimilar(invalidInput, candidates, DEFAULT_FIND_SIMILAR_OPTIONS);
|
|
50
|
+
const suggestionMsg = customFormatter ? customFormatter(suggestions) : createSuggestionMessage(suggestions);
|
|
51
|
+
return suggestionMsg.length > 0 ? [
|
|
52
|
+
...baseError,
|
|
53
|
+
text("\n\n"),
|
|
54
|
+
...suggestionMsg
|
|
55
|
+
] : baseError;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
7
58
|
* Checks if the given token is an option name that requires a value
|
|
8
59
|
* (i.e., has a metavar) within the given usage terms.
|
|
9
60
|
* @param usage The usage terms to search through.
|
|
@@ -168,16 +219,6 @@ function getNoMatchError(options, noMatchContext) {
|
|
|
168
219
|
return customNoMatch ? typeof customNoMatch === "function" ? customNoMatch(noMatchContext) : customNoMatch : generateNoMatchError(noMatchContext);
|
|
169
220
|
}
|
|
170
221
|
/**
|
|
171
|
-
* Creates default error for parse() method when buffer is not empty.
|
|
172
|
-
* Shared by or() and longestMatch().
|
|
173
|
-
* @internal
|
|
174
|
-
*/
|
|
175
|
-
function createUnexpectedInputError(token, usage, options) {
|
|
176
|
-
const defaultMsg = message`Unexpected option or subcommand: ${optionName(token)}.`;
|
|
177
|
-
if (options?.errors?.unexpectedInput != null) return typeof options.errors.unexpectedInput === "function" ? options.errors.unexpectedInput(token) : options.errors.unexpectedInput;
|
|
178
|
-
return createErrorWithSuggestions(defaultMsg, token, usage, "both", options?.errors?.suggestions);
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
222
|
* @since 0.5.0
|
|
182
223
|
*/
|
|
183
224
|
function or(...args) {
|
|
@@ -204,7 +245,12 @@ function or(...args) {
|
|
|
204
245
|
parse(context) {
|
|
205
246
|
let error = {
|
|
206
247
|
consumed: 0,
|
|
207
|
-
error: context.buffer.length < 1 ? getNoMatchError(options, noMatchContext) :
|
|
248
|
+
error: context.buffer.length < 1 ? getNoMatchError(options, noMatchContext) : (() => {
|
|
249
|
+
const token = context.buffer[0];
|
|
250
|
+
const defaultMsg = message`Unexpected option or subcommand: ${optionName(token)}.`;
|
|
251
|
+
if (options?.errors?.unexpectedInput != null) return typeof options.errors.unexpectedInput === "function" ? options.errors.unexpectedInput(token) : options.errors.unexpectedInput;
|
|
252
|
+
return createUnexpectedInputErrorWithScopedSuggestions(defaultMsg, token, parsers, options?.errors?.suggestions);
|
|
253
|
+
})()
|
|
208
254
|
};
|
|
209
255
|
const orderedParsers = parsers.map((p, i) => [p, i]);
|
|
210
256
|
orderedParsers.sort(([_, a], [__, b]) => context.state?.[0] === a ? -1 : context.state?.[0] === b ? 1 : a - b);
|
|
@@ -305,7 +351,12 @@ function longestMatch(...args) {
|
|
|
305
351
|
let bestMatch = null;
|
|
306
352
|
let error = {
|
|
307
353
|
consumed: 0,
|
|
308
|
-
error: context.buffer.length < 1 ? getNoMatchError(options, noMatchContext) :
|
|
354
|
+
error: context.buffer.length < 1 ? getNoMatchError(options, noMatchContext) : (() => {
|
|
355
|
+
const token = context.buffer[0];
|
|
356
|
+
const defaultMsg = message`Unexpected option or subcommand: ${optionName(token)}.`;
|
|
357
|
+
if (options?.errors?.unexpectedInput != null) return typeof options.errors.unexpectedInput === "function" ? options.errors.unexpectedInput(token) : options.errors.unexpectedInput;
|
|
358
|
+
return createUnexpectedInputErrorWithScopedSuggestions(defaultMsg, token, parsers, options?.errors?.suggestions);
|
|
359
|
+
})()
|
|
309
360
|
};
|
|
310
361
|
for (let i = 0; i < parsers.length; i++) {
|
|
311
362
|
const parser = parsers[i];
|
package/dist/facade.cjs
CHANGED
|
@@ -285,9 +285,26 @@ function combineWithHelpVersion(originalParser, helpParsers, versionParsers, com
|
|
|
285
285
|
completion: require_primitives.constant(false),
|
|
286
286
|
result: originalParser
|
|
287
287
|
}));
|
|
288
|
+
const mainParserIndex = parsers.length - 1;
|
|
289
|
+
let combined;
|
|
288
290
|
if (parsers.length === 1) return parsers[0];
|
|
289
|
-
else if (parsers.length === 2)
|
|
290
|
-
else
|
|
291
|
+
else if (parsers.length === 2) combined = require_constructs.longestMatch(parsers[0], parsers[1]);
|
|
292
|
+
else combined = require_constructs.longestMatch(...parsers);
|
|
293
|
+
const topUsage = combined.usage[0];
|
|
294
|
+
if (topUsage?.type === "exclusive" && mainParserIndex > 0) {
|
|
295
|
+
const terms = [...topUsage.terms];
|
|
296
|
+
const [mainTerm] = terms.splice(mainParserIndex, 1);
|
|
297
|
+
const lenientCount = (helpParsers.helpOption ? 1 : 0) + (versionParsers.versionOption ? 1 : 0);
|
|
298
|
+
terms.splice(lenientCount, 0, mainTerm);
|
|
299
|
+
combined = {
|
|
300
|
+
...combined,
|
|
301
|
+
usage: [{
|
|
302
|
+
...topUsage,
|
|
303
|
+
terms
|
|
304
|
+
}]
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
return combined;
|
|
291
308
|
}
|
|
292
309
|
/**
|
|
293
310
|
* Classifies the parsing result into a discriminated union for cleaner handling.
|
|
@@ -470,9 +487,9 @@ function run(parser, programName, args, options = {}) {
|
|
|
470
487
|
} else {
|
|
471
488
|
const singularMatchExact = completionName === "singular" || completionName === "both" ? arg === "--completion" : false;
|
|
472
489
|
const pluralMatchExact = completionName === "plural" || completionName === "both" ? arg === "--completions" : false;
|
|
473
|
-
if (
|
|
474
|
-
const shell = args[i + 1];
|
|
475
|
-
const completionArgs = args.slice(i + 2);
|
|
490
|
+
if (singularMatchExact || pluralMatchExact) {
|
|
491
|
+
const shell = i + 1 < args.length ? args[i + 1] : "";
|
|
492
|
+
const completionArgs = i + 1 < args.length ? args.slice(i + 2) : [];
|
|
476
493
|
return handleCompletion([shell, ...completionArgs], programName, parser, completionParsers.completionCommand, stdout, stderr, onCompletion, onError, availableShells, colors, maxWidth, completionMode, completionName);
|
|
477
494
|
}
|
|
478
495
|
}
|
package/dist/facade.js
CHANGED
|
@@ -285,9 +285,26 @@ function combineWithHelpVersion(originalParser, helpParsers, versionParsers, com
|
|
|
285
285
|
completion: constant(false),
|
|
286
286
|
result: originalParser
|
|
287
287
|
}));
|
|
288
|
+
const mainParserIndex = parsers.length - 1;
|
|
289
|
+
let combined;
|
|
288
290
|
if (parsers.length === 1) return parsers[0];
|
|
289
|
-
else if (parsers.length === 2)
|
|
290
|
-
else
|
|
291
|
+
else if (parsers.length === 2) combined = longestMatch(parsers[0], parsers[1]);
|
|
292
|
+
else combined = longestMatch(...parsers);
|
|
293
|
+
const topUsage = combined.usage[0];
|
|
294
|
+
if (topUsage?.type === "exclusive" && mainParserIndex > 0) {
|
|
295
|
+
const terms = [...topUsage.terms];
|
|
296
|
+
const [mainTerm] = terms.splice(mainParserIndex, 1);
|
|
297
|
+
const lenientCount = (helpParsers.helpOption ? 1 : 0) + (versionParsers.versionOption ? 1 : 0);
|
|
298
|
+
terms.splice(lenientCount, 0, mainTerm);
|
|
299
|
+
combined = {
|
|
300
|
+
...combined,
|
|
301
|
+
usage: [{
|
|
302
|
+
...topUsage,
|
|
303
|
+
terms
|
|
304
|
+
}]
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
return combined;
|
|
291
308
|
}
|
|
292
309
|
/**
|
|
293
310
|
* Classifies the parsing result into a discriminated union for cleaner handling.
|
|
@@ -470,9 +487,9 @@ function run(parser, programName, args, options = {}) {
|
|
|
470
487
|
} else {
|
|
471
488
|
const singularMatchExact = completionName === "singular" || completionName === "both" ? arg === "--completion" : false;
|
|
472
489
|
const pluralMatchExact = completionName === "plural" || completionName === "both" ? arg === "--completions" : false;
|
|
473
|
-
if (
|
|
474
|
-
const shell = args[i + 1];
|
|
475
|
-
const completionArgs = args.slice(i + 2);
|
|
490
|
+
if (singularMatchExact || pluralMatchExact) {
|
|
491
|
+
const shell = i + 1 < args.length ? args[i + 1] : "";
|
|
492
|
+
const completionArgs = i + 1 < args.length ? args.slice(i + 2) : [];
|
|
476
493
|
return handleCompletion([shell, ...completionArgs], programName, parser, completionParsers.completionCommand, stdout, stderr, onCompletion, onError, availableShells, colors, maxWidth, completionMode, completionName);
|
|
477
494
|
}
|
|
478
495
|
}
|
package/dist/suggestion.cjs
CHANGED
|
@@ -232,5 +232,6 @@ function deduplicateSuggestions(suggestions) {
|
|
|
232
232
|
//#endregion
|
|
233
233
|
exports.DEFAULT_FIND_SIMILAR_OPTIONS = DEFAULT_FIND_SIMILAR_OPTIONS;
|
|
234
234
|
exports.createErrorWithSuggestions = createErrorWithSuggestions;
|
|
235
|
+
exports.createSuggestionMessage = createSuggestionMessage;
|
|
235
236
|
exports.deduplicateSuggestions = deduplicateSuggestions;
|
|
236
237
|
exports.findSimilar = findSimilar;
|
package/dist/suggestion.js
CHANGED
|
@@ -230,4 +230,4 @@ function deduplicateSuggestions(suggestions) {
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
//#endregion
|
|
233
|
-
export { DEFAULT_FIND_SIMILAR_OPTIONS, createErrorWithSuggestions, deduplicateSuggestions, findSimilar };
|
|
233
|
+
export { DEFAULT_FIND_SIMILAR_OPTIONS, createErrorWithSuggestions, createSuggestionMessage, deduplicateSuggestions, findSimilar };
|