@optique/core 1.3.0-dev.2352 → 1.3.0-dev.2357

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.
@@ -582,6 +582,35 @@ function createExclusiveState(parentState, index, parser, result) {
582
582
  return require_internal_annotations.annotateFreshArray(parentState, [index, annotatedResult]);
583
583
  }
584
584
  /**
585
+ * Detects a branch result that handled only the global options terminator.
586
+ * Such a result advances the shared parser context, but does not identify
587
+ * which exclusive branch matched.
588
+ * @internal
589
+ */
590
+ function isOptionsTerminatorOnlyResult(context, result) {
591
+ return !context.optionsTerminated && context.buffer[0] === "--" && result.success && result.next.optionsTerminated && result.consumed.length === 1 && result.consumed[0] === "--";
592
+ }
593
+ function isUnselectedBooleanOptionState(state) {
594
+ return state != null && typeof state === "object" && "success" in state && state.success === true && "value" in state && state.value === false;
595
+ }
596
+ function preserveExclusiveStateAfterOptionsTerminator(context, result) {
597
+ const mergedExec = require_execution_context.mergeChildExec(context.exec, result.next.exec);
598
+ return {
599
+ success: true,
600
+ next: {
601
+ ...context,
602
+ buffer: result.next.buffer,
603
+ optionsTerminated: true,
604
+ state: context.state,
605
+ ...mergedExec != null ? {
606
+ exec: mergedExec,
607
+ dependencyRegistry: mergedExec.dependencyRegistry
608
+ } : {}
609
+ },
610
+ consumed: result.consumed
611
+ };
612
+ }
613
+ /**
585
614
  * Creates a complete() method shared by or() and longestMatch().
586
615
  * @internal
587
616
  */
@@ -819,9 +848,15 @@ function or(...args) {
819
848
  let zeroConsumedBranch = null;
820
849
  let zeroConsumedCount = 0;
821
850
  let provisionalConsuming = null;
851
+ let optionsTerminatorResult = null;
822
852
  let provisionalAmbiguous = false;
823
853
  for (const [parser, i] of orderedParsers) {
824
854
  const result = parser.parse(withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
855
+ if (isOptionsTerminatorOnlyResult(context, result)) {
856
+ if (activeState?.[0] === i && activeState[1].success && activeState[1].consumed.length > 0 && result.next.buffer.length < 1) return preserveExclusiveStateAfterOptionsTerminator(context, result);
857
+ optionsTerminatorResult ??= result;
858
+ continue;
859
+ }
825
860
  if (result.success && result.consumed.length > 0) {
826
861
  if (result.provisional) {
827
862
  const activeBranchLocked = activeState != null && activeState[1].success && activeState[1].consumed.length > 0;
@@ -1002,6 +1037,7 @@ function or(...args) {
1002
1037
  }
1003
1038
  }
1004
1039
  }
1040
+ if (optionsTerminatorResult != null) return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorResult);
1005
1041
  return {
1006
1042
  ...error,
1007
1043
  success: false
@@ -1015,10 +1051,16 @@ function or(...args) {
1015
1051
  let zeroConsumedBranch = null;
1016
1052
  let zeroConsumedCount = 0;
1017
1053
  let provisionalConsuming = null;
1054
+ let optionsTerminatorResult = null;
1018
1055
  let provisionalAmbiguous = false;
1019
1056
  for (const [parser, i] of orderedParsers) {
1020
1057
  const resultOrPromise = parser.parse(withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
1021
1058
  const result = await resultOrPromise;
1059
+ if (isOptionsTerminatorOnlyResult(context, result)) {
1060
+ if (activeState?.[0] === i && activeState[1].success && activeState[1].consumed.length > 0 && result.next.buffer.length < 1) return preserveExclusiveStateAfterOptionsTerminator(context, result);
1061
+ optionsTerminatorResult ??= result;
1062
+ continue;
1063
+ }
1022
1064
  if (result.success && result.consumed.length > 0) {
1023
1065
  if (result.provisional) {
1024
1066
  const activeBranchLocked = activeState != null && activeState[1].success && activeState[1].consumed.length > 0;
@@ -1201,6 +1243,7 @@ function or(...args) {
1201
1243
  }
1202
1244
  }
1203
1245
  }
1246
+ if (optionsTerminatorResult != null) return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorResult);
1204
1247
  return {
1205
1248
  ...error,
1206
1249
  success: false
@@ -1302,82 +1345,122 @@ function createLongestMatch(...args) {
1302
1345
  return createUnexpectedInputErrorWithScopedSuggestions(defaultMsg, token, parsers, options?.errors?.suggestions);
1303
1346
  })()
1304
1347
  });
1305
- const parseSync = (context) => {
1348
+ const commitLongestMatch = (context, match, optionsTerminatorResult) => {
1349
+ const selectedResult = optionsTerminatorResult == null ? match.result : {
1350
+ ...match.result,
1351
+ next: {
1352
+ ...match.result.next,
1353
+ buffer: optionsTerminatorResult.next.buffer,
1354
+ optionsTerminated: true
1355
+ },
1356
+ consumed: [...optionsTerminatorResult.consumed, ...match.result.consumed]
1357
+ };
1358
+ const parser = parsers[match.index];
1359
+ const mergedExec = require_execution_context.mergeChildExec(context.exec, selectedResult.next.exec);
1360
+ return {
1361
+ success: true,
1362
+ next: {
1363
+ ...context,
1364
+ buffer: selectedResult.next.buffer,
1365
+ optionsTerminated: selectedResult.next.optionsTerminated,
1366
+ state: createExclusiveState(context.state, match.index, parser, selectedResult),
1367
+ ...mergedExec != null ? {
1368
+ exec: mergedExec,
1369
+ dependencyRegistry: mergedExec.dependencyRegistry
1370
+ } : {}
1371
+ },
1372
+ consumed: selectedResult.consumed
1373
+ };
1374
+ };
1375
+ const resolveLongestMatchOptionsTerminator = (context, bestMatch, optionsTerminatorMatches) => {
1376
+ if (optionsTerminatorMatches.length < 1) return null;
1377
+ if (bestMatch != null && bestMatch.consumed > 0) return null;
1378
+ const activeState = normalizeExclusiveState(context.state);
1379
+ if (activeState != null && activeState[1].success && activeState[1].consumed.length > 0) return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorMatches[0].result);
1380
+ let selected = bestMatch == null ? null : {
1381
+ match: bestMatch,
1382
+ fromTerminator: false
1383
+ };
1384
+ for (const match of optionsTerminatorMatches) {
1385
+ if (!match.selectedAfterTerminator && (extractRequiredUsage(parsers[match.index].usage).length > 0 || isUnselectedBooleanOptionState(match.result.next.state))) continue;
1386
+ const selectedIsProvisional = selected != null && !!selected.match.result.provisional;
1387
+ const matchIsProvisional = !!match.result.provisional;
1388
+ if (selected == null || selectedIsProvisional && !matchIsProvisional || selectedIsProvisional === matchIsProvisional && match.index < selected.match.index) selected = {
1389
+ match,
1390
+ fromTerminator: true
1391
+ };
1392
+ }
1393
+ if (selected != null) return commitLongestMatch(context, selected.match, selected.fromTerminator ? void 0 : optionsTerminatorMatches[0].result);
1394
+ return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorMatches[0].result);
1395
+ };
1396
+ const parseSyncBranches = (context) => {
1306
1397
  let bestMatch = null;
1398
+ const optionsTerminatorMatches = [];
1307
1399
  let error = getInitialError(context);
1308
1400
  const activeState = normalizeExclusiveState(context.state);
1309
1401
  for (let i = 0; i < syncParsers.length; i++) {
1310
1402
  const parser = syncParsers[i];
1311
- const result = parser.parse(withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
1403
+ const childContext = withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser);
1404
+ const result = parser.parse(childContext);
1405
+ if (isOptionsTerminatorOnlyResult(context, result)) {
1406
+ optionsTerminatorMatches.push({
1407
+ index: i,
1408
+ result,
1409
+ consumed: 1,
1410
+ ...normalizeExclusiveState(result.next.state) != null ? { selectedAfterTerminator: true } : {}
1411
+ });
1412
+ continue;
1413
+ }
1312
1414
  if (result.success) {
1313
1415
  const consumed = context.buffer.length - result.next.buffer.length;
1314
1416
  const bestIsProvisional = bestMatch != null && bestMatch.result.success && !!bestMatch.result.provisional;
1315
1417
  if (bestMatch === null || consumed > bestMatch.consumed || consumed === bestMatch.consumed && bestIsProvisional && !result.provisional) bestMatch = {
1316
1418
  index: i,
1317
- parser,
1318
1419
  result,
1319
1420
  consumed
1320
1421
  };
1321
1422
  } else if (error.consumed < result.consumed) error = result;
1322
1423
  }
1323
- if (bestMatch && bestMatch.result.success) {
1324
- const mergedExec = require_execution_context.mergeChildExec(context.exec, bestMatch.result.next.exec);
1325
- return {
1326
- success: true,
1327
- next: {
1328
- ...context,
1329
- buffer: bestMatch.result.next.buffer,
1330
- optionsTerminated: bestMatch.result.next.optionsTerminated,
1331
- state: createExclusiveState(context.state, bestMatch.index, bestMatch.parser, bestMatch.result),
1332
- ...mergedExec != null ? {
1333
- exec: mergedExec,
1334
- dependencyRegistry: mergedExec.dependencyRegistry
1335
- } : {}
1336
- },
1337
- consumed: bestMatch.result.consumed
1338
- };
1339
- }
1424
+ const optionsTerminatorResolution = resolveLongestMatchOptionsTerminator(context, bestMatch, optionsTerminatorMatches);
1425
+ if (optionsTerminatorResolution != null) return optionsTerminatorResolution;
1426
+ if (bestMatch != null) return commitLongestMatch(context, bestMatch);
1340
1427
  return {
1341
1428
  ...error,
1342
1429
  success: false
1343
1430
  };
1344
1431
  };
1345
- const parseAsync = async (context) => {
1432
+ const parseAsyncBranches = async (context) => {
1346
1433
  let bestMatch = null;
1434
+ const optionsTerminatorMatches = [];
1347
1435
  let error = getInitialError(context);
1348
1436
  const activeState = normalizeExclusiveState(context.state);
1349
1437
  for (let i = 0; i < parsers.length; i++) {
1350
1438
  const parser = parsers[i];
1351
- const resultOrPromise = parser.parse(withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
1439
+ const childContext = withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser);
1440
+ const resultOrPromise = parser.parse(childContext);
1352
1441
  const result = await resultOrPromise;
1442
+ if (isOptionsTerminatorOnlyResult(context, result)) {
1443
+ optionsTerminatorMatches.push({
1444
+ index: i,
1445
+ result,
1446
+ consumed: 1,
1447
+ ...normalizeExclusiveState(result.next.state) != null ? { selectedAfterTerminator: true } : {}
1448
+ });
1449
+ continue;
1450
+ }
1353
1451
  if (result.success) {
1354
1452
  const consumed = context.buffer.length - result.next.buffer.length;
1355
1453
  const bestIsProvisional = bestMatch != null && bestMatch.result.success && !!bestMatch.result.provisional;
1356
1454
  if (bestMatch === null || consumed > bestMatch.consumed || consumed === bestMatch.consumed && bestIsProvisional && !result.provisional) bestMatch = {
1357
1455
  index: i,
1358
- parser,
1359
1456
  result,
1360
1457
  consumed
1361
1458
  };
1362
1459
  } else if (error.consumed < result.consumed) error = result;
1363
1460
  }
1364
- if (bestMatch && bestMatch.result.success) {
1365
- const mergedExec = require_execution_context.mergeChildExec(context.exec, bestMatch.result.next.exec);
1366
- return {
1367
- success: true,
1368
- next: {
1369
- ...context,
1370
- buffer: bestMatch.result.next.buffer,
1371
- optionsTerminated: bestMatch.result.next.optionsTerminated,
1372
- state: createExclusiveState(context.state, bestMatch.index, bestMatch.parser, bestMatch.result),
1373
- ...mergedExec != null ? {
1374
- exec: mergedExec,
1375
- dependencyRegistry: mergedExec.dependencyRegistry
1376
- } : {}
1377
- },
1378
- consumed: bestMatch.result.consumed
1379
- };
1380
- }
1461
+ const optionsTerminatorResolution = resolveLongestMatchOptionsTerminator(context, bestMatch, optionsTerminatorMatches);
1462
+ if (optionsTerminatorResolution != null) return optionsTerminatorResolution;
1463
+ if (bestMatch != null) return commitLongestMatch(context, bestMatch);
1381
1464
  return {
1382
1465
  ...error,
1383
1466
  success: false
@@ -1400,7 +1483,7 @@ function createLongestMatch(...args) {
1400
1483
  return extractExclusivePhase2Seed(parsers, state, exec, combinedMode);
1401
1484
  },
1402
1485
  parse(context) {
1403
- return require_mode_dispatch.dispatchByMode(combinedMode, () => parseSync(context), () => parseAsync(context));
1486
+ return require_mode_dispatch.dispatchByMode(combinedMode, () => parseSyncBranches(context), () => parseAsyncBranches(context));
1404
1487
  },
1405
1488
  getSuggestRuntimeNodes(state, path) {
1406
1489
  return getExclusiveSuggestRuntimeNodes(parsers, state, path);
@@ -582,6 +582,35 @@ function createExclusiveState(parentState, index, parser, result) {
582
582
  return annotateFreshArray(parentState, [index, annotatedResult]);
583
583
  }
584
584
  /**
585
+ * Detects a branch result that handled only the global options terminator.
586
+ * Such a result advances the shared parser context, but does not identify
587
+ * which exclusive branch matched.
588
+ * @internal
589
+ */
590
+ function isOptionsTerminatorOnlyResult(context, result) {
591
+ return !context.optionsTerminated && context.buffer[0] === "--" && result.success && result.next.optionsTerminated && result.consumed.length === 1 && result.consumed[0] === "--";
592
+ }
593
+ function isUnselectedBooleanOptionState(state) {
594
+ return state != null && typeof state === "object" && "success" in state && state.success === true && "value" in state && state.value === false;
595
+ }
596
+ function preserveExclusiveStateAfterOptionsTerminator(context, result) {
597
+ const mergedExec = mergeChildExec(context.exec, result.next.exec);
598
+ return {
599
+ success: true,
600
+ next: {
601
+ ...context,
602
+ buffer: result.next.buffer,
603
+ optionsTerminated: true,
604
+ state: context.state,
605
+ ...mergedExec != null ? {
606
+ exec: mergedExec,
607
+ dependencyRegistry: mergedExec.dependencyRegistry
608
+ } : {}
609
+ },
610
+ consumed: result.consumed
611
+ };
612
+ }
613
+ /**
585
614
  * Creates a complete() method shared by or() and longestMatch().
586
615
  * @internal
587
616
  */
@@ -819,9 +848,15 @@ function or(...args) {
819
848
  let zeroConsumedBranch = null;
820
849
  let zeroConsumedCount = 0;
821
850
  let provisionalConsuming = null;
851
+ let optionsTerminatorResult = null;
822
852
  let provisionalAmbiguous = false;
823
853
  for (const [parser, i] of orderedParsers) {
824
854
  const result = parser.parse(withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
855
+ if (isOptionsTerminatorOnlyResult(context, result)) {
856
+ if (activeState?.[0] === i && activeState[1].success && activeState[1].consumed.length > 0 && result.next.buffer.length < 1) return preserveExclusiveStateAfterOptionsTerminator(context, result);
857
+ optionsTerminatorResult ??= result;
858
+ continue;
859
+ }
825
860
  if (result.success && result.consumed.length > 0) {
826
861
  if (result.provisional) {
827
862
  const activeBranchLocked = activeState != null && activeState[1].success && activeState[1].consumed.length > 0;
@@ -1002,6 +1037,7 @@ function or(...args) {
1002
1037
  }
1003
1038
  }
1004
1039
  }
1040
+ if (optionsTerminatorResult != null) return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorResult);
1005
1041
  return {
1006
1042
  ...error,
1007
1043
  success: false
@@ -1015,10 +1051,16 @@ function or(...args) {
1015
1051
  let zeroConsumedBranch = null;
1016
1052
  let zeroConsumedCount = 0;
1017
1053
  let provisionalConsuming = null;
1054
+ let optionsTerminatorResult = null;
1018
1055
  let provisionalAmbiguous = false;
1019
1056
  for (const [parser, i] of orderedParsers) {
1020
1057
  const resultOrPromise = parser.parse(withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
1021
1058
  const result = await resultOrPromise;
1059
+ if (isOptionsTerminatorOnlyResult(context, result)) {
1060
+ if (activeState?.[0] === i && activeState[1].success && activeState[1].consumed.length > 0 && result.next.buffer.length < 1) return preserveExclusiveStateAfterOptionsTerminator(context, result);
1061
+ optionsTerminatorResult ??= result;
1062
+ continue;
1063
+ }
1022
1064
  if (result.success && result.consumed.length > 0) {
1023
1065
  if (result.provisional) {
1024
1066
  const activeBranchLocked = activeState != null && activeState[1].success && activeState[1].consumed.length > 0;
@@ -1201,6 +1243,7 @@ function or(...args) {
1201
1243
  }
1202
1244
  }
1203
1245
  }
1246
+ if (optionsTerminatorResult != null) return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorResult);
1204
1247
  return {
1205
1248
  ...error,
1206
1249
  success: false
@@ -1302,82 +1345,122 @@ function createLongestMatch(...args) {
1302
1345
  return createUnexpectedInputErrorWithScopedSuggestions(defaultMsg, token, parsers, options?.errors?.suggestions);
1303
1346
  })()
1304
1347
  });
1305
- const parseSync = (context) => {
1348
+ const commitLongestMatch = (context, match, optionsTerminatorResult) => {
1349
+ const selectedResult = optionsTerminatorResult == null ? match.result : {
1350
+ ...match.result,
1351
+ next: {
1352
+ ...match.result.next,
1353
+ buffer: optionsTerminatorResult.next.buffer,
1354
+ optionsTerminated: true
1355
+ },
1356
+ consumed: [...optionsTerminatorResult.consumed, ...match.result.consumed]
1357
+ };
1358
+ const parser = parsers[match.index];
1359
+ const mergedExec = mergeChildExec(context.exec, selectedResult.next.exec);
1360
+ return {
1361
+ success: true,
1362
+ next: {
1363
+ ...context,
1364
+ buffer: selectedResult.next.buffer,
1365
+ optionsTerminated: selectedResult.next.optionsTerminated,
1366
+ state: createExclusiveState(context.state, match.index, parser, selectedResult),
1367
+ ...mergedExec != null ? {
1368
+ exec: mergedExec,
1369
+ dependencyRegistry: mergedExec.dependencyRegistry
1370
+ } : {}
1371
+ },
1372
+ consumed: selectedResult.consumed
1373
+ };
1374
+ };
1375
+ const resolveLongestMatchOptionsTerminator = (context, bestMatch, optionsTerminatorMatches) => {
1376
+ if (optionsTerminatorMatches.length < 1) return null;
1377
+ if (bestMatch != null && bestMatch.consumed > 0) return null;
1378
+ const activeState = normalizeExclusiveState(context.state);
1379
+ if (activeState != null && activeState[1].success && activeState[1].consumed.length > 0) return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorMatches[0].result);
1380
+ let selected = bestMatch == null ? null : {
1381
+ match: bestMatch,
1382
+ fromTerminator: false
1383
+ };
1384
+ for (const match of optionsTerminatorMatches) {
1385
+ if (!match.selectedAfterTerminator && (extractRequiredUsage(parsers[match.index].usage).length > 0 || isUnselectedBooleanOptionState(match.result.next.state))) continue;
1386
+ const selectedIsProvisional = selected != null && !!selected.match.result.provisional;
1387
+ const matchIsProvisional = !!match.result.provisional;
1388
+ if (selected == null || selectedIsProvisional && !matchIsProvisional || selectedIsProvisional === matchIsProvisional && match.index < selected.match.index) selected = {
1389
+ match,
1390
+ fromTerminator: true
1391
+ };
1392
+ }
1393
+ if (selected != null) return commitLongestMatch(context, selected.match, selected.fromTerminator ? void 0 : optionsTerminatorMatches[0].result);
1394
+ return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorMatches[0].result);
1395
+ };
1396
+ const parseSyncBranches = (context) => {
1306
1397
  let bestMatch = null;
1398
+ const optionsTerminatorMatches = [];
1307
1399
  let error = getInitialError(context);
1308
1400
  const activeState = normalizeExclusiveState(context.state);
1309
1401
  for (let i = 0; i < syncParsers.length; i++) {
1310
1402
  const parser = syncParsers[i];
1311
- const result = parser.parse(withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
1403
+ const childContext = withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser);
1404
+ const result = parser.parse(childContext);
1405
+ if (isOptionsTerminatorOnlyResult(context, result)) {
1406
+ optionsTerminatorMatches.push({
1407
+ index: i,
1408
+ result,
1409
+ consumed: 1,
1410
+ ...normalizeExclusiveState(result.next.state) != null ? { selectedAfterTerminator: true } : {}
1411
+ });
1412
+ continue;
1413
+ }
1312
1414
  if (result.success) {
1313
1415
  const consumed = context.buffer.length - result.next.buffer.length;
1314
1416
  const bestIsProvisional = bestMatch != null && bestMatch.result.success && !!bestMatch.result.provisional;
1315
1417
  if (bestMatch === null || consumed > bestMatch.consumed || consumed === bestMatch.consumed && bestIsProvisional && !result.provisional) bestMatch = {
1316
1418
  index: i,
1317
- parser,
1318
1419
  result,
1319
1420
  consumed
1320
1421
  };
1321
1422
  } else if (error.consumed < result.consumed) error = result;
1322
1423
  }
1323
- if (bestMatch && bestMatch.result.success) {
1324
- const mergedExec = mergeChildExec(context.exec, bestMatch.result.next.exec);
1325
- return {
1326
- success: true,
1327
- next: {
1328
- ...context,
1329
- buffer: bestMatch.result.next.buffer,
1330
- optionsTerminated: bestMatch.result.next.optionsTerminated,
1331
- state: createExclusiveState(context.state, bestMatch.index, bestMatch.parser, bestMatch.result),
1332
- ...mergedExec != null ? {
1333
- exec: mergedExec,
1334
- dependencyRegistry: mergedExec.dependencyRegistry
1335
- } : {}
1336
- },
1337
- consumed: bestMatch.result.consumed
1338
- };
1339
- }
1424
+ const optionsTerminatorResolution = resolveLongestMatchOptionsTerminator(context, bestMatch, optionsTerminatorMatches);
1425
+ if (optionsTerminatorResolution != null) return optionsTerminatorResolution;
1426
+ if (bestMatch != null) return commitLongestMatch(context, bestMatch);
1340
1427
  return {
1341
1428
  ...error,
1342
1429
  success: false
1343
1430
  };
1344
1431
  };
1345
- const parseAsync = async (context) => {
1432
+ const parseAsyncBranches = async (context) => {
1346
1433
  let bestMatch = null;
1434
+ const optionsTerminatorMatches = [];
1347
1435
  let error = getInitialError(context);
1348
1436
  const activeState = normalizeExclusiveState(context.state);
1349
1437
  for (let i = 0; i < parsers.length; i++) {
1350
1438
  const parser = parsers[i];
1351
- const resultOrPromise = parser.parse(withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
1439
+ const childContext = withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser);
1440
+ const resultOrPromise = parser.parse(childContext);
1352
1441
  const result = await resultOrPromise;
1442
+ if (isOptionsTerminatorOnlyResult(context, result)) {
1443
+ optionsTerminatorMatches.push({
1444
+ index: i,
1445
+ result,
1446
+ consumed: 1,
1447
+ ...normalizeExclusiveState(result.next.state) != null ? { selectedAfterTerminator: true } : {}
1448
+ });
1449
+ continue;
1450
+ }
1353
1451
  if (result.success) {
1354
1452
  const consumed = context.buffer.length - result.next.buffer.length;
1355
1453
  const bestIsProvisional = bestMatch != null && bestMatch.result.success && !!bestMatch.result.provisional;
1356
1454
  if (bestMatch === null || consumed > bestMatch.consumed || consumed === bestMatch.consumed && bestIsProvisional && !result.provisional) bestMatch = {
1357
1455
  index: i,
1358
- parser,
1359
1456
  result,
1360
1457
  consumed
1361
1458
  };
1362
1459
  } else if (error.consumed < result.consumed) error = result;
1363
1460
  }
1364
- if (bestMatch && bestMatch.result.success) {
1365
- const mergedExec = mergeChildExec(context.exec, bestMatch.result.next.exec);
1366
- return {
1367
- success: true,
1368
- next: {
1369
- ...context,
1370
- buffer: bestMatch.result.next.buffer,
1371
- optionsTerminated: bestMatch.result.next.optionsTerminated,
1372
- state: createExclusiveState(context.state, bestMatch.index, bestMatch.parser, bestMatch.result),
1373
- ...mergedExec != null ? {
1374
- exec: mergedExec,
1375
- dependencyRegistry: mergedExec.dependencyRegistry
1376
- } : {}
1377
- },
1378
- consumed: bestMatch.result.consumed
1379
- };
1380
- }
1461
+ const optionsTerminatorResolution = resolveLongestMatchOptionsTerminator(context, bestMatch, optionsTerminatorMatches);
1462
+ if (optionsTerminatorResolution != null) return optionsTerminatorResolution;
1463
+ if (bestMatch != null) return commitLongestMatch(context, bestMatch);
1381
1464
  return {
1382
1465
  ...error,
1383
1466
  success: false
@@ -1400,7 +1483,7 @@ function createLongestMatch(...args) {
1400
1483
  return extractExclusivePhase2Seed(parsers, state, exec, combinedMode);
1401
1484
  },
1402
1485
  parse(context) {
1403
- return dispatchByMode(combinedMode, () => parseSync(context), () => parseAsync(context));
1486
+ return dispatchByMode(combinedMode, () => parseSyncBranches(context), () => parseAsyncBranches(context));
1404
1487
  },
1405
1488
  getSuggestRuntimeNodes(state, path) {
1406
1489
  return getExclusiveSuggestRuntimeNodes(parsers, state, path);
package/dist/facade.cjs CHANGED
@@ -952,7 +952,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
952
952
  options = optionsParam ?? {};
953
953
  }
954
954
  require_validate.validateProgramName(programName);
955
- const { colors, maxWidth, showDefault, showChoices, sectionOrder, showUsage, commandList = "recursive", aboveError = "usage", onError = () => {
955
+ const { colors, maxWidth, showDefault, showChoices, sectionOrder, showUsage, usageLine, commandList = "recursive", aboveError = "usage", onError = () => {
956
956
  throw new RunParserError("Failed to parse command line arguments.");
957
957
  }, stderr = console.error, stdout = console.log, brief, description, examples, author, bugs, footer } = options;
958
958
  const norm = (c) => c === true ? {} : c;
@@ -1045,6 +1045,41 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
1045
1045
  completionCommandGroup: completionCommandConfig?.group,
1046
1046
  completionOptionGroup: completionOptionConfig?.group
1047
1047
  });
1048
+ const helpAsCommand = helpCommandConfig != null;
1049
+ const versionAsCommand = versionCommandConfig != null;
1050
+ const completionAsCommand = completionCommandConfig != null;
1051
+ const helpAsOption = helpOptionConfig != null;
1052
+ const versionAsOption = versionOptionConfig != null;
1053
+ const completionAsOption = completionOptionConfig != null;
1054
+ let cachedRootHelpGeneratorParser;
1055
+ const getRootHelpGeneratorParser = () => {
1056
+ if (cachedRootHelpGeneratorParser != null) return cachedRootHelpGeneratorParser;
1057
+ const commandParsers = [parser];
1058
+ const groupedMeta = {};
1059
+ const ungroupedMeta = [];
1060
+ const addMeta = (metaParser, groupLabel) => {
1061
+ if (groupLabel) (groupedMeta[groupLabel] ??= []).push(metaParser);
1062
+ else ungroupedMeta.push(metaParser);
1063
+ };
1064
+ if (helpAsCommand && helpParsers.helpCommand) addMeta(helpParsers.helpCommand, helpCommandConfig?.group);
1065
+ if (versionAsCommand && versionParsers.versionCommand) addMeta(versionParsers.versionCommand, versionCommandConfig?.group);
1066
+ if (completionAsCommand && completionParsers.completionCommand) addMeta(completionParsers.completionCommand, completionCommandConfig?.group);
1067
+ commandParsers.push(...ungroupedMeta);
1068
+ for (const [label, parsers] of Object.entries(groupedMeta)) commandParsers.push(require_constructs.group(label, parsers.length === 1 ? parsers[0] : require_constructs.longestMatch(...parsers)));
1069
+ const groupedMetaOptions = {};
1070
+ const ungroupedMetaOptions = [];
1071
+ const addMetaOption = (metaParser, groupLabel) => {
1072
+ if (groupLabel) (groupedMetaOptions[groupLabel] ??= []).push(metaParser);
1073
+ else ungroupedMetaOptions.push(metaParser);
1074
+ };
1075
+ if (helpAsOption && helpParsers.helpOption) addMetaOption(helpParsers.helpOption, helpOptionConfig?.group);
1076
+ if (versionAsOption && versionParsers.versionOption) addMetaOption(versionParsers.versionOption, versionOptionConfig?.group);
1077
+ if (completionAsOption && completionParsers.completionOption) addMetaOption(completionParsers.completionOption, completionOptionConfig?.group);
1078
+ commandParsers.push(...ungroupedMetaOptions);
1079
+ for (const [label, parsers] of Object.entries(groupedMetaOptions)) commandParsers.push(require_constructs.group(label, parsers.length === 1 ? parsers[0] : require_constructs.longestMatch(...parsers)));
1080
+ cachedRootHelpGeneratorParser = commandParsers.length === 1 ? commandParsers[0] : longestMatchForMetaCommands(...commandParsers);
1081
+ return cachedRootHelpGeneratorParser;
1082
+ };
1048
1083
  const handleResult = (classified) => {
1049
1084
  switch (classified.type) {
1050
1085
  case "success": return classified.value;
@@ -1059,12 +1094,6 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
1059
1094
  case "help": {
1060
1095
  let helpGeneratorParser;
1061
1096
  let docGeneratorParser;
1062
- const helpAsCommand = helpCommandConfig != null;
1063
- const versionAsCommand = versionCommandConfig != null;
1064
- const completionAsCommand = completionCommandConfig != null;
1065
- const helpAsOption = helpOptionConfig != null;
1066
- const versionAsOption = versionOptionConfig != null;
1067
- const completionAsOption = completionOptionConfig != null;
1068
1097
  const requestedCommand = classified.commands[0];
1069
1098
  if (requestedCommand != null && !classified.preferUserCommandDocs && completionCommandNames.includes(requestedCommand) && completionAsCommand && completionParsers.completionCommand) {
1070
1099
  helpGeneratorParser = completionParsers.completionCommand;
@@ -1076,33 +1105,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
1076
1105
  helpGeneratorParser = versionParsers.versionCommand;
1077
1106
  docGeneratorParser = helpGeneratorParser;
1078
1107
  } else {
1079
- const commandParsers = [parser];
1080
- const groupedMeta = {};
1081
- const ungroupedMeta = [];
1082
- const addMeta = (p, groupLabel) => {
1083
- if (groupLabel) (groupedMeta[groupLabel] ??= []).push(p);
1084
- else ungroupedMeta.push(p);
1085
- };
1086
- if (helpAsCommand && helpParsers.helpCommand) addMeta(helpParsers.helpCommand, helpCommandConfig?.group);
1087
- if (versionAsCommand && versionParsers.versionCommand) addMeta(versionParsers.versionCommand, versionCommandConfig?.group);
1088
- if (completionAsCommand && completionParsers.completionCommand) addMeta(completionParsers.completionCommand, completionCommandConfig?.group);
1089
- commandParsers.push(...ungroupedMeta);
1090
- for (const [label, parsers] of Object.entries(groupedMeta)) if (parsers.length === 1) commandParsers.push(require_constructs.group(label, parsers[0]));
1091
- else commandParsers.push(require_constructs.group(label, require_constructs.longestMatch(...parsers)));
1092
- const groupedMetaOptions = {};
1093
- const ungroupedMetaOptions = [];
1094
- const addMetaOption = (p, groupLabel) => {
1095
- if (groupLabel) (groupedMetaOptions[groupLabel] ??= []).push(p);
1096
- else ungroupedMetaOptions.push(p);
1097
- };
1098
- if (helpAsOption && helpParsers.helpOption) addMetaOption(helpParsers.helpOption, helpOptionConfig?.group);
1099
- if (versionAsOption && versionParsers.versionOption) addMetaOption(versionParsers.versionOption, versionOptionConfig?.group);
1100
- if (completionAsOption && completionParsers.completionOption) addMetaOption(completionParsers.completionOption, completionOptionConfig?.group);
1101
- commandParsers.push(...ungroupedMetaOptions);
1102
- for (const [label, optParsers] of Object.entries(groupedMetaOptions)) if (optParsers.length === 1) commandParsers.push(require_constructs.group(label, optParsers[0]));
1103
- else commandParsers.push(require_constructs.group(label, require_constructs.longestMatch(...optParsers)));
1104
- if (commandParsers.length === 1) helpGeneratorParser = commandParsers[0];
1105
- else helpGeneratorParser = longestMatchForMetaCommands(...commandParsers);
1108
+ helpGeneratorParser = getRootHelpGeneratorParser();
1106
1109
  docGeneratorParser = classified.commands.length > 0 ? parser : helpGeneratorParser;
1107
1110
  }
1108
1111
  const reportInvalidHelpCommand = (validationError) => {
@@ -1133,7 +1136,8 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
1133
1136
  bugs: isTopLevel && !isMetaCommandHelp ? bugs ?? doc.bugs : void 0,
1134
1137
  footer: shouldOverride ? footer ?? doc.footer : doc.footer ?? footer
1135
1138
  }, commandList, isTopLevel);
1136
- stdout(require_doc.formatDocPage(programName, augmentedDoc, {
1139
+ const renderedDoc = isTopLevel && usageLine != null ? applyUsageLine(augmentedDoc, usageLine) : augmentedDoc;
1140
+ stdout(require_doc.formatDocPage(programName, renderedDoc, {
1137
1141
  colors,
1138
1142
  maxWidth,
1139
1143
  showDefault,
@@ -1193,6 +1197,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
1193
1197
  let effectiveAboveError = currentAboveError;
1194
1198
  if (effectiveAboveError === "help") if (doc == null) effectiveAboveError = "usage";
1195
1199
  else {
1200
+ const isTopLevel = classified.commandPath.length < 1;
1196
1201
  const augmentedDoc = maybeCollapseCommandList({
1197
1202
  ...doc,
1198
1203
  brief: brief ?? doc.brief,
@@ -1201,8 +1206,10 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
1201
1206
  author: author ?? doc.author,
1202
1207
  bugs: bugs ?? doc.bugs,
1203
1208
  footer: footer ?? doc.footer
1204
- }, commandList, classified.commandPath.length < 1);
1205
- stderr(require_doc.formatDocPage(programName, augmentedDoc, {
1209
+ }, commandList, isTopLevel);
1210
+ const defaultRootUsage = typeof usageLine === "function" && (options.help || options.version || options.completion) ? require_usage.normalizeUsage(getRootHelpGeneratorParser().usage) : augmentedDoc.usage ?? [];
1211
+ const renderedDoc = isTopLevel && usageLine != null ? applyUsageLine(augmentedDoc, usageLine, defaultRootUsage) : augmentedDoc;
1212
+ stderr(require_doc.formatDocPage(programName, renderedDoc, {
1206
1213
  colors,
1207
1214
  maxWidth,
1208
1215
  showDefault,
@@ -1298,6 +1305,13 @@ function runParserAsync(parser, programName, args, options) {
1298
1305
  const result = runParser(parser, programName, args, options);
1299
1306
  return Promise.resolve(result);
1300
1307
  }
1308
+ function applyUsageLine(doc, usageLine, defaultUsageLine = doc.usage ?? []) {
1309
+ const customUsageLine = typeof usageLine === "function" ? usageLine(require_usage.cloneUsage(defaultUsageLine)) : usageLine;
1310
+ return {
1311
+ ...doc,
1312
+ usage: require_usage.normalizeUsage(customUsageLine)
1313
+ };
1314
+ }
1301
1315
  function maybeCollapseCommandList(doc, commandList, isTopLevel) {
1302
1316
  if (commandList !== "top-level" || !isTopLevel) return doc;
1303
1317
  return {
package/dist/facade.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Message } from "./message.cjs";
2
- import { HiddenVisibility, OptionName } from "./usage.cjs";
2
+ import { HiddenVisibility, OptionName, Usage } from "./usage.cjs";
3
3
  import { DocSection, ShowChoicesOptions, ShowDefaultOptions } from "./doc.cjs";
4
4
  import { InferMode, InferValue, Mode, ModeValue, Parser } from "./internal/parser.cjs";
5
5
  import { ShellCompletion } from "./completion.cjs";
@@ -127,6 +127,20 @@ interface RunOptions<THelp, TError> {
127
127
  * @since 1.2.0
128
128
  */
129
129
  readonly showUsage?: boolean;
130
+ /**
131
+ * Usage line override for top-level full help.
132
+ *
133
+ * This option customizes the usage shown by `--help`, the root help command,
134
+ * and `aboveError: "help"` at the root. It does not affect subcommand help,
135
+ * parsing, shell completion, or usage-only error preambles from
136
+ * `aboveError: "usage"`.
137
+ *
138
+ * The callback form receives the generated root usage after built-in help,
139
+ * version, and completion entries have been added.
140
+ *
141
+ * @since 1.3.0
142
+ */
143
+ readonly usageLine?: Usage | ((defaultUsageLine: Usage) => Usage);
130
144
  /**
131
145
  * How to render command lists in top-level help pages.
132
146
  *
package/dist/facade.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Message } from "./message.js";
2
- import { HiddenVisibility, OptionName } from "./usage.js";
2
+ import { HiddenVisibility, OptionName, Usage } from "./usage.js";
3
3
  import { DocSection, ShowChoicesOptions, ShowDefaultOptions } from "./doc.js";
4
4
  import { InferMode, InferValue, Mode, ModeValue, Parser } from "./internal/parser.js";
5
5
  import { ShellCompletion } from "./completion.js";
@@ -127,6 +127,20 @@ interface RunOptions<THelp, TError> {
127
127
  * @since 1.2.0
128
128
  */
129
129
  readonly showUsage?: boolean;
130
+ /**
131
+ * Usage line override for top-level full help.
132
+ *
133
+ * This option customizes the usage shown by `--help`, the root help command,
134
+ * and `aboveError: "help"` at the root. It does not affect subcommand help,
135
+ * parsing, shell completion, or usage-only error preambles from
136
+ * `aboveError: "usage"`.
137
+ *
138
+ * The callback form receives the generated root usage after built-in help,
139
+ * version, and completion entries have been added.
140
+ *
141
+ * @since 1.3.0
142
+ */
143
+ readonly usageLine?: Usage | ((defaultUsageLine: Usage) => Usage);
130
144
  /**
131
145
  * How to render command lists in top-level help pages.
132
146
  *
package/dist/facade.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { injectAnnotations, isInjectedAnnotationWrapper, unwrapInjectedAnnotationWrapper } from "./internal/annotations.js";
2
2
  import { commandLine, formatMessage, lineBreak, message, optionName, text, value } from "./message.js";
3
3
  import { validateCommandNames, validateContextIds, validateMetaNameCollisions, validateOptionNames, validateProgramName } from "./validate.js";
4
- import { formatUsage, isSuggestionHidden } from "./usage.js";
4
+ import { cloneUsage, formatUsage, isSuggestionHidden, normalizeUsage } from "./usage.js";
5
5
  import { formatDocPage } from "./doc.js";
6
6
  import { dispatchByMode } from "./internal/mode-dispatch.js";
7
7
  import { collectExplicitSourceValues, collectExplicitSourceValuesAsync, createDependencyRuntimeContext } from "./dependency-runtime.js";
@@ -952,7 +952,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
952
952
  options = optionsParam ?? {};
953
953
  }
954
954
  validateProgramName(programName);
955
- const { colors, maxWidth, showDefault, showChoices, sectionOrder, showUsage, commandList = "recursive", aboveError = "usage", onError = () => {
955
+ const { colors, maxWidth, showDefault, showChoices, sectionOrder, showUsage, usageLine, commandList = "recursive", aboveError = "usage", onError = () => {
956
956
  throw new RunParserError("Failed to parse command line arguments.");
957
957
  }, stderr = console.error, stdout = console.log, brief, description, examples, author, bugs, footer } = options;
958
958
  const norm = (c) => c === true ? {} : c;
@@ -1045,6 +1045,41 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
1045
1045
  completionCommandGroup: completionCommandConfig?.group,
1046
1046
  completionOptionGroup: completionOptionConfig?.group
1047
1047
  });
1048
+ const helpAsCommand = helpCommandConfig != null;
1049
+ const versionAsCommand = versionCommandConfig != null;
1050
+ const completionAsCommand = completionCommandConfig != null;
1051
+ const helpAsOption = helpOptionConfig != null;
1052
+ const versionAsOption = versionOptionConfig != null;
1053
+ const completionAsOption = completionOptionConfig != null;
1054
+ let cachedRootHelpGeneratorParser;
1055
+ const getRootHelpGeneratorParser = () => {
1056
+ if (cachedRootHelpGeneratorParser != null) return cachedRootHelpGeneratorParser;
1057
+ const commandParsers = [parser];
1058
+ const groupedMeta = {};
1059
+ const ungroupedMeta = [];
1060
+ const addMeta = (metaParser, groupLabel) => {
1061
+ if (groupLabel) (groupedMeta[groupLabel] ??= []).push(metaParser);
1062
+ else ungroupedMeta.push(metaParser);
1063
+ };
1064
+ if (helpAsCommand && helpParsers.helpCommand) addMeta(helpParsers.helpCommand, helpCommandConfig?.group);
1065
+ if (versionAsCommand && versionParsers.versionCommand) addMeta(versionParsers.versionCommand, versionCommandConfig?.group);
1066
+ if (completionAsCommand && completionParsers.completionCommand) addMeta(completionParsers.completionCommand, completionCommandConfig?.group);
1067
+ commandParsers.push(...ungroupedMeta);
1068
+ for (const [label, parsers] of Object.entries(groupedMeta)) commandParsers.push(group(label, parsers.length === 1 ? parsers[0] : longestMatch(...parsers)));
1069
+ const groupedMetaOptions = {};
1070
+ const ungroupedMetaOptions = [];
1071
+ const addMetaOption = (metaParser, groupLabel) => {
1072
+ if (groupLabel) (groupedMetaOptions[groupLabel] ??= []).push(metaParser);
1073
+ else ungroupedMetaOptions.push(metaParser);
1074
+ };
1075
+ if (helpAsOption && helpParsers.helpOption) addMetaOption(helpParsers.helpOption, helpOptionConfig?.group);
1076
+ if (versionAsOption && versionParsers.versionOption) addMetaOption(versionParsers.versionOption, versionOptionConfig?.group);
1077
+ if (completionAsOption && completionParsers.completionOption) addMetaOption(completionParsers.completionOption, completionOptionConfig?.group);
1078
+ commandParsers.push(...ungroupedMetaOptions);
1079
+ for (const [label, parsers] of Object.entries(groupedMetaOptions)) commandParsers.push(group(label, parsers.length === 1 ? parsers[0] : longestMatch(...parsers)));
1080
+ cachedRootHelpGeneratorParser = commandParsers.length === 1 ? commandParsers[0] : longestMatchForMetaCommands(...commandParsers);
1081
+ return cachedRootHelpGeneratorParser;
1082
+ };
1048
1083
  const handleResult = (classified) => {
1049
1084
  switch (classified.type) {
1050
1085
  case "success": return classified.value;
@@ -1059,12 +1094,6 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
1059
1094
  case "help": {
1060
1095
  let helpGeneratorParser;
1061
1096
  let docGeneratorParser;
1062
- const helpAsCommand = helpCommandConfig != null;
1063
- const versionAsCommand = versionCommandConfig != null;
1064
- const completionAsCommand = completionCommandConfig != null;
1065
- const helpAsOption = helpOptionConfig != null;
1066
- const versionAsOption = versionOptionConfig != null;
1067
- const completionAsOption = completionOptionConfig != null;
1068
1097
  const requestedCommand = classified.commands[0];
1069
1098
  if (requestedCommand != null && !classified.preferUserCommandDocs && completionCommandNames.includes(requestedCommand) && completionAsCommand && completionParsers.completionCommand) {
1070
1099
  helpGeneratorParser = completionParsers.completionCommand;
@@ -1076,33 +1105,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
1076
1105
  helpGeneratorParser = versionParsers.versionCommand;
1077
1106
  docGeneratorParser = helpGeneratorParser;
1078
1107
  } else {
1079
- const commandParsers = [parser];
1080
- const groupedMeta = {};
1081
- const ungroupedMeta = [];
1082
- const addMeta = (p, groupLabel) => {
1083
- if (groupLabel) (groupedMeta[groupLabel] ??= []).push(p);
1084
- else ungroupedMeta.push(p);
1085
- };
1086
- if (helpAsCommand && helpParsers.helpCommand) addMeta(helpParsers.helpCommand, helpCommandConfig?.group);
1087
- if (versionAsCommand && versionParsers.versionCommand) addMeta(versionParsers.versionCommand, versionCommandConfig?.group);
1088
- if (completionAsCommand && completionParsers.completionCommand) addMeta(completionParsers.completionCommand, completionCommandConfig?.group);
1089
- commandParsers.push(...ungroupedMeta);
1090
- for (const [label, parsers] of Object.entries(groupedMeta)) if (parsers.length === 1) commandParsers.push(group(label, parsers[0]));
1091
- else commandParsers.push(group(label, longestMatch(...parsers)));
1092
- const groupedMetaOptions = {};
1093
- const ungroupedMetaOptions = [];
1094
- const addMetaOption = (p, groupLabel) => {
1095
- if (groupLabel) (groupedMetaOptions[groupLabel] ??= []).push(p);
1096
- else ungroupedMetaOptions.push(p);
1097
- };
1098
- if (helpAsOption && helpParsers.helpOption) addMetaOption(helpParsers.helpOption, helpOptionConfig?.group);
1099
- if (versionAsOption && versionParsers.versionOption) addMetaOption(versionParsers.versionOption, versionOptionConfig?.group);
1100
- if (completionAsOption && completionParsers.completionOption) addMetaOption(completionParsers.completionOption, completionOptionConfig?.group);
1101
- commandParsers.push(...ungroupedMetaOptions);
1102
- for (const [label, optParsers] of Object.entries(groupedMetaOptions)) if (optParsers.length === 1) commandParsers.push(group(label, optParsers[0]));
1103
- else commandParsers.push(group(label, longestMatch(...optParsers)));
1104
- if (commandParsers.length === 1) helpGeneratorParser = commandParsers[0];
1105
- else helpGeneratorParser = longestMatchForMetaCommands(...commandParsers);
1108
+ helpGeneratorParser = getRootHelpGeneratorParser();
1106
1109
  docGeneratorParser = classified.commands.length > 0 ? parser : helpGeneratorParser;
1107
1110
  }
1108
1111
  const reportInvalidHelpCommand = (validationError) => {
@@ -1133,7 +1136,8 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
1133
1136
  bugs: isTopLevel && !isMetaCommandHelp ? bugs ?? doc.bugs : void 0,
1134
1137
  footer: shouldOverride ? footer ?? doc.footer : doc.footer ?? footer
1135
1138
  }, commandList, isTopLevel);
1136
- stdout(formatDocPage(programName, augmentedDoc, {
1139
+ const renderedDoc = isTopLevel && usageLine != null ? applyUsageLine(augmentedDoc, usageLine) : augmentedDoc;
1140
+ stdout(formatDocPage(programName, renderedDoc, {
1137
1141
  colors,
1138
1142
  maxWidth,
1139
1143
  showDefault,
@@ -1193,6 +1197,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
1193
1197
  let effectiveAboveError = currentAboveError;
1194
1198
  if (effectiveAboveError === "help") if (doc == null) effectiveAboveError = "usage";
1195
1199
  else {
1200
+ const isTopLevel = classified.commandPath.length < 1;
1196
1201
  const augmentedDoc = maybeCollapseCommandList({
1197
1202
  ...doc,
1198
1203
  brief: brief ?? doc.brief,
@@ -1201,8 +1206,10 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
1201
1206
  author: author ?? doc.author,
1202
1207
  bugs: bugs ?? doc.bugs,
1203
1208
  footer: footer ?? doc.footer
1204
- }, commandList, classified.commandPath.length < 1);
1205
- stderr(formatDocPage(programName, augmentedDoc, {
1209
+ }, commandList, isTopLevel);
1210
+ const defaultRootUsage = typeof usageLine === "function" && (options.help || options.version || options.completion) ? normalizeUsage(getRootHelpGeneratorParser().usage) : augmentedDoc.usage ?? [];
1211
+ const renderedDoc = isTopLevel && usageLine != null ? applyUsageLine(augmentedDoc, usageLine, defaultRootUsage) : augmentedDoc;
1212
+ stderr(formatDocPage(programName, renderedDoc, {
1206
1213
  colors,
1207
1214
  maxWidth,
1208
1215
  showDefault,
@@ -1298,6 +1305,13 @@ function runParserAsync(parser, programName, args, options) {
1298
1305
  const result = runParser(parser, programName, args, options);
1299
1306
  return Promise.resolve(result);
1300
1307
  }
1308
+ function applyUsageLine(doc, usageLine, defaultUsageLine = doc.usage ?? []) {
1309
+ const customUsageLine = typeof usageLine === "function" ? usageLine(cloneUsage(defaultUsageLine)) : usageLine;
1310
+ return {
1311
+ ...doc,
1312
+ usage: normalizeUsage(customUsageLine)
1313
+ };
1314
+ }
1301
1315
  function maybeCollapseCommandList(doc, commandList, isTopLevel) {
1302
1316
  if (commandList !== "top-level" || !isTopLevel) return doc;
1303
1317
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.3.0-dev.2352",
3
+ "version": "1.3.0-dev.2357",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",
@@ -225,7 +225,7 @@
225
225
  "fast-check": "^4.7.0",
226
226
  "tsdown": "^0.13.0",
227
227
  "typescript": "^5.8.3",
228
- "@optique/env": "1.3.0-dev.2352+32a63ede"
228
+ "@optique/env": "1.3.0-dev.2357+dcd8c5c2"
229
229
  },
230
230
  "scripts": {
231
231
  "build": "tsdown",
@@ -56,6 +56,9 @@ Core rules
56
56
  discriminated union.
57
57
  - Enable completion through `run(parser, { completion: "both" })` for CLI
58
58
  apps. Do not hand-write completion scripts from parser metadata.
59
+ - Use `usageLine: [{ type: "ellipsis" }]` in runner options when a large root
60
+ synopsis should become a compact `Usage: myapp ...` line. This applies only
61
+ to root full help; use `command()`'s `usageLine` for subcommand help.
59
62
  - Use `showUsage: false` in runner options when full help should show the
60
63
  brief and command or option sections without the `Usage:` synopsis.
61
64
  For deeply nested command trees, add `commandList: "top-level"` when root