@optique/core 0.7.11 → 0.7.13-dev.361
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 +57 -2
- package/dist/constructs.js +59 -4
- package/dist/doc.d.cts +6 -0
- package/dist/doc.d.ts +6 -0
- package/dist/facade.cjs +5 -3
- package/dist/facade.js +5 -3
- package/dist/parser.cjs +2 -1
- package/dist/parser.js +2 -1
- package/dist/primitives.cjs +1 -0
- package/dist/primitives.js +1 -0
- package/dist/suggestion.cjs +1 -0
- package/dist/suggestion.js +1 -1
- package/package.json +1 -1
package/dist/constructs.cjs
CHANGED
|
@@ -24,6 +24,52 @@ function isOptionRequiringValue(usage, token) {
|
|
|
24
24
|
}
|
|
25
25
|
return traverse(usage);
|
|
26
26
|
}
|
|
27
|
+
function collectLeadingCandidates(terms, optionNames, commandNames) {
|
|
28
|
+
if (!terms || !Array.isArray(terms)) return true;
|
|
29
|
+
for (const term of terms) {
|
|
30
|
+
if (term.type === "option") {
|
|
31
|
+
for (const name of term.names) optionNames.add(name);
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
if (term.type === "command") {
|
|
35
|
+
commandNames.add(term.name);
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (term.type === "argument") return false;
|
|
39
|
+
if (term.type === "optional") {
|
|
40
|
+
collectLeadingCandidates(term.terms, optionNames, commandNames);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (term.type === "multiple") {
|
|
44
|
+
collectLeadingCandidates(term.terms, optionNames, commandNames);
|
|
45
|
+
if (term.min === 0) continue;
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
if (term.type === "exclusive") {
|
|
49
|
+
let allAlternativesSkippable = true;
|
|
50
|
+
for (const exclusiveUsage of term.terms) {
|
|
51
|
+
const alternativeSkippable = collectLeadingCandidates(exclusiveUsage, optionNames, commandNames);
|
|
52
|
+
allAlternativesSkippable = allAlternativesSkippable && alternativeSkippable;
|
|
53
|
+
}
|
|
54
|
+
if (allAlternativesSkippable) continue;
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
function createUnexpectedInputErrorWithScopedSuggestions(baseError, invalidInput, parsers, customFormatter) {
|
|
61
|
+
const options = /* @__PURE__ */ new Set();
|
|
62
|
+
const commands = /* @__PURE__ */ new Set();
|
|
63
|
+
for (const parser of parsers) collectLeadingCandidates(parser.usage, options, commands);
|
|
64
|
+
const candidates = new Set([...options, ...commands]);
|
|
65
|
+
const suggestions = require_suggestion.findSimilar(invalidInput, candidates, require_suggestion.DEFAULT_FIND_SIMILAR_OPTIONS);
|
|
66
|
+
const suggestionMsg = customFormatter ? customFormatter(suggestions) : require_suggestion.createSuggestionMessage(suggestions);
|
|
67
|
+
return suggestionMsg.length > 0 ? [
|
|
68
|
+
...baseError,
|
|
69
|
+
require_message.text("\n\n"),
|
|
70
|
+
...suggestionMsg
|
|
71
|
+
] : baseError;
|
|
72
|
+
}
|
|
27
73
|
/**
|
|
28
74
|
* Extracts required (non-optional) usage terms from a usage array.
|
|
29
75
|
* @param usage The usage to extract required terms from
|
|
@@ -132,7 +178,7 @@ function or(...args) {
|
|
|
132
178
|
const token = context.buffer[0];
|
|
133
179
|
const defaultMsg = require_message.message`Unexpected option or subcommand: ${require_message.optionName(token)}.`;
|
|
134
180
|
if (options?.errors?.unexpectedInput != null) return typeof options.errors.unexpectedInput === "function" ? options.errors.unexpectedInput(token) : options.errors.unexpectedInput;
|
|
135
|
-
return
|
|
181
|
+
return createUnexpectedInputErrorWithScopedSuggestions(defaultMsg, token, parsers, options?.errors?.suggestions);
|
|
136
182
|
})()
|
|
137
183
|
};
|
|
138
184
|
const orderedParsers = parsers.map((p, i) => [p, i]);
|
|
@@ -193,7 +239,9 @@ function or(...args) {
|
|
|
193
239
|
});
|
|
194
240
|
},
|
|
195
241
|
getDocFragments(state, _defaultValue) {
|
|
242
|
+
let brief;
|
|
196
243
|
let description;
|
|
244
|
+
let footer;
|
|
197
245
|
let fragments;
|
|
198
246
|
if (state.kind === "unavailable" || state.state == null) fragments = parsers.flatMap((p) => p.getDocFragments({ kind: "unavailable" }, void 0).fragments);
|
|
199
247
|
else {
|
|
@@ -203,7 +251,9 @@ function or(...args) {
|
|
|
203
251
|
state: parserResult.next.state
|
|
204
252
|
} : { kind: "unavailable" };
|
|
205
253
|
const docFragments = parsers[index].getDocFragments(innerState, void 0);
|
|
254
|
+
brief = docFragments.brief;
|
|
206
255
|
description = docFragments.description;
|
|
256
|
+
footer = docFragments.footer;
|
|
207
257
|
fragments = docFragments.fragments;
|
|
208
258
|
}
|
|
209
259
|
const entries = fragments.filter((f) => f.type === "entry");
|
|
@@ -214,7 +264,9 @@ function or(...args) {
|
|
|
214
264
|
else sections.push(fragment);
|
|
215
265
|
}
|
|
216
266
|
return {
|
|
267
|
+
brief,
|
|
217
268
|
description,
|
|
269
|
+
footer,
|
|
218
270
|
fragments: [...sections.map((s) => ({
|
|
219
271
|
...s,
|
|
220
272
|
type: "section"
|
|
@@ -276,7 +328,7 @@ function longestMatch(...args) {
|
|
|
276
328
|
const token = context.buffer[0];
|
|
277
329
|
const defaultMsg = require_message.message`Unexpected option or subcommand: ${require_message.optionName(token)}.`;
|
|
278
330
|
if (options?.errors?.unexpectedInput != null) return typeof options.errors.unexpectedInput === "function" ? options.errors.unexpectedInput(token) : options.errors.unexpectedInput;
|
|
279
|
-
return
|
|
331
|
+
return createUnexpectedInputErrorWithScopedSuggestions(defaultMsg, token, parsers, options?.errors?.suggestions);
|
|
280
332
|
})()
|
|
281
333
|
};
|
|
282
334
|
for (let i = 0; i < parsers.length; i++) {
|
|
@@ -337,6 +389,7 @@ function longestMatch(...args) {
|
|
|
337
389
|
});
|
|
338
390
|
},
|
|
339
391
|
getDocFragments(state, _defaultValue) {
|
|
392
|
+
let brief;
|
|
340
393
|
let description;
|
|
341
394
|
let footer;
|
|
342
395
|
let fragments;
|
|
@@ -348,12 +401,14 @@ function longestMatch(...args) {
|
|
|
348
401
|
kind: "available",
|
|
349
402
|
state: result.next.state
|
|
350
403
|
});
|
|
404
|
+
brief = docResult.brief;
|
|
351
405
|
description = docResult.description;
|
|
352
406
|
footer = docResult.footer;
|
|
353
407
|
fragments = docResult.fragments;
|
|
354
408
|
} else fragments = parsers.flatMap((p) => p.getDocFragments({ kind: "unavailable" }).fragments);
|
|
355
409
|
}
|
|
356
410
|
return {
|
|
411
|
+
brief,
|
|
357
412
|
description,
|
|
358
413
|
fragments,
|
|
359
414
|
footer
|
package/dist/constructs.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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 } from "./suggestion.js";
|
|
3
|
+
import { DEFAULT_FIND_SIMILAR_OPTIONS, createErrorWithSuggestions, createSuggestionMessage, findSimilar } from "./suggestion.js";
|
|
4
4
|
|
|
5
5
|
//#region src/constructs.ts
|
|
6
6
|
/**
|
|
@@ -24,6 +24,52 @@ function isOptionRequiringValue(usage, token) {
|
|
|
24
24
|
}
|
|
25
25
|
return traverse(usage);
|
|
26
26
|
}
|
|
27
|
+
function collectLeadingCandidates(terms, optionNames, commandNames) {
|
|
28
|
+
if (!terms || !Array.isArray(terms)) return true;
|
|
29
|
+
for (const term of terms) {
|
|
30
|
+
if (term.type === "option") {
|
|
31
|
+
for (const name of term.names) optionNames.add(name);
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
if (term.type === "command") {
|
|
35
|
+
commandNames.add(term.name);
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (term.type === "argument") return false;
|
|
39
|
+
if (term.type === "optional") {
|
|
40
|
+
collectLeadingCandidates(term.terms, optionNames, commandNames);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (term.type === "multiple") {
|
|
44
|
+
collectLeadingCandidates(term.terms, optionNames, commandNames);
|
|
45
|
+
if (term.min === 0) continue;
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
if (term.type === "exclusive") {
|
|
49
|
+
let allAlternativesSkippable = true;
|
|
50
|
+
for (const exclusiveUsage of term.terms) {
|
|
51
|
+
const alternativeSkippable = collectLeadingCandidates(exclusiveUsage, optionNames, commandNames);
|
|
52
|
+
allAlternativesSkippable = allAlternativesSkippable && alternativeSkippable;
|
|
53
|
+
}
|
|
54
|
+
if (allAlternativesSkippable) continue;
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
function createUnexpectedInputErrorWithScopedSuggestions(baseError, invalidInput, parsers, customFormatter) {
|
|
61
|
+
const options = /* @__PURE__ */ new Set();
|
|
62
|
+
const commands = /* @__PURE__ */ new Set();
|
|
63
|
+
for (const parser of parsers) collectLeadingCandidates(parser.usage, options, commands);
|
|
64
|
+
const candidates = new Set([...options, ...commands]);
|
|
65
|
+
const suggestions = findSimilar(invalidInput, candidates, DEFAULT_FIND_SIMILAR_OPTIONS);
|
|
66
|
+
const suggestionMsg = customFormatter ? customFormatter(suggestions) : createSuggestionMessage(suggestions);
|
|
67
|
+
return suggestionMsg.length > 0 ? [
|
|
68
|
+
...baseError,
|
|
69
|
+
text("\n\n"),
|
|
70
|
+
...suggestionMsg
|
|
71
|
+
] : baseError;
|
|
72
|
+
}
|
|
27
73
|
/**
|
|
28
74
|
* Extracts required (non-optional) usage terms from a usage array.
|
|
29
75
|
* @param usage The usage to extract required terms from
|
|
@@ -132,7 +178,7 @@ function or(...args) {
|
|
|
132
178
|
const token = context.buffer[0];
|
|
133
179
|
const defaultMsg = message`Unexpected option or subcommand: ${optionName(token)}.`;
|
|
134
180
|
if (options?.errors?.unexpectedInput != null) return typeof options.errors.unexpectedInput === "function" ? options.errors.unexpectedInput(token) : options.errors.unexpectedInput;
|
|
135
|
-
return
|
|
181
|
+
return createUnexpectedInputErrorWithScopedSuggestions(defaultMsg, token, parsers, options?.errors?.suggestions);
|
|
136
182
|
})()
|
|
137
183
|
};
|
|
138
184
|
const orderedParsers = parsers.map((p, i) => [p, i]);
|
|
@@ -193,7 +239,9 @@ function or(...args) {
|
|
|
193
239
|
});
|
|
194
240
|
},
|
|
195
241
|
getDocFragments(state, _defaultValue) {
|
|
242
|
+
let brief;
|
|
196
243
|
let description;
|
|
244
|
+
let footer;
|
|
197
245
|
let fragments;
|
|
198
246
|
if (state.kind === "unavailable" || state.state == null) fragments = parsers.flatMap((p) => p.getDocFragments({ kind: "unavailable" }, void 0).fragments);
|
|
199
247
|
else {
|
|
@@ -203,7 +251,9 @@ function or(...args) {
|
|
|
203
251
|
state: parserResult.next.state
|
|
204
252
|
} : { kind: "unavailable" };
|
|
205
253
|
const docFragments = parsers[index].getDocFragments(innerState, void 0);
|
|
254
|
+
brief = docFragments.brief;
|
|
206
255
|
description = docFragments.description;
|
|
256
|
+
footer = docFragments.footer;
|
|
207
257
|
fragments = docFragments.fragments;
|
|
208
258
|
}
|
|
209
259
|
const entries = fragments.filter((f) => f.type === "entry");
|
|
@@ -214,7 +264,9 @@ function or(...args) {
|
|
|
214
264
|
else sections.push(fragment);
|
|
215
265
|
}
|
|
216
266
|
return {
|
|
267
|
+
brief,
|
|
217
268
|
description,
|
|
269
|
+
footer,
|
|
218
270
|
fragments: [...sections.map((s) => ({
|
|
219
271
|
...s,
|
|
220
272
|
type: "section"
|
|
@@ -276,7 +328,7 @@ function longestMatch(...args) {
|
|
|
276
328
|
const token = context.buffer[0];
|
|
277
329
|
const defaultMsg = message`Unexpected option or subcommand: ${optionName(token)}.`;
|
|
278
330
|
if (options?.errors?.unexpectedInput != null) return typeof options.errors.unexpectedInput === "function" ? options.errors.unexpectedInput(token) : options.errors.unexpectedInput;
|
|
279
|
-
return
|
|
331
|
+
return createUnexpectedInputErrorWithScopedSuggestions(defaultMsg, token, parsers, options?.errors?.suggestions);
|
|
280
332
|
})()
|
|
281
333
|
};
|
|
282
334
|
for (let i = 0; i < parsers.length; i++) {
|
|
@@ -337,6 +389,7 @@ function longestMatch(...args) {
|
|
|
337
389
|
});
|
|
338
390
|
},
|
|
339
391
|
getDocFragments(state, _defaultValue) {
|
|
392
|
+
let brief;
|
|
340
393
|
let description;
|
|
341
394
|
let footer;
|
|
342
395
|
let fragments;
|
|
@@ -348,12 +401,14 @@ function longestMatch(...args) {
|
|
|
348
401
|
kind: "available",
|
|
349
402
|
state: result.next.state
|
|
350
403
|
});
|
|
404
|
+
brief = docResult.brief;
|
|
351
405
|
description = docResult.description;
|
|
352
406
|
footer = docResult.footer;
|
|
353
407
|
fragments = docResult.fragments;
|
|
354
408
|
} else fragments = parsers.flatMap((p) => p.getDocFragments({ kind: "unavailable" }).fragments);
|
|
355
409
|
}
|
|
356
410
|
return {
|
|
411
|
+
brief,
|
|
357
412
|
description,
|
|
358
413
|
fragments,
|
|
359
414
|
footer
|
package/dist/doc.d.cts
CHANGED
|
@@ -60,6 +60,12 @@ type DocFragment = {
|
|
|
60
60
|
* a final document page.
|
|
61
61
|
*/
|
|
62
62
|
interface DocFragments {
|
|
63
|
+
/**
|
|
64
|
+
* An optional brief that provides a short summary for the collection
|
|
65
|
+
* of fragments.
|
|
66
|
+
* @since 0.7.12
|
|
67
|
+
*/
|
|
68
|
+
readonly brief?: Message;
|
|
63
69
|
/**
|
|
64
70
|
* An optional description that applies to the entire collection of fragments.
|
|
65
71
|
*/
|
package/dist/doc.d.ts
CHANGED
|
@@ -60,6 +60,12 @@ type DocFragment = {
|
|
|
60
60
|
* a final document page.
|
|
61
61
|
*/
|
|
62
62
|
interface DocFragments {
|
|
63
|
+
/**
|
|
64
|
+
* An optional brief that provides a short summary for the collection
|
|
65
|
+
* of fragments.
|
|
66
|
+
* @since 0.7.12
|
|
67
|
+
*/
|
|
68
|
+
readonly brief?: Message;
|
|
63
69
|
/**
|
|
64
70
|
* An optional description that applies to the entire collection of fragments.
|
|
65
71
|
*/
|
package/dist/facade.cjs
CHANGED
|
@@ -561,11 +561,13 @@ function run(parser, programName, args, options = {}) {
|
|
|
561
561
|
const doc = require_parser.getDocPage(helpGeneratorParser, classified.commands);
|
|
562
562
|
if (doc != null) {
|
|
563
563
|
const isMetaCommandHelp = (completionName === "singular" || completionName === "both" ? requestedCommand === "completion" : false) || (completionName === "plural" || completionName === "both" ? requestedCommand === "completions" : false) || requestedCommand === "help" || requestedCommand === "version";
|
|
564
|
+
const isSubcommandHelp = classified.commands.length > 0;
|
|
565
|
+
const shouldOverride = !isMetaCommandHelp && !isSubcommandHelp;
|
|
564
566
|
const augmentedDoc = {
|
|
565
567
|
...doc,
|
|
566
|
-
brief:
|
|
567
|
-
description:
|
|
568
|
-
footer:
|
|
568
|
+
brief: shouldOverride ? brief ?? doc.brief : doc.brief ?? brief,
|
|
569
|
+
description: shouldOverride ? description ?? doc.description : doc.description ?? description,
|
|
570
|
+
footer: shouldOverride ? footer ?? doc.footer : doc.footer ?? footer
|
|
569
571
|
};
|
|
570
572
|
stdout(require_doc.formatDocPage(programName, augmentedDoc, {
|
|
571
573
|
colors,
|
package/dist/facade.js
CHANGED
|
@@ -561,11 +561,13 @@ function run(parser, programName, args, options = {}) {
|
|
|
561
561
|
const doc = getDocPage(helpGeneratorParser, classified.commands);
|
|
562
562
|
if (doc != null) {
|
|
563
563
|
const isMetaCommandHelp = (completionName === "singular" || completionName === "both" ? requestedCommand === "completion" : false) || (completionName === "plural" || completionName === "both" ? requestedCommand === "completions" : false) || requestedCommand === "help" || requestedCommand === "version";
|
|
564
|
+
const isSubcommandHelp = classified.commands.length > 0;
|
|
565
|
+
const shouldOverride = !isMetaCommandHelp && !isSubcommandHelp;
|
|
564
566
|
const augmentedDoc = {
|
|
565
567
|
...doc,
|
|
566
|
-
brief:
|
|
567
|
-
description:
|
|
568
|
-
footer:
|
|
568
|
+
brief: shouldOverride ? brief ?? doc.brief : doc.brief ?? brief,
|
|
569
|
+
description: shouldOverride ? description ?? doc.description : doc.description ?? description,
|
|
570
|
+
footer: shouldOverride ? footer ?? doc.footer : doc.footer ?? footer
|
|
569
571
|
};
|
|
570
572
|
stdout(formatDocPage(programName, augmentedDoc, {
|
|
571
573
|
colors,
|
package/dist/parser.cjs
CHANGED
|
@@ -159,7 +159,7 @@ function getDocPage(parser, args = []) {
|
|
|
159
159
|
if (!result.success) break;
|
|
160
160
|
context = result.next;
|
|
161
161
|
} while (context.buffer.length > 0);
|
|
162
|
-
const { description, fragments, footer } = parser.getDocFragments({
|
|
162
|
+
const { brief, description, fragments, footer } = parser.getDocFragments({
|
|
163
163
|
kind: "available",
|
|
164
164
|
state: context.state
|
|
165
165
|
}, void 0);
|
|
@@ -184,6 +184,7 @@ function getDocPage(parser, args = []) {
|
|
|
184
184
|
return {
|
|
185
185
|
usage,
|
|
186
186
|
sections,
|
|
187
|
+
...brief != null && { brief },
|
|
187
188
|
...description != null && { description },
|
|
188
189
|
...footer != null && { footer }
|
|
189
190
|
};
|
package/dist/parser.js
CHANGED
|
@@ -159,7 +159,7 @@ function getDocPage(parser, args = []) {
|
|
|
159
159
|
if (!result.success) break;
|
|
160
160
|
context = result.next;
|
|
161
161
|
} while (context.buffer.length > 0);
|
|
162
|
-
const { description, fragments, footer } = parser.getDocFragments({
|
|
162
|
+
const { brief, description, fragments, footer } = parser.getDocFragments({
|
|
163
163
|
kind: "available",
|
|
164
164
|
state: context.state
|
|
165
165
|
}, void 0);
|
|
@@ -184,6 +184,7 @@ function getDocPage(parser, args = []) {
|
|
|
184
184
|
return {
|
|
185
185
|
usage,
|
|
186
186
|
sections,
|
|
187
|
+
...brief != null && { brief },
|
|
187
188
|
...description != null && { description },
|
|
188
189
|
...footer != null && { footer }
|
|
189
190
|
};
|
package/dist/primitives.cjs
CHANGED
|
@@ -725,6 +725,7 @@ function command(name, parser, options = {}) {
|
|
|
725
725
|
const innerFragments = parser.getDocFragments(innerState, defaultValue);
|
|
726
726
|
return {
|
|
727
727
|
...innerFragments,
|
|
728
|
+
brief: innerFragments.brief ?? options.brief,
|
|
728
729
|
description: innerFragments.description ?? options.description,
|
|
729
730
|
footer: innerFragments.footer ?? options.footer
|
|
730
731
|
};
|
package/dist/primitives.js
CHANGED
|
@@ -725,6 +725,7 @@ function command(name, parser, options = {}) {
|
|
|
725
725
|
const innerFragments = parser.getDocFragments(innerState, defaultValue);
|
|
726
726
|
return {
|
|
727
727
|
...innerFragments,
|
|
728
|
+
brief: innerFragments.brief ?? options.brief,
|
|
728
729
|
description: innerFragments.description ?? options.description,
|
|
729
730
|
footer: innerFragments.footer ?? options.footer
|
|
730
731
|
};
|
package/dist/suggestion.cjs
CHANGED
|
@@ -183,4 +183,5 @@ function createErrorWithSuggestions(baseError, invalidInput, usage, type = "both
|
|
|
183
183
|
//#endregion
|
|
184
184
|
exports.DEFAULT_FIND_SIMILAR_OPTIONS = DEFAULT_FIND_SIMILAR_OPTIONS;
|
|
185
185
|
exports.createErrorWithSuggestions = createErrorWithSuggestions;
|
|
186
|
+
exports.createSuggestionMessage = createSuggestionMessage;
|
|
186
187
|
exports.findSimilar = findSimilar;
|
package/dist/suggestion.js
CHANGED
|
@@ -181,4 +181,4 @@ function createErrorWithSuggestions(baseError, invalidInput, usage, type = "both
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
//#endregion
|
|
184
|
-
export { DEFAULT_FIND_SIMILAR_OPTIONS, createErrorWithSuggestions, findSimilar };
|
|
184
|
+
export { DEFAULT_FIND_SIMILAR_OPTIONS, createErrorWithSuggestions, createSuggestionMessage, findSimilar };
|