@optique/core 1.1.2 → 1.1.4
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 +155 -58
- package/dist/constructs.js +155 -58
- package/package.json +2 -2
package/dist/constructs.cjs
CHANGED
|
@@ -581,6 +581,35 @@ function createExclusiveState(parentState, index, parser, result) {
|
|
|
581
581
|
return require_internal_annotations.annotateFreshArray(parentState, [index, annotatedResult]);
|
|
582
582
|
}
|
|
583
583
|
/**
|
|
584
|
+
* Detects a branch result that handled only the global options terminator.
|
|
585
|
+
* Such a result advances the shared parser context, but does not identify
|
|
586
|
+
* which exclusive branch matched.
|
|
587
|
+
* @internal
|
|
588
|
+
*/
|
|
589
|
+
function isOptionsTerminatorOnlyResult(context, result) {
|
|
590
|
+
return !context.optionsTerminated && context.buffer[0] === "--" && result.success && result.next.optionsTerminated && result.consumed.length === 1 && result.consumed[0] === "--";
|
|
591
|
+
}
|
|
592
|
+
function isUnselectedBooleanOptionState(state) {
|
|
593
|
+
return state != null && typeof state === "object" && "success" in state && state.success === true && "value" in state && state.value === false;
|
|
594
|
+
}
|
|
595
|
+
function preserveExclusiveStateAfterOptionsTerminator(context, result) {
|
|
596
|
+
const mergedExec = require_execution_context.mergeChildExec(context.exec, result.next.exec);
|
|
597
|
+
return {
|
|
598
|
+
success: true,
|
|
599
|
+
next: {
|
|
600
|
+
...context,
|
|
601
|
+
buffer: result.next.buffer,
|
|
602
|
+
optionsTerminated: true,
|
|
603
|
+
state: context.state,
|
|
604
|
+
...mergedExec != null ? {
|
|
605
|
+
exec: mergedExec,
|
|
606
|
+
dependencyRegistry: mergedExec.dependencyRegistry
|
|
607
|
+
} : {}
|
|
608
|
+
},
|
|
609
|
+
consumed: result.consumed
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
584
613
|
* Creates a complete() method shared by or() and longestMatch().
|
|
585
614
|
* @internal
|
|
586
615
|
*/
|
|
@@ -818,9 +847,15 @@ function or(...args) {
|
|
|
818
847
|
let zeroConsumedBranch = null;
|
|
819
848
|
let zeroConsumedCount = 0;
|
|
820
849
|
let provisionalConsuming = null;
|
|
850
|
+
let optionsTerminatorResult = null;
|
|
821
851
|
let provisionalAmbiguous = false;
|
|
822
852
|
for (const [parser, i] of orderedParsers) {
|
|
823
853
|
const result = parser.parse(withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
|
|
854
|
+
if (isOptionsTerminatorOnlyResult(context, result)) {
|
|
855
|
+
if (activeState?.[0] === i && activeState[1].success && activeState[1].consumed.length > 0 && result.next.buffer.length < 1) return preserveExclusiveStateAfterOptionsTerminator(context, result);
|
|
856
|
+
optionsTerminatorResult ??= result;
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
824
859
|
if (result.success && result.consumed.length > 0) {
|
|
825
860
|
if (result.provisional) {
|
|
826
861
|
const activeBranchLocked = activeState != null && activeState[1].success && activeState[1].consumed.length > 0;
|
|
@@ -1001,6 +1036,7 @@ function or(...args) {
|
|
|
1001
1036
|
}
|
|
1002
1037
|
}
|
|
1003
1038
|
}
|
|
1039
|
+
if (optionsTerminatorResult != null) return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorResult);
|
|
1004
1040
|
return {
|
|
1005
1041
|
...error,
|
|
1006
1042
|
success: false
|
|
@@ -1014,10 +1050,16 @@ function or(...args) {
|
|
|
1014
1050
|
let zeroConsumedBranch = null;
|
|
1015
1051
|
let zeroConsumedCount = 0;
|
|
1016
1052
|
let provisionalConsuming = null;
|
|
1053
|
+
let optionsTerminatorResult = null;
|
|
1017
1054
|
let provisionalAmbiguous = false;
|
|
1018
1055
|
for (const [parser, i] of orderedParsers) {
|
|
1019
1056
|
const resultOrPromise = parser.parse(withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
|
|
1020
1057
|
const result = await resultOrPromise;
|
|
1058
|
+
if (isOptionsTerminatorOnlyResult(context, result)) {
|
|
1059
|
+
if (activeState?.[0] === i && activeState[1].success && activeState[1].consumed.length > 0 && result.next.buffer.length < 1) return preserveExclusiveStateAfterOptionsTerminator(context, result);
|
|
1060
|
+
optionsTerminatorResult ??= result;
|
|
1061
|
+
continue;
|
|
1062
|
+
}
|
|
1021
1063
|
if (result.success && result.consumed.length > 0) {
|
|
1022
1064
|
if (result.provisional) {
|
|
1023
1065
|
const activeBranchLocked = activeState != null && activeState[1].success && activeState[1].consumed.length > 0;
|
|
@@ -1200,6 +1242,7 @@ function or(...args) {
|
|
|
1200
1242
|
}
|
|
1201
1243
|
}
|
|
1202
1244
|
}
|
|
1245
|
+
if (optionsTerminatorResult != null) return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorResult);
|
|
1203
1246
|
return {
|
|
1204
1247
|
...error,
|
|
1205
1248
|
success: false
|
|
@@ -1301,82 +1344,122 @@ function createLongestMatch(...args) {
|
|
|
1301
1344
|
return createUnexpectedInputErrorWithScopedSuggestions(defaultMsg, token, parsers, options?.errors?.suggestions);
|
|
1302
1345
|
})()
|
|
1303
1346
|
});
|
|
1304
|
-
const
|
|
1347
|
+
const commitLongestMatch = (context, match, optionsTerminatorResult) => {
|
|
1348
|
+
const selectedResult = optionsTerminatorResult == null ? match.result : {
|
|
1349
|
+
...match.result,
|
|
1350
|
+
next: {
|
|
1351
|
+
...match.result.next,
|
|
1352
|
+
buffer: optionsTerminatorResult.next.buffer,
|
|
1353
|
+
optionsTerminated: true
|
|
1354
|
+
},
|
|
1355
|
+
consumed: [...optionsTerminatorResult.consumed, ...match.result.consumed]
|
|
1356
|
+
};
|
|
1357
|
+
const parser = parsers[match.index];
|
|
1358
|
+
const mergedExec = require_execution_context.mergeChildExec(context.exec, selectedResult.next.exec);
|
|
1359
|
+
return {
|
|
1360
|
+
success: true,
|
|
1361
|
+
next: {
|
|
1362
|
+
...context,
|
|
1363
|
+
buffer: selectedResult.next.buffer,
|
|
1364
|
+
optionsTerminated: selectedResult.next.optionsTerminated,
|
|
1365
|
+
state: createExclusiveState(context.state, match.index, parser, selectedResult),
|
|
1366
|
+
...mergedExec != null ? {
|
|
1367
|
+
exec: mergedExec,
|
|
1368
|
+
dependencyRegistry: mergedExec.dependencyRegistry
|
|
1369
|
+
} : {}
|
|
1370
|
+
},
|
|
1371
|
+
consumed: selectedResult.consumed
|
|
1372
|
+
};
|
|
1373
|
+
};
|
|
1374
|
+
const resolveLongestMatchOptionsTerminator = (context, bestMatch, optionsTerminatorMatches) => {
|
|
1375
|
+
if (optionsTerminatorMatches.length < 1) return null;
|
|
1376
|
+
if (bestMatch != null && bestMatch.consumed > 0) return null;
|
|
1377
|
+
const activeState = normalizeExclusiveState(context.state);
|
|
1378
|
+
if (activeState != null && activeState[1].success && activeState[1].consumed.length > 0) return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorMatches[0].result);
|
|
1379
|
+
let selected = bestMatch == null ? null : {
|
|
1380
|
+
match: bestMatch,
|
|
1381
|
+
fromTerminator: false
|
|
1382
|
+
};
|
|
1383
|
+
for (const match of optionsTerminatorMatches) {
|
|
1384
|
+
if (!match.selectedAfterTerminator && (extractRequiredUsage(parsers[match.index].usage).length > 0 || isUnselectedBooleanOptionState(match.result.next.state))) continue;
|
|
1385
|
+
const selectedIsProvisional = selected != null && !!selected.match.result.provisional;
|
|
1386
|
+
const matchIsProvisional = !!match.result.provisional;
|
|
1387
|
+
if (selected == null || selectedIsProvisional && !matchIsProvisional || selectedIsProvisional === matchIsProvisional && match.index < selected.match.index) selected = {
|
|
1388
|
+
match,
|
|
1389
|
+
fromTerminator: true
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
if (selected != null) return commitLongestMatch(context, selected.match, selected.fromTerminator ? void 0 : optionsTerminatorMatches[0].result);
|
|
1393
|
+
return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorMatches[0].result);
|
|
1394
|
+
};
|
|
1395
|
+
const parseSyncBranches = (context) => {
|
|
1305
1396
|
let bestMatch = null;
|
|
1397
|
+
const optionsTerminatorMatches = [];
|
|
1306
1398
|
let error = getInitialError(context);
|
|
1307
1399
|
const activeState = normalizeExclusiveState(context.state);
|
|
1308
1400
|
for (let i = 0; i < syncParsers.length; i++) {
|
|
1309
1401
|
const parser = syncParsers[i];
|
|
1310
|
-
const
|
|
1402
|
+
const childContext = withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser);
|
|
1403
|
+
const result = parser.parse(childContext);
|
|
1404
|
+
if (isOptionsTerminatorOnlyResult(context, result)) {
|
|
1405
|
+
optionsTerminatorMatches.push({
|
|
1406
|
+
index: i,
|
|
1407
|
+
result,
|
|
1408
|
+
consumed: 1,
|
|
1409
|
+
...normalizeExclusiveState(result.next.state) != null ? { selectedAfterTerminator: true } : {}
|
|
1410
|
+
});
|
|
1411
|
+
continue;
|
|
1412
|
+
}
|
|
1311
1413
|
if (result.success) {
|
|
1312
1414
|
const consumed = context.buffer.length - result.next.buffer.length;
|
|
1313
1415
|
const bestIsProvisional = bestMatch != null && bestMatch.result.success && !!bestMatch.result.provisional;
|
|
1314
1416
|
if (bestMatch === null || consumed > bestMatch.consumed || consumed === bestMatch.consumed && bestIsProvisional && !result.provisional) bestMatch = {
|
|
1315
1417
|
index: i,
|
|
1316
|
-
parser,
|
|
1317
1418
|
result,
|
|
1318
1419
|
consumed
|
|
1319
1420
|
};
|
|
1320
1421
|
} else if (error.consumed < result.consumed) error = result;
|
|
1321
1422
|
}
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
success: true,
|
|
1326
|
-
next: {
|
|
1327
|
-
...context,
|
|
1328
|
-
buffer: bestMatch.result.next.buffer,
|
|
1329
|
-
optionsTerminated: bestMatch.result.next.optionsTerminated,
|
|
1330
|
-
state: createExclusiveState(context.state, bestMatch.index, bestMatch.parser, bestMatch.result),
|
|
1331
|
-
...mergedExec != null ? {
|
|
1332
|
-
exec: mergedExec,
|
|
1333
|
-
dependencyRegistry: mergedExec.dependencyRegistry
|
|
1334
|
-
} : {}
|
|
1335
|
-
},
|
|
1336
|
-
consumed: bestMatch.result.consumed
|
|
1337
|
-
};
|
|
1338
|
-
}
|
|
1423
|
+
const optionsTerminatorResolution = resolveLongestMatchOptionsTerminator(context, bestMatch, optionsTerminatorMatches);
|
|
1424
|
+
if (optionsTerminatorResolution != null) return optionsTerminatorResolution;
|
|
1425
|
+
if (bestMatch != null) return commitLongestMatch(context, bestMatch);
|
|
1339
1426
|
return {
|
|
1340
1427
|
...error,
|
|
1341
1428
|
success: false
|
|
1342
1429
|
};
|
|
1343
1430
|
};
|
|
1344
|
-
const
|
|
1431
|
+
const parseAsyncBranches = async (context) => {
|
|
1345
1432
|
let bestMatch = null;
|
|
1433
|
+
const optionsTerminatorMatches = [];
|
|
1346
1434
|
let error = getInitialError(context);
|
|
1347
1435
|
const activeState = normalizeExclusiveState(context.state);
|
|
1348
1436
|
for (let i = 0; i < parsers.length; i++) {
|
|
1349
1437
|
const parser = parsers[i];
|
|
1350
|
-
const
|
|
1438
|
+
const childContext = withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser);
|
|
1439
|
+
const resultOrPromise = parser.parse(childContext);
|
|
1351
1440
|
const result = await resultOrPromise;
|
|
1441
|
+
if (isOptionsTerminatorOnlyResult(context, result)) {
|
|
1442
|
+
optionsTerminatorMatches.push({
|
|
1443
|
+
index: i,
|
|
1444
|
+
result,
|
|
1445
|
+
consumed: 1,
|
|
1446
|
+
...normalizeExclusiveState(result.next.state) != null ? { selectedAfterTerminator: true } : {}
|
|
1447
|
+
});
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1352
1450
|
if (result.success) {
|
|
1353
1451
|
const consumed = context.buffer.length - result.next.buffer.length;
|
|
1354
1452
|
const bestIsProvisional = bestMatch != null && bestMatch.result.success && !!bestMatch.result.provisional;
|
|
1355
1453
|
if (bestMatch === null || consumed > bestMatch.consumed || consumed === bestMatch.consumed && bestIsProvisional && !result.provisional) bestMatch = {
|
|
1356
1454
|
index: i,
|
|
1357
|
-
parser,
|
|
1358
1455
|
result,
|
|
1359
1456
|
consumed
|
|
1360
1457
|
};
|
|
1361
1458
|
} else if (error.consumed < result.consumed) error = result;
|
|
1362
1459
|
}
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
success: true,
|
|
1367
|
-
next: {
|
|
1368
|
-
...context,
|
|
1369
|
-
buffer: bestMatch.result.next.buffer,
|
|
1370
|
-
optionsTerminated: bestMatch.result.next.optionsTerminated,
|
|
1371
|
-
state: createExclusiveState(context.state, bestMatch.index, bestMatch.parser, bestMatch.result),
|
|
1372
|
-
...mergedExec != null ? {
|
|
1373
|
-
exec: mergedExec,
|
|
1374
|
-
dependencyRegistry: mergedExec.dependencyRegistry
|
|
1375
|
-
} : {}
|
|
1376
|
-
},
|
|
1377
|
-
consumed: bestMatch.result.consumed
|
|
1378
|
-
};
|
|
1379
|
-
}
|
|
1460
|
+
const optionsTerminatorResolution = resolveLongestMatchOptionsTerminator(context, bestMatch, optionsTerminatorMatches);
|
|
1461
|
+
if (optionsTerminatorResolution != null) return optionsTerminatorResolution;
|
|
1462
|
+
if (bestMatch != null) return commitLongestMatch(context, bestMatch);
|
|
1380
1463
|
return {
|
|
1381
1464
|
...error,
|
|
1382
1465
|
success: false
|
|
@@ -1399,7 +1482,7 @@ function createLongestMatch(...args) {
|
|
|
1399
1482
|
return extractExclusivePhase2Seed(parsers, state, exec, combinedMode);
|
|
1400
1483
|
},
|
|
1401
1484
|
parse(context) {
|
|
1402
|
-
return require_mode_dispatch.dispatchByMode(combinedMode, () =>
|
|
1485
|
+
return require_mode_dispatch.dispatchByMode(combinedMode, () => parseSyncBranches(context), () => parseAsyncBranches(context));
|
|
1403
1486
|
},
|
|
1404
1487
|
getSuggestRuntimeNodes(state, path) {
|
|
1405
1488
|
return getExclusiveSuggestRuntimeNodes(parsers, state, path);
|
|
@@ -1445,6 +1528,33 @@ function createLongestMatch(...args) {
|
|
|
1445
1528
|
return multiResult;
|
|
1446
1529
|
}
|
|
1447
1530
|
/**
|
|
1531
|
+
* Creates the initial parse error shared by object-like combinators.
|
|
1532
|
+
* @param context The current parser context.
|
|
1533
|
+
* @param noMatchContext The kinds of input accepted by the combinator.
|
|
1534
|
+
* @param errors Optional custom error formatters.
|
|
1535
|
+
* @returns A zero-consumption parse error.
|
|
1536
|
+
*/
|
|
1537
|
+
function createObjectLikeInitialError(context, noMatchContext, errors) {
|
|
1538
|
+
if (context.buffer.length < 1) {
|
|
1539
|
+
const customEndOfInput = errors?.endOfInput;
|
|
1540
|
+
return {
|
|
1541
|
+
consumed: 0,
|
|
1542
|
+
error: customEndOfInput ? typeof customEndOfInput === "function" ? customEndOfInput(noMatchContext) : customEndOfInput : generateNoMatchError(noMatchContext)
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
const token = context.buffer[0];
|
|
1546
|
+
const customMessage = errors?.unexpectedInput;
|
|
1547
|
+
if (customMessage) return {
|
|
1548
|
+
consumed: 0,
|
|
1549
|
+
error: typeof customMessage === "function" ? customMessage(token) : customMessage
|
|
1550
|
+
};
|
|
1551
|
+
const baseError = require_message.message`Unexpected option or argument: ${token}.`;
|
|
1552
|
+
return {
|
|
1553
|
+
consumed: 0,
|
|
1554
|
+
error: require_suggestion.createErrorWithSuggestions(baseError, token, context.usage, "both", errors?.suggestions)
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1557
|
+
/**
|
|
1448
1558
|
* Internal sync helper for object suggest functionality.
|
|
1449
1559
|
* @internal
|
|
1450
1560
|
*/
|
|
@@ -1837,19 +1947,7 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
|
|
|
1837
1947
|
checkDuplicateReachableLeadingCommandNames(parserPairs.map(([field, parser]) => [field, parser]));
|
|
1838
1948
|
const noMatchContext = analyzeNoMatchContext(parserKeys.map((k) => parsers[k]));
|
|
1839
1949
|
const combinedMode = parserKeys.some((k) => parsers[k].mode === "async") ? "async" : "sync";
|
|
1840
|
-
const getInitialError = (context) => (
|
|
1841
|
-
consumed: 0,
|
|
1842
|
-
error: context.buffer.length > 0 ? (() => {
|
|
1843
|
-
const token = context.buffer[0];
|
|
1844
|
-
const customMessage = options.errors?.unexpectedInput;
|
|
1845
|
-
if (customMessage) return typeof customMessage === "function" ? customMessage(token) : customMessage;
|
|
1846
|
-
const baseError = require_message.message`Unexpected option or argument: ${token}.`;
|
|
1847
|
-
return require_suggestion.createErrorWithSuggestions(baseError, token, context.usage, "both", options.errors?.suggestions);
|
|
1848
|
-
})() : (() => {
|
|
1849
|
-
const customEndOfInput = options.errors?.endOfInput;
|
|
1850
|
-
return customEndOfInput ? typeof customEndOfInput === "function" ? customEndOfInput(noMatchContext) : customEndOfInput : generateNoMatchError(noMatchContext);
|
|
1851
|
-
})()
|
|
1852
|
-
});
|
|
1950
|
+
const getInitialError = (context) => createObjectLikeInitialError(context, noMatchContext, options.errors);
|
|
1853
1951
|
const parseSync = (context) => {
|
|
1854
1952
|
let error = getInitialError(context);
|
|
1855
1953
|
let currentContext = context;
|
|
@@ -3719,6 +3817,7 @@ function merge(...args) {
|
|
|
3719
3817
|
const syncParsers = syncSorted.map(([p]) => p);
|
|
3720
3818
|
if (!options.allowDuplicates) checkDuplicateOptionNames(sorted.map(([parser, originalIndex]) => [String(originalIndex), parser.usage]));
|
|
3721
3819
|
checkDuplicateReachableLeadingCommandNames(sorted.map(([parser, originalIndex]) => [String(originalIndex), parser]));
|
|
3820
|
+
const noMatchContext = analyzeNoMatchContext(rawParsers);
|
|
3722
3821
|
const mergedFieldParsers = collectChildFieldParsers(parsers);
|
|
3723
3822
|
const duplicateOutputFieldNames = collectDuplicateFieldNames(mergedFieldParsers);
|
|
3724
3823
|
const parserStateKey = (index) => `__parser_${index}`;
|
|
@@ -3812,8 +3911,7 @@ function merge(...args) {
|
|
|
3812
3911
|
};
|
|
3813
3912
|
return {
|
|
3814
3913
|
success: false,
|
|
3815
|
-
|
|
3816
|
-
error: require_message.message`No matching option or argument found.`
|
|
3914
|
+
...createObjectLikeInitialError(context, noMatchContext)
|
|
3817
3915
|
};
|
|
3818
3916
|
};
|
|
3819
3917
|
const parseAsync = async (context) => {
|
|
@@ -3858,8 +3956,7 @@ function merge(...args) {
|
|
|
3858
3956
|
};
|
|
3859
3957
|
return {
|
|
3860
3958
|
success: false,
|
|
3861
|
-
|
|
3862
|
-
error: require_message.message`No matching option or argument found.`
|
|
3959
|
+
...createObjectLikeInitialError(context, noMatchContext)
|
|
3863
3960
|
};
|
|
3864
3961
|
};
|
|
3865
3962
|
const mergeParser = {
|
package/dist/constructs.js
CHANGED
|
@@ -581,6 +581,35 @@ function createExclusiveState(parentState, index, parser, result) {
|
|
|
581
581
|
return annotateFreshArray(parentState, [index, annotatedResult]);
|
|
582
582
|
}
|
|
583
583
|
/**
|
|
584
|
+
* Detects a branch result that handled only the global options terminator.
|
|
585
|
+
* Such a result advances the shared parser context, but does not identify
|
|
586
|
+
* which exclusive branch matched.
|
|
587
|
+
* @internal
|
|
588
|
+
*/
|
|
589
|
+
function isOptionsTerminatorOnlyResult(context, result) {
|
|
590
|
+
return !context.optionsTerminated && context.buffer[0] === "--" && result.success && result.next.optionsTerminated && result.consumed.length === 1 && result.consumed[0] === "--";
|
|
591
|
+
}
|
|
592
|
+
function isUnselectedBooleanOptionState(state) {
|
|
593
|
+
return state != null && typeof state === "object" && "success" in state && state.success === true && "value" in state && state.value === false;
|
|
594
|
+
}
|
|
595
|
+
function preserveExclusiveStateAfterOptionsTerminator(context, result) {
|
|
596
|
+
const mergedExec = mergeChildExec(context.exec, result.next.exec);
|
|
597
|
+
return {
|
|
598
|
+
success: true,
|
|
599
|
+
next: {
|
|
600
|
+
...context,
|
|
601
|
+
buffer: result.next.buffer,
|
|
602
|
+
optionsTerminated: true,
|
|
603
|
+
state: context.state,
|
|
604
|
+
...mergedExec != null ? {
|
|
605
|
+
exec: mergedExec,
|
|
606
|
+
dependencyRegistry: mergedExec.dependencyRegistry
|
|
607
|
+
} : {}
|
|
608
|
+
},
|
|
609
|
+
consumed: result.consumed
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
584
613
|
* Creates a complete() method shared by or() and longestMatch().
|
|
585
614
|
* @internal
|
|
586
615
|
*/
|
|
@@ -818,9 +847,15 @@ function or(...args) {
|
|
|
818
847
|
let zeroConsumedBranch = null;
|
|
819
848
|
let zeroConsumedCount = 0;
|
|
820
849
|
let provisionalConsuming = null;
|
|
850
|
+
let optionsTerminatorResult = null;
|
|
821
851
|
let provisionalAmbiguous = false;
|
|
822
852
|
for (const [parser, i] of orderedParsers) {
|
|
823
853
|
const result = parser.parse(withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
|
|
854
|
+
if (isOptionsTerminatorOnlyResult(context, result)) {
|
|
855
|
+
if (activeState?.[0] === i && activeState[1].success && activeState[1].consumed.length > 0 && result.next.buffer.length < 1) return preserveExclusiveStateAfterOptionsTerminator(context, result);
|
|
856
|
+
optionsTerminatorResult ??= result;
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
824
859
|
if (result.success && result.consumed.length > 0) {
|
|
825
860
|
if (result.provisional) {
|
|
826
861
|
const activeBranchLocked = activeState != null && activeState[1].success && activeState[1].consumed.length > 0;
|
|
@@ -1001,6 +1036,7 @@ function or(...args) {
|
|
|
1001
1036
|
}
|
|
1002
1037
|
}
|
|
1003
1038
|
}
|
|
1039
|
+
if (optionsTerminatorResult != null) return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorResult);
|
|
1004
1040
|
return {
|
|
1005
1041
|
...error,
|
|
1006
1042
|
success: false
|
|
@@ -1014,10 +1050,16 @@ function or(...args) {
|
|
|
1014
1050
|
let zeroConsumedBranch = null;
|
|
1015
1051
|
let zeroConsumedCount = 0;
|
|
1016
1052
|
let provisionalConsuming = null;
|
|
1053
|
+
let optionsTerminatorResult = null;
|
|
1017
1054
|
let provisionalAmbiguous = false;
|
|
1018
1055
|
for (const [parser, i] of orderedParsers) {
|
|
1019
1056
|
const resultOrPromise = parser.parse(withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
|
|
1020
1057
|
const result = await resultOrPromise;
|
|
1058
|
+
if (isOptionsTerminatorOnlyResult(context, result)) {
|
|
1059
|
+
if (activeState?.[0] === i && activeState[1].success && activeState[1].consumed.length > 0 && result.next.buffer.length < 1) return preserveExclusiveStateAfterOptionsTerminator(context, result);
|
|
1060
|
+
optionsTerminatorResult ??= result;
|
|
1061
|
+
continue;
|
|
1062
|
+
}
|
|
1021
1063
|
if (result.success && result.consumed.length > 0) {
|
|
1022
1064
|
if (result.provisional) {
|
|
1023
1065
|
const activeBranchLocked = activeState != null && activeState[1].success && activeState[1].consumed.length > 0;
|
|
@@ -1200,6 +1242,7 @@ function or(...args) {
|
|
|
1200
1242
|
}
|
|
1201
1243
|
}
|
|
1202
1244
|
}
|
|
1245
|
+
if (optionsTerminatorResult != null) return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorResult);
|
|
1203
1246
|
return {
|
|
1204
1247
|
...error,
|
|
1205
1248
|
success: false
|
|
@@ -1301,82 +1344,122 @@ function createLongestMatch(...args) {
|
|
|
1301
1344
|
return createUnexpectedInputErrorWithScopedSuggestions(defaultMsg, token, parsers, options?.errors?.suggestions);
|
|
1302
1345
|
})()
|
|
1303
1346
|
});
|
|
1304
|
-
const
|
|
1347
|
+
const commitLongestMatch = (context, match, optionsTerminatorResult) => {
|
|
1348
|
+
const selectedResult = optionsTerminatorResult == null ? match.result : {
|
|
1349
|
+
...match.result,
|
|
1350
|
+
next: {
|
|
1351
|
+
...match.result.next,
|
|
1352
|
+
buffer: optionsTerminatorResult.next.buffer,
|
|
1353
|
+
optionsTerminated: true
|
|
1354
|
+
},
|
|
1355
|
+
consumed: [...optionsTerminatorResult.consumed, ...match.result.consumed]
|
|
1356
|
+
};
|
|
1357
|
+
const parser = parsers[match.index];
|
|
1358
|
+
const mergedExec = mergeChildExec(context.exec, selectedResult.next.exec);
|
|
1359
|
+
return {
|
|
1360
|
+
success: true,
|
|
1361
|
+
next: {
|
|
1362
|
+
...context,
|
|
1363
|
+
buffer: selectedResult.next.buffer,
|
|
1364
|
+
optionsTerminated: selectedResult.next.optionsTerminated,
|
|
1365
|
+
state: createExclusiveState(context.state, match.index, parser, selectedResult),
|
|
1366
|
+
...mergedExec != null ? {
|
|
1367
|
+
exec: mergedExec,
|
|
1368
|
+
dependencyRegistry: mergedExec.dependencyRegistry
|
|
1369
|
+
} : {}
|
|
1370
|
+
},
|
|
1371
|
+
consumed: selectedResult.consumed
|
|
1372
|
+
};
|
|
1373
|
+
};
|
|
1374
|
+
const resolveLongestMatchOptionsTerminator = (context, bestMatch, optionsTerminatorMatches) => {
|
|
1375
|
+
if (optionsTerminatorMatches.length < 1) return null;
|
|
1376
|
+
if (bestMatch != null && bestMatch.consumed > 0) return null;
|
|
1377
|
+
const activeState = normalizeExclusiveState(context.state);
|
|
1378
|
+
if (activeState != null && activeState[1].success && activeState[1].consumed.length > 0) return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorMatches[0].result);
|
|
1379
|
+
let selected = bestMatch == null ? null : {
|
|
1380
|
+
match: bestMatch,
|
|
1381
|
+
fromTerminator: false
|
|
1382
|
+
};
|
|
1383
|
+
for (const match of optionsTerminatorMatches) {
|
|
1384
|
+
if (!match.selectedAfterTerminator && (extractRequiredUsage(parsers[match.index].usage).length > 0 || isUnselectedBooleanOptionState(match.result.next.state))) continue;
|
|
1385
|
+
const selectedIsProvisional = selected != null && !!selected.match.result.provisional;
|
|
1386
|
+
const matchIsProvisional = !!match.result.provisional;
|
|
1387
|
+
if (selected == null || selectedIsProvisional && !matchIsProvisional || selectedIsProvisional === matchIsProvisional && match.index < selected.match.index) selected = {
|
|
1388
|
+
match,
|
|
1389
|
+
fromTerminator: true
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
if (selected != null) return commitLongestMatch(context, selected.match, selected.fromTerminator ? void 0 : optionsTerminatorMatches[0].result);
|
|
1393
|
+
return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorMatches[0].result);
|
|
1394
|
+
};
|
|
1395
|
+
const parseSyncBranches = (context) => {
|
|
1305
1396
|
let bestMatch = null;
|
|
1397
|
+
const optionsTerminatorMatches = [];
|
|
1306
1398
|
let error = getInitialError(context);
|
|
1307
1399
|
const activeState = normalizeExclusiveState(context.state);
|
|
1308
1400
|
for (let i = 0; i < syncParsers.length; i++) {
|
|
1309
1401
|
const parser = syncParsers[i];
|
|
1310
|
-
const
|
|
1402
|
+
const childContext = withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser);
|
|
1403
|
+
const result = parser.parse(childContext);
|
|
1404
|
+
if (isOptionsTerminatorOnlyResult(context, result)) {
|
|
1405
|
+
optionsTerminatorMatches.push({
|
|
1406
|
+
index: i,
|
|
1407
|
+
result,
|
|
1408
|
+
consumed: 1,
|
|
1409
|
+
...normalizeExclusiveState(result.next.state) != null ? { selectedAfterTerminator: true } : {}
|
|
1410
|
+
});
|
|
1411
|
+
continue;
|
|
1412
|
+
}
|
|
1311
1413
|
if (result.success) {
|
|
1312
1414
|
const consumed = context.buffer.length - result.next.buffer.length;
|
|
1313
1415
|
const bestIsProvisional = bestMatch != null && bestMatch.result.success && !!bestMatch.result.provisional;
|
|
1314
1416
|
if (bestMatch === null || consumed > bestMatch.consumed || consumed === bestMatch.consumed && bestIsProvisional && !result.provisional) bestMatch = {
|
|
1315
1417
|
index: i,
|
|
1316
|
-
parser,
|
|
1317
1418
|
result,
|
|
1318
1419
|
consumed
|
|
1319
1420
|
};
|
|
1320
1421
|
} else if (error.consumed < result.consumed) error = result;
|
|
1321
1422
|
}
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
success: true,
|
|
1326
|
-
next: {
|
|
1327
|
-
...context,
|
|
1328
|
-
buffer: bestMatch.result.next.buffer,
|
|
1329
|
-
optionsTerminated: bestMatch.result.next.optionsTerminated,
|
|
1330
|
-
state: createExclusiveState(context.state, bestMatch.index, bestMatch.parser, bestMatch.result),
|
|
1331
|
-
...mergedExec != null ? {
|
|
1332
|
-
exec: mergedExec,
|
|
1333
|
-
dependencyRegistry: mergedExec.dependencyRegistry
|
|
1334
|
-
} : {}
|
|
1335
|
-
},
|
|
1336
|
-
consumed: bestMatch.result.consumed
|
|
1337
|
-
};
|
|
1338
|
-
}
|
|
1423
|
+
const optionsTerminatorResolution = resolveLongestMatchOptionsTerminator(context, bestMatch, optionsTerminatorMatches);
|
|
1424
|
+
if (optionsTerminatorResolution != null) return optionsTerminatorResolution;
|
|
1425
|
+
if (bestMatch != null) return commitLongestMatch(context, bestMatch);
|
|
1339
1426
|
return {
|
|
1340
1427
|
...error,
|
|
1341
1428
|
success: false
|
|
1342
1429
|
};
|
|
1343
1430
|
};
|
|
1344
|
-
const
|
|
1431
|
+
const parseAsyncBranches = async (context) => {
|
|
1345
1432
|
let bestMatch = null;
|
|
1433
|
+
const optionsTerminatorMatches = [];
|
|
1346
1434
|
let error = getInitialError(context);
|
|
1347
1435
|
const activeState = normalizeExclusiveState(context.state);
|
|
1348
1436
|
for (let i = 0; i < parsers.length; i++) {
|
|
1349
1437
|
const parser = parsers[i];
|
|
1350
|
-
const
|
|
1438
|
+
const childContext = withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser);
|
|
1439
|
+
const resultOrPromise = parser.parse(childContext);
|
|
1351
1440
|
const result = await resultOrPromise;
|
|
1441
|
+
if (isOptionsTerminatorOnlyResult(context, result)) {
|
|
1442
|
+
optionsTerminatorMatches.push({
|
|
1443
|
+
index: i,
|
|
1444
|
+
result,
|
|
1445
|
+
consumed: 1,
|
|
1446
|
+
...normalizeExclusiveState(result.next.state) != null ? { selectedAfterTerminator: true } : {}
|
|
1447
|
+
});
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1352
1450
|
if (result.success) {
|
|
1353
1451
|
const consumed = context.buffer.length - result.next.buffer.length;
|
|
1354
1452
|
const bestIsProvisional = bestMatch != null && bestMatch.result.success && !!bestMatch.result.provisional;
|
|
1355
1453
|
if (bestMatch === null || consumed > bestMatch.consumed || consumed === bestMatch.consumed && bestIsProvisional && !result.provisional) bestMatch = {
|
|
1356
1454
|
index: i,
|
|
1357
|
-
parser,
|
|
1358
1455
|
result,
|
|
1359
1456
|
consumed
|
|
1360
1457
|
};
|
|
1361
1458
|
} else if (error.consumed < result.consumed) error = result;
|
|
1362
1459
|
}
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
success: true,
|
|
1367
|
-
next: {
|
|
1368
|
-
...context,
|
|
1369
|
-
buffer: bestMatch.result.next.buffer,
|
|
1370
|
-
optionsTerminated: bestMatch.result.next.optionsTerminated,
|
|
1371
|
-
state: createExclusiveState(context.state, bestMatch.index, bestMatch.parser, bestMatch.result),
|
|
1372
|
-
...mergedExec != null ? {
|
|
1373
|
-
exec: mergedExec,
|
|
1374
|
-
dependencyRegistry: mergedExec.dependencyRegistry
|
|
1375
|
-
} : {}
|
|
1376
|
-
},
|
|
1377
|
-
consumed: bestMatch.result.consumed
|
|
1378
|
-
};
|
|
1379
|
-
}
|
|
1460
|
+
const optionsTerminatorResolution = resolveLongestMatchOptionsTerminator(context, bestMatch, optionsTerminatorMatches);
|
|
1461
|
+
if (optionsTerminatorResolution != null) return optionsTerminatorResolution;
|
|
1462
|
+
if (bestMatch != null) return commitLongestMatch(context, bestMatch);
|
|
1380
1463
|
return {
|
|
1381
1464
|
...error,
|
|
1382
1465
|
success: false
|
|
@@ -1399,7 +1482,7 @@ function createLongestMatch(...args) {
|
|
|
1399
1482
|
return extractExclusivePhase2Seed(parsers, state, exec, combinedMode);
|
|
1400
1483
|
},
|
|
1401
1484
|
parse(context) {
|
|
1402
|
-
return dispatchByMode(combinedMode, () =>
|
|
1485
|
+
return dispatchByMode(combinedMode, () => parseSyncBranches(context), () => parseAsyncBranches(context));
|
|
1403
1486
|
},
|
|
1404
1487
|
getSuggestRuntimeNodes(state, path) {
|
|
1405
1488
|
return getExclusiveSuggestRuntimeNodes(parsers, state, path);
|
|
@@ -1445,6 +1528,33 @@ function createLongestMatch(...args) {
|
|
|
1445
1528
|
return multiResult;
|
|
1446
1529
|
}
|
|
1447
1530
|
/**
|
|
1531
|
+
* Creates the initial parse error shared by object-like combinators.
|
|
1532
|
+
* @param context The current parser context.
|
|
1533
|
+
* @param noMatchContext The kinds of input accepted by the combinator.
|
|
1534
|
+
* @param errors Optional custom error formatters.
|
|
1535
|
+
* @returns A zero-consumption parse error.
|
|
1536
|
+
*/
|
|
1537
|
+
function createObjectLikeInitialError(context, noMatchContext, errors) {
|
|
1538
|
+
if (context.buffer.length < 1) {
|
|
1539
|
+
const customEndOfInput = errors?.endOfInput;
|
|
1540
|
+
return {
|
|
1541
|
+
consumed: 0,
|
|
1542
|
+
error: customEndOfInput ? typeof customEndOfInput === "function" ? customEndOfInput(noMatchContext) : customEndOfInput : generateNoMatchError(noMatchContext)
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
const token = context.buffer[0];
|
|
1546
|
+
const customMessage = errors?.unexpectedInput;
|
|
1547
|
+
if (customMessage) return {
|
|
1548
|
+
consumed: 0,
|
|
1549
|
+
error: typeof customMessage === "function" ? customMessage(token) : customMessage
|
|
1550
|
+
};
|
|
1551
|
+
const baseError = message`Unexpected option or argument: ${token}.`;
|
|
1552
|
+
return {
|
|
1553
|
+
consumed: 0,
|
|
1554
|
+
error: createErrorWithSuggestions(baseError, token, context.usage, "both", errors?.suggestions)
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1557
|
+
/**
|
|
1448
1558
|
* Internal sync helper for object suggest functionality.
|
|
1449
1559
|
* @internal
|
|
1450
1560
|
*/
|
|
@@ -1837,19 +1947,7 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
|
|
|
1837
1947
|
checkDuplicateReachableLeadingCommandNames(parserPairs.map(([field, parser]) => [field, parser]));
|
|
1838
1948
|
const noMatchContext = analyzeNoMatchContext(parserKeys.map((k) => parsers[k]));
|
|
1839
1949
|
const combinedMode = parserKeys.some((k) => parsers[k].mode === "async") ? "async" : "sync";
|
|
1840
|
-
const getInitialError = (context) => (
|
|
1841
|
-
consumed: 0,
|
|
1842
|
-
error: context.buffer.length > 0 ? (() => {
|
|
1843
|
-
const token = context.buffer[0];
|
|
1844
|
-
const customMessage = options.errors?.unexpectedInput;
|
|
1845
|
-
if (customMessage) return typeof customMessage === "function" ? customMessage(token) : customMessage;
|
|
1846
|
-
const baseError = message`Unexpected option or argument: ${token}.`;
|
|
1847
|
-
return createErrorWithSuggestions(baseError, token, context.usage, "both", options.errors?.suggestions);
|
|
1848
|
-
})() : (() => {
|
|
1849
|
-
const customEndOfInput = options.errors?.endOfInput;
|
|
1850
|
-
return customEndOfInput ? typeof customEndOfInput === "function" ? customEndOfInput(noMatchContext) : customEndOfInput : generateNoMatchError(noMatchContext);
|
|
1851
|
-
})()
|
|
1852
|
-
});
|
|
1950
|
+
const getInitialError = (context) => createObjectLikeInitialError(context, noMatchContext, options.errors);
|
|
1853
1951
|
const parseSync = (context) => {
|
|
1854
1952
|
let error = getInitialError(context);
|
|
1855
1953
|
let currentContext = context;
|
|
@@ -3719,6 +3817,7 @@ function merge(...args) {
|
|
|
3719
3817
|
const syncParsers = syncSorted.map(([p]) => p);
|
|
3720
3818
|
if (!options.allowDuplicates) checkDuplicateOptionNames(sorted.map(([parser, originalIndex]) => [String(originalIndex), parser.usage]));
|
|
3721
3819
|
checkDuplicateReachableLeadingCommandNames(sorted.map(([parser, originalIndex]) => [String(originalIndex), parser]));
|
|
3820
|
+
const noMatchContext = analyzeNoMatchContext(rawParsers);
|
|
3722
3821
|
const mergedFieldParsers = collectChildFieldParsers(parsers);
|
|
3723
3822
|
const duplicateOutputFieldNames = collectDuplicateFieldNames(mergedFieldParsers);
|
|
3724
3823
|
const parserStateKey = (index) => `__parser_${index}`;
|
|
@@ -3812,8 +3911,7 @@ function merge(...args) {
|
|
|
3812
3911
|
};
|
|
3813
3912
|
return {
|
|
3814
3913
|
success: false,
|
|
3815
|
-
|
|
3816
|
-
error: message`No matching option or argument found.`
|
|
3914
|
+
...createObjectLikeInitialError(context, noMatchContext)
|
|
3817
3915
|
};
|
|
3818
3916
|
};
|
|
3819
3917
|
const parseAsync = async (context) => {
|
|
@@ -3858,8 +3956,7 @@ function merge(...args) {
|
|
|
3858
3956
|
};
|
|
3859
3957
|
return {
|
|
3860
3958
|
success: false,
|
|
3861
|
-
|
|
3862
|
-
error: message`No matching option or argument found.`
|
|
3959
|
+
...createObjectLikeInitialError(context, noMatchContext)
|
|
3863
3960
|
};
|
|
3864
3961
|
};
|
|
3865
3962
|
const mergeParser = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optique/core",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "Type-safe combinatorial command-line interface parser",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"CLI",
|
|
@@ -200,7 +200,7 @@
|
|
|
200
200
|
"fast-check": "^4.7.0",
|
|
201
201
|
"tsdown": "^0.13.0",
|
|
202
202
|
"typescript": "^5.8.3",
|
|
203
|
-
"@optique/env": "1.1.
|
|
203
|
+
"@optique/env": "1.1.4"
|
|
204
204
|
},
|
|
205
205
|
"scripts": {
|
|
206
206
|
"build": "tsdown",
|