@optique/core 0.10.2 → 0.10.4-dev.408
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 +13 -43
- package/dist/constructs.js +12 -42
- package/dist/facade.cjs +2 -2
- package/dist/facade.js +2 -2
- package/dist/message.cjs +104 -95
- package/dist/message.js +104 -95
- package/dist/primitives.cjs +9 -4
- package/dist/primitives.js +12 -7
- package/dist/usage-internals.cjs +73 -0
- package/dist/usage-internals.js +71 -0
- package/package.json +1 -1
package/dist/constructs.cjs
CHANGED
|
@@ -3,50 +3,13 @@ const require_dependency = require('./dependency.cjs');
|
|
|
3
3
|
const require_mode_dispatch = require('./mode-dispatch.cjs');
|
|
4
4
|
const require_usage = require('./usage.cjs');
|
|
5
5
|
const require_suggestion = require('./suggestion.cjs');
|
|
6
|
+
const require_usage_internals = require('./usage-internals.cjs');
|
|
6
7
|
|
|
7
8
|
//#region src/constructs.ts
|
|
8
|
-
/**
|
|
9
|
-
* Collects option names and command names that are valid at the current
|
|
10
|
-
* parse position by walking the usage tree. Only "leading" candidates
|
|
11
|
-
* (those reachable before a required positional argument) are collected.
|
|
12
|
-
*/
|
|
13
|
-
function collectLeadingCandidates(terms, optionNames, commandNames) {
|
|
14
|
-
if (!terms || !Array.isArray(terms)) return true;
|
|
15
|
-
for (const term of terms) {
|
|
16
|
-
if (term.type === "option") {
|
|
17
|
-
for (const name of term.names) optionNames.add(name);
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
if (term.type === "command") {
|
|
21
|
-
commandNames.add(term.name);
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
if (term.type === "argument") return false;
|
|
25
|
-
if (term.type === "optional") {
|
|
26
|
-
collectLeadingCandidates(term.terms, optionNames, commandNames);
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
if (term.type === "multiple") {
|
|
30
|
-
collectLeadingCandidates(term.terms, optionNames, commandNames);
|
|
31
|
-
if (term.min === 0) continue;
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
if (term.type === "exclusive") {
|
|
35
|
-
let allAlternativesSkippable = true;
|
|
36
|
-
for (const exclusiveUsage of term.terms) {
|
|
37
|
-
const alternativeSkippable = collectLeadingCandidates(exclusiveUsage, optionNames, commandNames);
|
|
38
|
-
allAlternativesSkippable = allAlternativesSkippable && alternativeSkippable;
|
|
39
|
-
}
|
|
40
|
-
if (allAlternativesSkippable) continue;
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
9
|
function createUnexpectedInputErrorWithScopedSuggestions(baseError, invalidInput, parsers, customFormatter) {
|
|
47
10
|
const options = /* @__PURE__ */ new Set();
|
|
48
11
|
const commands = /* @__PURE__ */ new Set();
|
|
49
|
-
for (const parser of parsers) collectLeadingCandidates(parser.usage, options, commands);
|
|
12
|
+
for (const parser of parsers) require_usage_internals.collectLeadingCandidates(parser.usage, options, commands);
|
|
50
13
|
const candidates = new Set([...options, ...commands]);
|
|
51
14
|
const suggestions = require_suggestion.findSimilar(invalidInput, candidates, require_suggestion.DEFAULT_FIND_SIMILAR_OPTIONS);
|
|
52
15
|
const suggestionMsg = customFormatter ? customFormatter(suggestions) : require_suggestion.createSuggestionMessage(suggestions);
|
|
@@ -2006,7 +1969,7 @@ function group(label, parser) {
|
|
|
2006
1969
|
complete: (state) => parser.complete(state),
|
|
2007
1970
|
suggest: (context, prefix) => parser.suggest(context, prefix),
|
|
2008
1971
|
getDocFragments: (state, defaultValue) => {
|
|
2009
|
-
const { description, fragments } = parser.getDocFragments(state, defaultValue);
|
|
1972
|
+
const { brief, description, footer, fragments } = parser.getDocFragments(state, defaultValue);
|
|
2010
1973
|
const allEntries = [];
|
|
2011
1974
|
const titledSections = [];
|
|
2012
1975
|
for (const fragment of fragments) if (fragment.type === "entry") allEntries.push(fragment);
|
|
@@ -2016,15 +1979,22 @@ function group(label, parser) {
|
|
|
2016
1979
|
kind: "available",
|
|
2017
1980
|
state: parser.initialState
|
|
2018
1981
|
}, void 0);
|
|
2019
|
-
const
|
|
2020
|
-
const
|
|
2021
|
-
|
|
1982
|
+
const initialCommandNames = /* @__PURE__ */ new Set();
|
|
1983
|
+
for (const f of initialFragments.fragments) if (f.type === "entry" && f.term.type === "command") initialCommandNames.add(f.term.name);
|
|
1984
|
+
else if (f.type === "section") {
|
|
1985
|
+
for (const e of f.entries) if (e.term.type === "command") initialCommandNames.add(e.term.name);
|
|
1986
|
+
}
|
|
1987
|
+
const initialHasCommands = initialCommandNames.size > 0;
|
|
1988
|
+
const currentCommandsAreGroupOwn = allEntries.some((e) => e.term.type === "command" && initialCommandNames.has(e.term.name));
|
|
1989
|
+
const applyLabel = !initialHasCommands || currentCommandsAreGroupOwn;
|
|
2022
1990
|
const labeledSection = applyLabel ? {
|
|
2023
1991
|
title: label,
|
|
2024
1992
|
entries: allEntries
|
|
2025
1993
|
} : { entries: allEntries };
|
|
2026
1994
|
return {
|
|
1995
|
+
brief,
|
|
2027
1996
|
description,
|
|
1997
|
+
footer,
|
|
2028
1998
|
fragments: [...titledSections.map((s) => ({
|
|
2029
1999
|
...s,
|
|
2030
2000
|
type: "section"
|
package/dist/constructs.js
CHANGED
|
@@ -3,46 +3,9 @@ import { DependencyRegistry, dependencyId, isDeferredParseState, isDependencySou
|
|
|
3
3
|
import { dispatchByMode, dispatchIterableByMode } from "./mode-dispatch.js";
|
|
4
4
|
import { extractArgumentMetavars, extractCommandNames, extractOptionNames } from "./usage.js";
|
|
5
5
|
import { DEFAULT_FIND_SIMILAR_OPTIONS, createErrorWithSuggestions, createSuggestionMessage, deduplicateSuggestions, findSimilar } from "./suggestion.js";
|
|
6
|
+
import { collectLeadingCandidates } from "./usage-internals.js";
|
|
6
7
|
|
|
7
8
|
//#region src/constructs.ts
|
|
8
|
-
/**
|
|
9
|
-
* Collects option names and command names that are valid at the current
|
|
10
|
-
* parse position by walking the usage tree. Only "leading" candidates
|
|
11
|
-
* (those reachable before a required positional argument) are collected.
|
|
12
|
-
*/
|
|
13
|
-
function collectLeadingCandidates(terms, optionNames, commandNames) {
|
|
14
|
-
if (!terms || !Array.isArray(terms)) return true;
|
|
15
|
-
for (const term of terms) {
|
|
16
|
-
if (term.type === "option") {
|
|
17
|
-
for (const name of term.names) optionNames.add(name);
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
if (term.type === "command") {
|
|
21
|
-
commandNames.add(term.name);
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
if (term.type === "argument") return false;
|
|
25
|
-
if (term.type === "optional") {
|
|
26
|
-
collectLeadingCandidates(term.terms, optionNames, commandNames);
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
if (term.type === "multiple") {
|
|
30
|
-
collectLeadingCandidates(term.terms, optionNames, commandNames);
|
|
31
|
-
if (term.min === 0) continue;
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
if (term.type === "exclusive") {
|
|
35
|
-
let allAlternativesSkippable = true;
|
|
36
|
-
for (const exclusiveUsage of term.terms) {
|
|
37
|
-
const alternativeSkippable = collectLeadingCandidates(exclusiveUsage, optionNames, commandNames);
|
|
38
|
-
allAlternativesSkippable = allAlternativesSkippable && alternativeSkippable;
|
|
39
|
-
}
|
|
40
|
-
if (allAlternativesSkippable) continue;
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
9
|
function createUnexpectedInputErrorWithScopedSuggestions(baseError, invalidInput, parsers, customFormatter) {
|
|
47
10
|
const options = /* @__PURE__ */ new Set();
|
|
48
11
|
const commands = /* @__PURE__ */ new Set();
|
|
@@ -2006,7 +1969,7 @@ function group(label, parser) {
|
|
|
2006
1969
|
complete: (state) => parser.complete(state),
|
|
2007
1970
|
suggest: (context, prefix) => parser.suggest(context, prefix),
|
|
2008
1971
|
getDocFragments: (state, defaultValue) => {
|
|
2009
|
-
const { description, fragments } = parser.getDocFragments(state, defaultValue);
|
|
1972
|
+
const { brief, description, footer, fragments } = parser.getDocFragments(state, defaultValue);
|
|
2010
1973
|
const allEntries = [];
|
|
2011
1974
|
const titledSections = [];
|
|
2012
1975
|
for (const fragment of fragments) if (fragment.type === "entry") allEntries.push(fragment);
|
|
@@ -2016,15 +1979,22 @@ function group(label, parser) {
|
|
|
2016
1979
|
kind: "available",
|
|
2017
1980
|
state: parser.initialState
|
|
2018
1981
|
}, void 0);
|
|
2019
|
-
const
|
|
2020
|
-
const
|
|
2021
|
-
|
|
1982
|
+
const initialCommandNames = /* @__PURE__ */ new Set();
|
|
1983
|
+
for (const f of initialFragments.fragments) if (f.type === "entry" && f.term.type === "command") initialCommandNames.add(f.term.name);
|
|
1984
|
+
else if (f.type === "section") {
|
|
1985
|
+
for (const e of f.entries) if (e.term.type === "command") initialCommandNames.add(e.term.name);
|
|
1986
|
+
}
|
|
1987
|
+
const initialHasCommands = initialCommandNames.size > 0;
|
|
1988
|
+
const currentCommandsAreGroupOwn = allEntries.some((e) => e.term.type === "command" && initialCommandNames.has(e.term.name));
|
|
1989
|
+
const applyLabel = !initialHasCommands || currentCommandsAreGroupOwn;
|
|
2022
1990
|
const labeledSection = applyLabel ? {
|
|
2023
1991
|
title: label,
|
|
2024
1992
|
entries: allEntries
|
|
2025
1993
|
} : { entries: allEntries };
|
|
2026
1994
|
return {
|
|
1995
|
+
brief,
|
|
2027
1996
|
description,
|
|
1997
|
+
footer,
|
|
2028
1998
|
fragments: [...titledSections.map((s) => ({
|
|
2029
1999
|
...s,
|
|
2030
2000
|
type: "section"
|
package/dist/facade.cjs
CHANGED
|
@@ -625,8 +625,8 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
|
|
|
625
625
|
const shouldOverride = !isMetaCommandHelp && !isSubcommandHelp;
|
|
626
626
|
const augmentedDoc = {
|
|
627
627
|
...doc,
|
|
628
|
-
brief: shouldOverride ? brief ?? doc.brief : doc.brief
|
|
629
|
-
description: shouldOverride ? description ?? doc.description : doc.description
|
|
628
|
+
brief: shouldOverride ? brief ?? doc.brief : doc.brief,
|
|
629
|
+
description: shouldOverride ? description ?? doc.description : doc.description,
|
|
630
630
|
examples: isTopLevel && !isMetaCommandHelp ? examples ?? doc.examples : void 0,
|
|
631
631
|
author: isTopLevel && !isMetaCommandHelp ? author ?? doc.author : void 0,
|
|
632
632
|
bugs: isTopLevel && !isMetaCommandHelp ? bugs ?? doc.bugs : void 0,
|
package/dist/facade.js
CHANGED
|
@@ -625,8 +625,8 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
|
|
|
625
625
|
const shouldOverride = !isMetaCommandHelp && !isSubcommandHelp;
|
|
626
626
|
const augmentedDoc = {
|
|
627
627
|
...doc,
|
|
628
|
-
brief: shouldOverride ? brief ?? doc.brief : doc.brief
|
|
629
|
-
description: shouldOverride ? description ?? doc.description : doc.description
|
|
628
|
+
brief: shouldOverride ? brief ?? doc.brief : doc.brief,
|
|
629
|
+
description: shouldOverride ? description ?? doc.description : doc.description,
|
|
630
630
|
examples: isTopLevel && !isMetaCommandHelp ? examples ?? doc.examples : void 0,
|
|
631
631
|
author: isTopLevel && !isMetaCommandHelp ? author ?? doc.author : void 0,
|
|
632
632
|
bugs: isTopLevel && !isMetaCommandHelp ? bugs ?? doc.bugs : void 0,
|
package/dist/message.cjs
CHANGED
|
@@ -253,115 +253,124 @@ function formatMessage(msg, options = {}) {
|
|
|
253
253
|
const resetSequence = `\x1b[0m${resetSuffix}`;
|
|
254
254
|
function* stream() {
|
|
255
255
|
const wordPattern = /\s*\S+\s*/g;
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
256
|
+
let prevWasLineBreak = false;
|
|
257
|
+
for (const term of msg) {
|
|
258
|
+
const isAfterLineBreak = prevWasLineBreak;
|
|
259
|
+
prevWasLineBreak = false;
|
|
260
|
+
if (term.type === "text") {
|
|
261
|
+
const rawText = isAfterLineBreak ? term.text.replace(/^\n(?!\n)/, "") : term.text;
|
|
262
|
+
if (rawText.includes("\n\n")) {
|
|
263
|
+
const paragraphs = rawText.split(/\n\n+/);
|
|
264
|
+
for (let paragraphIndex = 0; paragraphIndex < paragraphs.length; paragraphIndex++) {
|
|
265
|
+
if (paragraphIndex > 0) yield {
|
|
266
|
+
text: "\n",
|
|
267
|
+
width: -1
|
|
268
|
+
};
|
|
269
|
+
const paragraph = paragraphs[paragraphIndex].replace(/\n/g, " ");
|
|
270
|
+
wordPattern.lastIndex = 0;
|
|
271
|
+
while (true) {
|
|
272
|
+
const match = wordPattern.exec(paragraph);
|
|
273
|
+
if (match == null) break;
|
|
274
|
+
yield {
|
|
275
|
+
text: match[0],
|
|
276
|
+
width: match[0].length
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
} else {
|
|
281
|
+
const normalizedText = rawText.replace(/\n/g, " ");
|
|
282
|
+
if (normalizedText.trim() === "" && normalizedText.length > 0) yield {
|
|
283
|
+
text: " ",
|
|
284
|
+
width: 1
|
|
271
285
|
};
|
|
286
|
+
else {
|
|
287
|
+
wordPattern.lastIndex = 0;
|
|
288
|
+
while (true) {
|
|
289
|
+
const match = wordPattern.exec(normalizedText);
|
|
290
|
+
if (match == null) break;
|
|
291
|
+
yield {
|
|
292
|
+
text: match[0],
|
|
293
|
+
width: match[0].length
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
}
|
|
272
297
|
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
298
|
+
} else if (term.type === "optionName") {
|
|
299
|
+
const name = useQuotes ? `\`${term.optionName}\`` : term.optionName;
|
|
300
|
+
yield {
|
|
301
|
+
text: useColors ? `\x1b[3m${name}${resetSequence}` : name,
|
|
302
|
+
width: name.length
|
|
303
|
+
};
|
|
304
|
+
} else if (term.type === "optionNames") {
|
|
305
|
+
const names = term.optionNames.map((name) => useQuotes ? `\`${name}\`` : name);
|
|
306
|
+
let i = 0;
|
|
307
|
+
for (const name of names) {
|
|
308
|
+
if (i > 0) yield {
|
|
309
|
+
text: "/",
|
|
310
|
+
width: 1
|
|
311
|
+
};
|
|
285
312
|
yield {
|
|
286
|
-
text:
|
|
287
|
-
width:
|
|
313
|
+
text: useColors ? `\x1b[3m${name}${resetSequence}` : name,
|
|
314
|
+
width: name.length
|
|
288
315
|
};
|
|
316
|
+
i++;
|
|
289
317
|
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
318
|
+
} else if (term.type === "metavar") {
|
|
319
|
+
const metavar$1 = useQuotes ? `\`${term.metavar}\`` : term.metavar;
|
|
320
|
+
yield {
|
|
321
|
+
text: useColors ? `\x1b[1m${metavar$1}${resetSequence}` : metavar$1,
|
|
322
|
+
width: metavar$1.length
|
|
323
|
+
};
|
|
324
|
+
} else if (term.type === "value") {
|
|
325
|
+
const value$1 = useQuotes ? `${JSON.stringify(term.value)}` : term.value;
|
|
326
|
+
yield {
|
|
327
|
+
text: useColors ? `\x1b[32m${value$1}${resetSequence}` : value$1,
|
|
328
|
+
width: value$1.length
|
|
329
|
+
};
|
|
330
|
+
} else if (term.type === "values") for (let i = 0; i < term.values.length; i++) {
|
|
302
331
|
if (i > 0) yield {
|
|
303
|
-
text: "
|
|
332
|
+
text: " ",
|
|
304
333
|
width: 1
|
|
305
334
|
};
|
|
335
|
+
const value$1 = useQuotes ? JSON.stringify(term.values[i]) : term.values[i];
|
|
306
336
|
yield {
|
|
307
|
-
text: useColors ? `\x1b[
|
|
308
|
-
width:
|
|
337
|
+
text: useColors ? i <= 0 ? `\x1b[32m${value$1}` : i + 1 >= term.values.length ? `${value$1}${resetSequence}` : value$1 : value$1,
|
|
338
|
+
width: value$1.length
|
|
309
339
|
};
|
|
310
|
-
i++;
|
|
311
340
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
if (i > 0) yield {
|
|
326
|
-
text: " ",
|
|
327
|
-
width: 1
|
|
328
|
-
};
|
|
329
|
-
const value$1 = useQuotes ? JSON.stringify(term.values[i]) : term.values[i];
|
|
330
|
-
yield {
|
|
331
|
-
text: useColors ? i <= 0 ? `\x1b[32m${value$1}` : i + 1 >= term.values.length ? `${value$1}${resetSequence}` : value$1 : value$1,
|
|
332
|
-
width: value$1.length
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
else if (term.type === "envVar") {
|
|
336
|
-
const envVar$1 = useQuotes ? `\`${term.envVar}\`` : term.envVar;
|
|
337
|
-
yield {
|
|
338
|
-
text: useColors ? `\x1b[1;4m${envVar$1}${resetSequence}` : envVar$1,
|
|
339
|
-
width: envVar$1.length
|
|
340
|
-
};
|
|
341
|
-
} else if (term.type === "commandLine") {
|
|
342
|
-
const cmd = useQuotes ? `\`${term.commandLine}\`` : term.commandLine;
|
|
343
|
-
yield {
|
|
344
|
-
text: useColors ? `\x1b[36m${cmd}${resetSequence}` : cmd,
|
|
345
|
-
width: cmd.length
|
|
346
|
-
};
|
|
347
|
-
} else if (term.type === "lineBreak") yield {
|
|
348
|
-
text: "\n",
|
|
349
|
-
width: -1
|
|
350
|
-
};
|
|
351
|
-
else if (term.type === "url") {
|
|
352
|
-
const urlString = term.url.href;
|
|
353
|
-
const displayText = useQuotes ? `<${urlString}>` : urlString;
|
|
354
|
-
if (useColors) {
|
|
355
|
-
const hyperlink = `\x1b]8;;${urlString}\x1b\\${displayText}\x1b]8;;\x1b\\${resetSuffix}`;
|
|
341
|
+
else if (term.type === "envVar") {
|
|
342
|
+
const envVar$1 = useQuotes ? `\`${term.envVar}\`` : term.envVar;
|
|
343
|
+
yield {
|
|
344
|
+
text: useColors ? `\x1b[1;4m${envVar$1}${resetSequence}` : envVar$1,
|
|
345
|
+
width: envVar$1.length
|
|
346
|
+
};
|
|
347
|
+
} else if (term.type === "commandLine") {
|
|
348
|
+
const cmd = useQuotes ? `\`${term.commandLine}\`` : term.commandLine;
|
|
349
|
+
yield {
|
|
350
|
+
text: useColors ? `\x1b[36m${cmd}${resetSequence}` : cmd,
|
|
351
|
+
width: cmd.length
|
|
352
|
+
};
|
|
353
|
+
} else if (term.type === "lineBreak") {
|
|
356
354
|
yield {
|
|
357
|
-
text:
|
|
355
|
+
text: "\n",
|
|
356
|
+
width: -1
|
|
357
|
+
};
|
|
358
|
+
prevWasLineBreak = true;
|
|
359
|
+
} else if (term.type === "url") {
|
|
360
|
+
const urlString = term.url.href;
|
|
361
|
+
const displayText = useQuotes ? `<${urlString}>` : urlString;
|
|
362
|
+
if (useColors) {
|
|
363
|
+
const hyperlink = `\x1b]8;;${urlString}\x1b\\${displayText}\x1b]8;;\x1b\\${resetSuffix}`;
|
|
364
|
+
yield {
|
|
365
|
+
text: hyperlink,
|
|
366
|
+
width: displayText.length
|
|
367
|
+
};
|
|
368
|
+
} else yield {
|
|
369
|
+
text: displayText,
|
|
358
370
|
width: displayText.length
|
|
359
371
|
};
|
|
360
|
-
} else
|
|
361
|
-
|
|
362
|
-
width: displayText.length
|
|
363
|
-
};
|
|
364
|
-
} else throw new TypeError(`Invalid MessageTerm type: ${term["type"]}.`);
|
|
372
|
+
} else throw new TypeError(`Invalid MessageTerm type: ${term["type"]}.`);
|
|
373
|
+
}
|
|
365
374
|
}
|
|
366
375
|
let output = "";
|
|
367
376
|
let totalWidth = 0;
|
package/dist/message.js
CHANGED
|
@@ -252,115 +252,124 @@ function formatMessage(msg, options = {}) {
|
|
|
252
252
|
const resetSequence = `\x1b[0m${resetSuffix}`;
|
|
253
253
|
function* stream() {
|
|
254
254
|
const wordPattern = /\s*\S+\s*/g;
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
255
|
+
let prevWasLineBreak = false;
|
|
256
|
+
for (const term of msg) {
|
|
257
|
+
const isAfterLineBreak = prevWasLineBreak;
|
|
258
|
+
prevWasLineBreak = false;
|
|
259
|
+
if (term.type === "text") {
|
|
260
|
+
const rawText = isAfterLineBreak ? term.text.replace(/^\n(?!\n)/, "") : term.text;
|
|
261
|
+
if (rawText.includes("\n\n")) {
|
|
262
|
+
const paragraphs = rawText.split(/\n\n+/);
|
|
263
|
+
for (let paragraphIndex = 0; paragraphIndex < paragraphs.length; paragraphIndex++) {
|
|
264
|
+
if (paragraphIndex > 0) yield {
|
|
265
|
+
text: "\n",
|
|
266
|
+
width: -1
|
|
267
|
+
};
|
|
268
|
+
const paragraph = paragraphs[paragraphIndex].replace(/\n/g, " ");
|
|
269
|
+
wordPattern.lastIndex = 0;
|
|
270
|
+
while (true) {
|
|
271
|
+
const match = wordPattern.exec(paragraph);
|
|
272
|
+
if (match == null) break;
|
|
273
|
+
yield {
|
|
274
|
+
text: match[0],
|
|
275
|
+
width: match[0].length
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
const normalizedText = rawText.replace(/\n/g, " ");
|
|
281
|
+
if (normalizedText.trim() === "" && normalizedText.length > 0) yield {
|
|
282
|
+
text: " ",
|
|
283
|
+
width: 1
|
|
270
284
|
};
|
|
285
|
+
else {
|
|
286
|
+
wordPattern.lastIndex = 0;
|
|
287
|
+
while (true) {
|
|
288
|
+
const match = wordPattern.exec(normalizedText);
|
|
289
|
+
if (match == null) break;
|
|
290
|
+
yield {
|
|
291
|
+
text: match[0],
|
|
292
|
+
width: match[0].length
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
|
271
296
|
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
297
|
+
} else if (term.type === "optionName") {
|
|
298
|
+
const name = useQuotes ? `\`${term.optionName}\`` : term.optionName;
|
|
299
|
+
yield {
|
|
300
|
+
text: useColors ? `\x1b[3m${name}${resetSequence}` : name,
|
|
301
|
+
width: name.length
|
|
302
|
+
};
|
|
303
|
+
} else if (term.type === "optionNames") {
|
|
304
|
+
const names = term.optionNames.map((name) => useQuotes ? `\`${name}\`` : name);
|
|
305
|
+
let i = 0;
|
|
306
|
+
for (const name of names) {
|
|
307
|
+
if (i > 0) yield {
|
|
308
|
+
text: "/",
|
|
309
|
+
width: 1
|
|
310
|
+
};
|
|
284
311
|
yield {
|
|
285
|
-
text:
|
|
286
|
-
width:
|
|
312
|
+
text: useColors ? `\x1b[3m${name}${resetSequence}` : name,
|
|
313
|
+
width: name.length
|
|
287
314
|
};
|
|
315
|
+
i++;
|
|
288
316
|
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
317
|
+
} else if (term.type === "metavar") {
|
|
318
|
+
const metavar$1 = useQuotes ? `\`${term.metavar}\`` : term.metavar;
|
|
319
|
+
yield {
|
|
320
|
+
text: useColors ? `\x1b[1m${metavar$1}${resetSequence}` : metavar$1,
|
|
321
|
+
width: metavar$1.length
|
|
322
|
+
};
|
|
323
|
+
} else if (term.type === "value") {
|
|
324
|
+
const value$1 = useQuotes ? `${JSON.stringify(term.value)}` : term.value;
|
|
325
|
+
yield {
|
|
326
|
+
text: useColors ? `\x1b[32m${value$1}${resetSequence}` : value$1,
|
|
327
|
+
width: value$1.length
|
|
328
|
+
};
|
|
329
|
+
} else if (term.type === "values") for (let i = 0; i < term.values.length; i++) {
|
|
301
330
|
if (i > 0) yield {
|
|
302
|
-
text: "
|
|
331
|
+
text: " ",
|
|
303
332
|
width: 1
|
|
304
333
|
};
|
|
334
|
+
const value$1 = useQuotes ? JSON.stringify(term.values[i]) : term.values[i];
|
|
305
335
|
yield {
|
|
306
|
-
text: useColors ? `\x1b[
|
|
307
|
-
width:
|
|
336
|
+
text: useColors ? i <= 0 ? `\x1b[32m${value$1}` : i + 1 >= term.values.length ? `${value$1}${resetSequence}` : value$1 : value$1,
|
|
337
|
+
width: value$1.length
|
|
308
338
|
};
|
|
309
|
-
i++;
|
|
310
339
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (i > 0) yield {
|
|
325
|
-
text: " ",
|
|
326
|
-
width: 1
|
|
327
|
-
};
|
|
328
|
-
const value$1 = useQuotes ? JSON.stringify(term.values[i]) : term.values[i];
|
|
329
|
-
yield {
|
|
330
|
-
text: useColors ? i <= 0 ? `\x1b[32m${value$1}` : i + 1 >= term.values.length ? `${value$1}${resetSequence}` : value$1 : value$1,
|
|
331
|
-
width: value$1.length
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
else if (term.type === "envVar") {
|
|
335
|
-
const envVar$1 = useQuotes ? `\`${term.envVar}\`` : term.envVar;
|
|
336
|
-
yield {
|
|
337
|
-
text: useColors ? `\x1b[1;4m${envVar$1}${resetSequence}` : envVar$1,
|
|
338
|
-
width: envVar$1.length
|
|
339
|
-
};
|
|
340
|
-
} else if (term.type === "commandLine") {
|
|
341
|
-
const cmd = useQuotes ? `\`${term.commandLine}\`` : term.commandLine;
|
|
342
|
-
yield {
|
|
343
|
-
text: useColors ? `\x1b[36m${cmd}${resetSequence}` : cmd,
|
|
344
|
-
width: cmd.length
|
|
345
|
-
};
|
|
346
|
-
} else if (term.type === "lineBreak") yield {
|
|
347
|
-
text: "\n",
|
|
348
|
-
width: -1
|
|
349
|
-
};
|
|
350
|
-
else if (term.type === "url") {
|
|
351
|
-
const urlString = term.url.href;
|
|
352
|
-
const displayText = useQuotes ? `<${urlString}>` : urlString;
|
|
353
|
-
if (useColors) {
|
|
354
|
-
const hyperlink = `\x1b]8;;${urlString}\x1b\\${displayText}\x1b]8;;\x1b\\${resetSuffix}`;
|
|
340
|
+
else if (term.type === "envVar") {
|
|
341
|
+
const envVar$1 = useQuotes ? `\`${term.envVar}\`` : term.envVar;
|
|
342
|
+
yield {
|
|
343
|
+
text: useColors ? `\x1b[1;4m${envVar$1}${resetSequence}` : envVar$1,
|
|
344
|
+
width: envVar$1.length
|
|
345
|
+
};
|
|
346
|
+
} else if (term.type === "commandLine") {
|
|
347
|
+
const cmd = useQuotes ? `\`${term.commandLine}\`` : term.commandLine;
|
|
348
|
+
yield {
|
|
349
|
+
text: useColors ? `\x1b[36m${cmd}${resetSequence}` : cmd,
|
|
350
|
+
width: cmd.length
|
|
351
|
+
};
|
|
352
|
+
} else if (term.type === "lineBreak") {
|
|
355
353
|
yield {
|
|
356
|
-
text:
|
|
354
|
+
text: "\n",
|
|
355
|
+
width: -1
|
|
356
|
+
};
|
|
357
|
+
prevWasLineBreak = true;
|
|
358
|
+
} else if (term.type === "url") {
|
|
359
|
+
const urlString = term.url.href;
|
|
360
|
+
const displayText = useQuotes ? `<${urlString}>` : urlString;
|
|
361
|
+
if (useColors) {
|
|
362
|
+
const hyperlink = `\x1b]8;;${urlString}\x1b\\${displayText}\x1b]8;;\x1b\\${resetSuffix}`;
|
|
363
|
+
yield {
|
|
364
|
+
text: hyperlink,
|
|
365
|
+
width: displayText.length
|
|
366
|
+
};
|
|
367
|
+
} else yield {
|
|
368
|
+
text: displayText,
|
|
357
369
|
width: displayText.length
|
|
358
370
|
};
|
|
359
|
-
} else
|
|
360
|
-
|
|
361
|
-
width: displayText.length
|
|
362
|
-
};
|
|
363
|
-
} else throw new TypeError(`Invalid MessageTerm type: ${term["type"]}.`);
|
|
371
|
+
} else throw new TypeError(`Invalid MessageTerm type: ${term["type"]}.`);
|
|
372
|
+
}
|
|
364
373
|
}
|
|
365
374
|
let output = "";
|
|
366
375
|
let totalWidth = 0;
|
package/dist/primitives.cjs
CHANGED
|
@@ -2,6 +2,7 @@ const require_message = require('./message.cjs');
|
|
|
2
2
|
const require_dependency = require('./dependency.cjs');
|
|
3
3
|
const require_usage = require('./usage.cjs');
|
|
4
4
|
const require_suggestion = require('./suggestion.cjs');
|
|
5
|
+
const require_usage_internals = require('./usage-internals.cjs');
|
|
5
6
|
const require_valueparser = require('./valueparser.cjs');
|
|
6
7
|
|
|
7
8
|
//#region src/primitives.ts
|
|
@@ -881,11 +882,10 @@ function command(name, parser, options = {}) {
|
|
|
881
882
|
if (context.state === void 0) {
|
|
882
883
|
if (context.buffer.length < 1 || context.buffer[0] !== name) {
|
|
883
884
|
const actual = context.buffer.length > 0 ? context.buffer[0] : null;
|
|
885
|
+
const leadingCmds = require_usage_internals.extractLeadingCommandNames(context.usage);
|
|
886
|
+
const suggestions = actual ? require_suggestion.findSimilar(actual, leadingCmds, require_suggestion.DEFAULT_FIND_SIMILAR_OPTIONS) : [];
|
|
884
887
|
if (options.errors?.notMatched) {
|
|
885
888
|
const errorMessage = options.errors.notMatched;
|
|
886
|
-
const candidates = /* @__PURE__ */ new Set();
|
|
887
|
-
for (const cmdName of require_usage.extractCommandNames(context.usage)) candidates.add(cmdName);
|
|
888
|
-
const suggestions = actual ? require_suggestion.findSimilar(actual, candidates, require_suggestion.DEFAULT_FIND_SIMILAR_OPTIONS) : [];
|
|
889
889
|
return {
|
|
890
890
|
success: false,
|
|
891
891
|
consumed: 0,
|
|
@@ -898,10 +898,15 @@ function command(name, parser, options = {}) {
|
|
|
898
898
|
error: require_message.message`Expected command ${require_message.optionName(name)}, but got end of input.`
|
|
899
899
|
};
|
|
900
900
|
const baseError = require_message.message`Expected command ${require_message.optionName(name)}, but got ${actual}.`;
|
|
901
|
+
const suggestionMsg = require_suggestion.createSuggestionMessage(suggestions);
|
|
901
902
|
return {
|
|
902
903
|
success: false,
|
|
903
904
|
consumed: 0,
|
|
904
|
-
error:
|
|
905
|
+
error: suggestionMsg.length > 0 ? [
|
|
906
|
+
...baseError,
|
|
907
|
+
require_message.text("\n\n"),
|
|
908
|
+
...suggestionMsg
|
|
909
|
+
] : baseError
|
|
905
910
|
};
|
|
906
911
|
}
|
|
907
912
|
return {
|
package/dist/primitives.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { message, metavar, optionName, optionNames, valueSet } from "./message.js";
|
|
1
|
+
import { message, metavar, optionName, optionNames, text, valueSet } from "./message.js";
|
|
2
2
|
import { createDeferredParseState, createDependencySourceState, createPendingDependencySourceState, dependencyId, getDefaultValuesFunction, getDependencyIds, isDeferredParseState, isDependencySource, isDependencySourceState, isDerivedValueParser, isPendingDependencySourceState, suggestWithDependency } from "./dependency.js";
|
|
3
|
-
import {
|
|
4
|
-
import { DEFAULT_FIND_SIMILAR_OPTIONS, createErrorWithSuggestions, findSimilar } from "./suggestion.js";
|
|
3
|
+
import { extractOptionNames } from "./usage.js";
|
|
4
|
+
import { DEFAULT_FIND_SIMILAR_OPTIONS, createErrorWithSuggestions, createSuggestionMessage, findSimilar } from "./suggestion.js";
|
|
5
|
+
import { extractLeadingCommandNames } from "./usage-internals.js";
|
|
5
6
|
import { isValueParser } from "./valueparser.js";
|
|
6
7
|
|
|
7
8
|
//#region src/primitives.ts
|
|
@@ -881,11 +882,10 @@ function command(name, parser, options = {}) {
|
|
|
881
882
|
if (context.state === void 0) {
|
|
882
883
|
if (context.buffer.length < 1 || context.buffer[0] !== name) {
|
|
883
884
|
const actual = context.buffer.length > 0 ? context.buffer[0] : null;
|
|
885
|
+
const leadingCmds = extractLeadingCommandNames(context.usage);
|
|
886
|
+
const suggestions = actual ? findSimilar(actual, leadingCmds, DEFAULT_FIND_SIMILAR_OPTIONS) : [];
|
|
884
887
|
if (options.errors?.notMatched) {
|
|
885
888
|
const errorMessage = options.errors.notMatched;
|
|
886
|
-
const candidates = /* @__PURE__ */ new Set();
|
|
887
|
-
for (const cmdName of extractCommandNames(context.usage)) candidates.add(cmdName);
|
|
888
|
-
const suggestions = actual ? findSimilar(actual, candidates, DEFAULT_FIND_SIMILAR_OPTIONS) : [];
|
|
889
889
|
return {
|
|
890
890
|
success: false,
|
|
891
891
|
consumed: 0,
|
|
@@ -898,10 +898,15 @@ function command(name, parser, options = {}) {
|
|
|
898
898
|
error: message`Expected command ${optionName(name)}, but got end of input.`
|
|
899
899
|
};
|
|
900
900
|
const baseError = message`Expected command ${optionName(name)}, but got ${actual}.`;
|
|
901
|
+
const suggestionMsg = createSuggestionMessage(suggestions);
|
|
901
902
|
return {
|
|
902
903
|
success: false,
|
|
903
904
|
consumed: 0,
|
|
904
|
-
error:
|
|
905
|
+
error: suggestionMsg.length > 0 ? [
|
|
906
|
+
...baseError,
|
|
907
|
+
text("\n\n"),
|
|
908
|
+
...suggestionMsg
|
|
909
|
+
] : baseError
|
|
905
910
|
};
|
|
906
911
|
}
|
|
907
912
|
return {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/usage-internals.ts
|
|
3
|
+
/**
|
|
4
|
+
* Collects option names and command names that are valid as the *immediate*
|
|
5
|
+
* next token at the current parse position ("leading candidates").
|
|
6
|
+
*
|
|
7
|
+
* Unlike the full-tree extractors in `usage.ts`, this function stops
|
|
8
|
+
* descending into a branch as soon as it hits a required (blocking) term —
|
|
9
|
+
* an option, a command, or a required argument. Optional and zero-or-more
|
|
10
|
+
* terms are traversed but do not block.
|
|
11
|
+
*
|
|
12
|
+
* @param terms The usage terms to inspect.
|
|
13
|
+
* @param optionNames Accumulator for leading option names.
|
|
14
|
+
* @param commandNames Accumulator for leading command names.
|
|
15
|
+
* @returns `true` if every term in `terms` is skippable (i.e., the caller
|
|
16
|
+
* may continue scanning the next sibling term), `false` otherwise.
|
|
17
|
+
*/
|
|
18
|
+
function collectLeadingCandidates(terms, optionNames, commandNames) {
|
|
19
|
+
if (!terms || !Array.isArray(terms)) return true;
|
|
20
|
+
for (const term of terms) {
|
|
21
|
+
if (term.type === "option") {
|
|
22
|
+
for (const name of term.names) optionNames.add(name);
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
if (term.type === "command") {
|
|
26
|
+
commandNames.add(term.name);
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
if (term.type === "argument") return false;
|
|
30
|
+
if (term.type === "optional") {
|
|
31
|
+
collectLeadingCandidates(term.terms, optionNames, commandNames);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (term.type === "multiple") {
|
|
35
|
+
collectLeadingCandidates(term.terms, optionNames, commandNames);
|
|
36
|
+
if (term.min === 0) continue;
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
if (term.type === "exclusive") {
|
|
40
|
+
let allSkippable = true;
|
|
41
|
+
for (const branch of term.terms) {
|
|
42
|
+
const branchSkippable = collectLeadingCandidates(branch, optionNames, commandNames);
|
|
43
|
+
allSkippable = allSkippable && branchSkippable;
|
|
44
|
+
}
|
|
45
|
+
if (allSkippable) continue;
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Returns the set of command names that are valid as the *immediate* next
|
|
53
|
+
* token, derived from the leading candidates of `usage`.
|
|
54
|
+
*
|
|
55
|
+
* This is the command-only projection of {@link collectLeadingCandidates}
|
|
56
|
+
* and is used to generate accurate "Did you mean?" suggestions in
|
|
57
|
+
* `command()` error messages — suggestions are scoped to commands actually
|
|
58
|
+
* reachable at the current parse position rather than all commands anywhere
|
|
59
|
+
* in the usage tree.
|
|
60
|
+
*
|
|
61
|
+
* @param usage The usage tree to inspect.
|
|
62
|
+
* @returns A `Set` of command names valid as the next input token.
|
|
63
|
+
*/
|
|
64
|
+
function extractLeadingCommandNames(usage) {
|
|
65
|
+
const options = /* @__PURE__ */ new Set();
|
|
66
|
+
const commands = /* @__PURE__ */ new Set();
|
|
67
|
+
collectLeadingCandidates(usage, options, commands);
|
|
68
|
+
return commands;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
exports.collectLeadingCandidates = collectLeadingCandidates;
|
|
73
|
+
exports.extractLeadingCommandNames = extractLeadingCommandNames;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
//#region src/usage-internals.ts
|
|
2
|
+
/**
|
|
3
|
+
* Collects option names and command names that are valid as the *immediate*
|
|
4
|
+
* next token at the current parse position ("leading candidates").
|
|
5
|
+
*
|
|
6
|
+
* Unlike the full-tree extractors in `usage.ts`, this function stops
|
|
7
|
+
* descending into a branch as soon as it hits a required (blocking) term —
|
|
8
|
+
* an option, a command, or a required argument. Optional and zero-or-more
|
|
9
|
+
* terms are traversed but do not block.
|
|
10
|
+
*
|
|
11
|
+
* @param terms The usage terms to inspect.
|
|
12
|
+
* @param optionNames Accumulator for leading option names.
|
|
13
|
+
* @param commandNames Accumulator for leading command names.
|
|
14
|
+
* @returns `true` if every term in `terms` is skippable (i.e., the caller
|
|
15
|
+
* may continue scanning the next sibling term), `false` otherwise.
|
|
16
|
+
*/
|
|
17
|
+
function collectLeadingCandidates(terms, optionNames, commandNames) {
|
|
18
|
+
if (!terms || !Array.isArray(terms)) return true;
|
|
19
|
+
for (const term of terms) {
|
|
20
|
+
if (term.type === "option") {
|
|
21
|
+
for (const name of term.names) optionNames.add(name);
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if (term.type === "command") {
|
|
25
|
+
commandNames.add(term.name);
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (term.type === "argument") return false;
|
|
29
|
+
if (term.type === "optional") {
|
|
30
|
+
collectLeadingCandidates(term.terms, optionNames, commandNames);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (term.type === "multiple") {
|
|
34
|
+
collectLeadingCandidates(term.terms, optionNames, commandNames);
|
|
35
|
+
if (term.min === 0) continue;
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (term.type === "exclusive") {
|
|
39
|
+
let allSkippable = true;
|
|
40
|
+
for (const branch of term.terms) {
|
|
41
|
+
const branchSkippable = collectLeadingCandidates(branch, optionNames, commandNames);
|
|
42
|
+
allSkippable = allSkippable && branchSkippable;
|
|
43
|
+
}
|
|
44
|
+
if (allSkippable) continue;
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Returns the set of command names that are valid as the *immediate* next
|
|
52
|
+
* token, derived from the leading candidates of `usage`.
|
|
53
|
+
*
|
|
54
|
+
* This is the command-only projection of {@link collectLeadingCandidates}
|
|
55
|
+
* and is used to generate accurate "Did you mean?" suggestions in
|
|
56
|
+
* `command()` error messages — suggestions are scoped to commands actually
|
|
57
|
+
* reachable at the current parse position rather than all commands anywhere
|
|
58
|
+
* in the usage tree.
|
|
59
|
+
*
|
|
60
|
+
* @param usage The usage tree to inspect.
|
|
61
|
+
* @returns A `Set` of command names valid as the next input token.
|
|
62
|
+
*/
|
|
63
|
+
function extractLeadingCommandNames(usage) {
|
|
64
|
+
const options = /* @__PURE__ */ new Set();
|
|
65
|
+
const commands = /* @__PURE__ */ new Set();
|
|
66
|
+
collectLeadingCandidates(usage, options, commands);
|
|
67
|
+
return commands;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
export { collectLeadingCandidates, extractLeadingCommandNames };
|