@optique/core 1.3.0-dev.2366 → 1.3.0-dev.2375

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.
@@ -42,6 +42,51 @@ type ModeValue<M extends Mode, T> = M extends "async" ? Promise<T> : T;
42
42
  * @since 0.9.0
43
43
  */
44
44
  type ModeIterable<M extends Mode, T> = M extends "async" ? AsyncIterable<T> : Iterable<T>;
45
+ /**
46
+ * Internal symbol used by transparent combinators to expose independently
47
+ * competing parse operations to shared-buffer parents.
48
+ * @internal
49
+ */
50
+ declare const parseLanesKey: unique symbol;
51
+ /**
52
+ * An owner-consumption requirement for a parse lane.
53
+ * @internal
54
+ */
55
+ interface ParseLaneConsumptionGroup {
56
+ /** Identity shared by lanes belonging to the same owning parser. */
57
+ readonly id: object;
58
+ /** Whether the owning parser is active after a consuming lane succeeds. */
59
+ readonly isActive?: (state: unknown) => boolean;
60
+ }
61
+ /**
62
+ * A state-preserving parse operation that competes at one exact priority.
63
+ * @internal
64
+ */
65
+ interface ParseLane<TState> {
66
+ /** The exact priority at which this lane competes. */
67
+ readonly priority: number;
68
+ /**
69
+ * Identity shared by lanes that must all succeed before their
70
+ * zero-consumption state updates can be committed.
71
+ */
72
+ readonly zeroConsumptionGroup?: object;
73
+ /**
74
+ * Whether a zero-consumption success can settle the owning parser while
75
+ * input remains. Defaults to `true`.
76
+ */
77
+ readonly settlesZeroConsumption?: boolean;
78
+ /** Fixed tokens reachable through this lane. */
79
+ readonly leadingNames: ReadonlySet<string>;
80
+ /** Whether this lane accepts any positional token. */
81
+ readonly acceptingAnyToken: boolean;
82
+ /**
83
+ * Groups in which a zero-consumption update is valid only after another
84
+ * lane in every group has consumed input during the same arbitration.
85
+ */
86
+ readonly requiredConsumptionGroups?: readonly ParseLaneConsumptionGroup[];
87
+ /** Parses through the owning parser's state and execution context. */
88
+ parse(context: ParserContext<TState>): ParserResult<TState> | Promise<ParserResult<TState>>;
89
+ }
45
90
  /**
46
91
  * Combines multiple modes into a single mode.
47
92
  * If any mode is `"async"`, the result is `"async"`; otherwise `"sync"`.
@@ -851,6 +896,22 @@ declare function getDelegatingSuggestRuntimeNodes<TInnerState>(innerParser: Pars
851
896
  * @internal
852
897
  */
853
898
  declare function composeWrappedSourceMetadata(dependencyMetadata: ParserDependencyMetadata | undefined, wrapSource: (source: NonNullable<ParserDependencyMetadata["source"]>) => NonNullable<ParserDependencyMetadata["source"]>): ParserDependencyMetadata | undefined;
899
+ /**
900
+ * Defines internal parse-lane metadata without exposing it through object
901
+ * spreads used by custom parser wrappers.
902
+ *
903
+ * @internal
904
+ */
905
+ declare function defineParseLanes<TState>(parser: object, lanes: readonly ParseLane<TState>[] | undefined): void;
906
+ /**
907
+ * Gets parse-lane metadata defined directly on a parser.
908
+ *
909
+ * Inherited metadata belongs to the parser's prototype and must not bypass an
910
+ * overriding `parse()` implementation on a custom wrapper.
911
+ *
912
+ * @internal
913
+ */
914
+ declare function getOwnParseLanes<TState>(parser: object): readonly ParseLane<TState>[] | undefined;
854
915
  /**
855
916
  * Marks a parser as inheriting parent-state annotations through wrapper-state
856
917
  * reconstruction.
@@ -999,4 +1060,4 @@ declare function getDocPage(parser: Parser<"sync", unknown, unknown>, argsOrOpti
999
1060
  declare function getDocPage(parser: Parser<"async", unknown, unknown>, argsOrOptions?: readonly string[] | ParseOptions, options?: ParseOptions): Promise<DocPage | undefined>;
1000
1061
  declare function getDocPage<M extends Mode>(parser: Parser<M, unknown, unknown>, argsOrOptions?: readonly string[] | ParseOptions, options?: ParseOptions): ModeValue<M, DocPage | undefined>;
1001
1062
  //#endregion
1002
- export { CombineModes, DocState, ExecutionContext, ExecutionPhase, InferMode, InferValue, Mode, ModeIterable, ModeValue, ParseFrame, type ParseOptions, Parser, ParserContext, ParserResult, Result, Suggestion, annotationWrapperRequiresSourceBindingKey, composeWrappedSourceMetadata, createParserContext, defineInheritedAnnotationParser, defineSourceBindingOnlyAnnotationCompletionParser, getDelegatingSuggestRuntimeNodes, getDocPage, getDocPageAsync, getDocPageSync, getParserSuggestRuntimeNodes, inheritParentAnnotationsKey, parse, parseAsync, parseSync, suggest, suggestAsync, suggestSync, unmatchedNonCliDependencySourceStateMarker };
1063
+ export { CombineModes, DocState, ExecutionContext, ExecutionPhase, InferMode, InferValue, Mode, ModeIterable, ModeValue, ParseFrame, ParseLane, ParseLaneConsumptionGroup, type ParseOptions, Parser, ParserContext, ParserResult, Result, Suggestion, annotationWrapperRequiresSourceBindingKey, composeWrappedSourceMetadata, createParserContext, defineInheritedAnnotationParser, defineParseLanes, defineSourceBindingOnlyAnnotationCompletionParser, getDelegatingSuggestRuntimeNodes, getDocPage, getDocPageAsync, getDocPageSync, getOwnParseLanes, getParserSuggestRuntimeNodes, inheritParentAnnotationsKey, parse, parseAsync, parseLanesKey, parseSync, suggest, suggestAsync, suggestSync, unmatchedNonCliDependencySourceStateMarker };
@@ -8,6 +8,12 @@ import { createInputTrace } from "../input-trace.js";
8
8
 
9
9
  //#region src/internal/parser.ts
10
10
  /**
11
+ * Internal symbol used by transparent combinators to expose independently
12
+ * competing parse operations to shared-buffer parents.
13
+ * @internal
14
+ */
15
+ const parseLanesKey = Symbol("parseLanes");
16
+ /**
11
17
  * Internal marker for wrappers whose `{ hasCliValue: false }` states should
12
18
  * be treated as unmatched dependency-source states during completion-time
13
19
  * Phase 1.
@@ -387,6 +393,32 @@ function composeWrappedSourceMetadata(dependencyMetadata, wrapSource) {
387
393
  };
388
394
  }
389
395
  /**
396
+ * Defines internal parse-lane metadata without exposing it through object
397
+ * spreads used by custom parser wrappers.
398
+ *
399
+ * @internal
400
+ */
401
+ function defineParseLanes(parser, lanes) {
402
+ if (lanes == null) return;
403
+ Object.defineProperty(parser, parseLanesKey, {
404
+ value: lanes,
405
+ configurable: true,
406
+ enumerable: false
407
+ });
408
+ }
409
+ /**
410
+ * Gets parse-lane metadata defined directly on a parser.
411
+ *
412
+ * Inherited metadata belongs to the parser's prototype and must not bypass an
413
+ * overriding `parse()` implementation on a custom wrapper.
414
+ *
415
+ * @internal
416
+ */
417
+ function getOwnParseLanes(parser) {
418
+ if (!Object.hasOwn(parser, parseLanesKey)) return void 0;
419
+ return parser[parseLanesKey];
420
+ }
421
+ /**
390
422
  * Marks a parser as inheriting parent-state annotations through wrapper-state
391
423
  * reconstruction.
392
424
  *
@@ -850,4 +882,4 @@ function findNextMatchedCommandArgIndex(args, matchedCommandArgIndices, start) {
850
882
  }
851
883
 
852
884
  //#endregion
853
- export { annotationWrapperRequiresSourceBindingKey, composeWrappedSourceMetadata, createParserContext, defineInheritedAnnotationParser, defineSourceBindingOnlyAnnotationCompletionParser, getDelegatingSuggestRuntimeNodes, getDocPage, getDocPageAsync, getDocPageSync, getParserSuggestRuntimeNodes, inheritParentAnnotationsKey, parse, parseAsync, parseSync, suggest, suggestAsync, suggestSync, unmatchedNonCliDependencySourceStateMarker };
885
+ export { annotationWrapperRequiresSourceBindingKey, composeWrappedSourceMetadata, createParserContext, defineInheritedAnnotationParser, defineParseLanes, defineSourceBindingOnlyAnnotationCompletionParser, getDelegatingSuggestRuntimeNodes, getDocPage, getDocPageAsync, getDocPageSync, getOwnParseLanes, getParserSuggestRuntimeNodes, inheritParentAnnotationsKey, parse, parseAsync, parseLanesKey, parseSync, suggest, suggestAsync, suggestSync, unmatchedNonCliDependencySourceStateMarker };
@@ -231,6 +231,44 @@ function processOptionalStyleResult(result, innerState, context) {
231
231
  };
232
232
  return result;
233
233
  }
234
+ function adaptOptionalStyleParseLanes(parser) {
235
+ const lanes = require_internal_parser.getOwnParseLanes(parser);
236
+ if (lanes == null) return void 0;
237
+ const consumptionGroup = {};
238
+ return lanes.map((lane) => ({
239
+ priority: lane.priority,
240
+ zeroConsumptionGroup: lane.zeroConsumptionGroup,
241
+ settlesZeroConsumption: lane.settlesZeroConsumption,
242
+ leadingNames: lane.leadingNames,
243
+ acceptingAnyToken: false,
244
+ requiredConsumptionGroups: [...lane.requiredConsumptionGroups?.map((group) => ({
245
+ id: group.id,
246
+ ...group.isActive == null ? {} : { isActive(state) {
247
+ const innerState = Array.isArray(state) ? state[0] : parser.initialState;
248
+ return group.isActive?.(innerState) ?? true;
249
+ } }
250
+ })) ?? [], {
251
+ id: consumptionGroup,
252
+ isActive(state) {
253
+ return Array.isArray(state);
254
+ }
255
+ }],
256
+ parse(context) {
257
+ const innerState = deriveOptionalInnerParseState(context.state, parser);
258
+ const innerContext = {
259
+ ...context,
260
+ state: innerState
261
+ };
262
+ return require_mode_dispatch.dispatchByMode(parser.mode, () => {
263
+ const result = lane.parse(innerContext);
264
+ return processOptionalStyleResult(result, innerState, context);
265
+ }, async () => {
266
+ const result = await lane.parse(innerContext);
267
+ return processOptionalStyleResult(result, innerState, context);
268
+ });
269
+ }
270
+ }));
271
+ }
234
272
  /**
235
273
  * Creates a `shouldDeferCompletion` adapter that unwraps the outer state
236
274
  * shape (`[TState] | undefined`) used by {@link optional} and
@@ -420,6 +458,7 @@ function optional(parser) {
420
458
  const composed = require_dependency_metadata.composeDependencyMetadata(parser.dependencyMetadata, "optional");
421
459
  if (composed != null) optionalParser.dependencyMetadata = composed;
422
460
  }
461
+ require_internal_parser.defineParseLanes(optionalParser, adaptOptionalStyleParseLanes(parser));
423
462
  require_internal_parser.defineInheritedAnnotationParser(optionalParser);
424
463
  require_internal_parser.defineSourceBindingOnlyAnnotationCompletionParser(optionalParser);
425
464
  return fluent(optionalParser);
@@ -654,6 +693,7 @@ function withDefault(parser, defaultValue, options) {
654
693
  } });
655
694
  if (composed != null) withDefaultParser.dependencyMetadata = composed;
656
695
  }
696
+ require_internal_parser.defineParseLanes(withDefaultParser, adaptOptionalStyleParseLanes(parser));
657
697
  require_internal_parser.defineInheritedAnnotationParser(withDefaultParser);
658
698
  require_internal_parser.defineSourceBindingOnlyAnnotationCompletionParser(withDefaultParser);
659
699
  return fluent(withDefaultParser);
@@ -793,6 +833,7 @@ function map(parser, transform) {
793
833
  return parser.getDocFragments(state, void 0);
794
834
  }
795
835
  };
836
+ require_internal_parser.defineParseLanes(mappedParser, require_internal_parser.getOwnParseLanes(parser));
796
837
  delete mappedParser.normalizeValue;
797
838
  delete mappedParser.validateValue;
798
839
  if ("placeholder" in parser) Object.defineProperty(mappedParser, "placeholder", {
@@ -1680,6 +1721,7 @@ function multiple(parser, options = {}) {
1680
1721
  function nonEmpty(parser) {
1681
1722
  const syncParser = parser;
1682
1723
  const initialState = parser.initialState;
1724
+ const consumptionGroup = {};
1683
1725
  const processNonEmptyResult = (result) => {
1684
1726
  if (!result.success) return result;
1685
1727
  if (result.consumed.length === 0) return {
@@ -1732,6 +1774,17 @@ function nonEmpty(parser) {
1732
1774
  return syncParser.getDocFragments(state, defaultValue);
1733
1775
  }
1734
1776
  };
1777
+ require_internal_parser.defineParseLanes(nonEmptyParser, require_internal_parser.getOwnParseLanes(parser)?.map((lane) => ({
1778
+ priority: lane.priority,
1779
+ zeroConsumptionGroup: lane.zeroConsumptionGroup,
1780
+ settlesZeroConsumption: lane.settlesZeroConsumption,
1781
+ leadingNames: lane.leadingNames,
1782
+ acceptingAnyToken: lane.acceptingAnyToken,
1783
+ requiredConsumptionGroups: [...lane.requiredConsumptionGroups ?? [], { id: consumptionGroup }],
1784
+ parse(context) {
1785
+ return lane.parse(context);
1786
+ }
1787
+ })));
1735
1788
  if ("placeholder" in parser) Object.defineProperty(nonEmptyParser, "placeholder", {
1736
1789
  get() {
1737
1790
  return parser.placeholder;
package/dist/modifiers.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { annotateFreshArray, annotationKey, getAnnotations, inheritAnnotations, isInjectedAnnotationWrapper, unwrapInjectedAnnotationWrapper } from "./internal/annotations.js";
2
2
  import { formatMessage, message, text } from "./message.js";
3
3
  import { dispatchByMode, dispatchIterableByMode, mapModeValue, wrapForMode } from "./internal/mode-dispatch.js";
4
- import { defineInheritedAnnotationParser, defineSourceBindingOnlyAnnotationCompletionParser, unmatchedNonCliDependencySourceStateMarker } from "./internal/parser.js";
4
+ import { defineInheritedAnnotationParser, defineParseLanes, defineSourceBindingOnlyAnnotationCompletionParser, getOwnParseLanes, unmatchedNonCliDependencySourceStateMarker } from "./internal/parser.js";
5
5
  import { getDelegatedAnnotationState, hasDelegatedAnnotationCarrier, isAnnotationWrappedInitialState, normalizeDelegatedAnnotationState, normalizeNestedDelegatedAnnotationState } from "./annotation-state.js";
6
6
  import { mergeChildExec, withChildContext, withChildExecPath } from "./execution-context.js";
7
7
  import { completeOrExtractPhase2Seed, extractPhase2Seed, extractPhase2SeedKey, phase2SeedFromValueResult } from "./phase2-seed.js";
@@ -231,6 +231,44 @@ function processOptionalStyleResult(result, innerState, context) {
231
231
  };
232
232
  return result;
233
233
  }
234
+ function adaptOptionalStyleParseLanes(parser) {
235
+ const lanes = getOwnParseLanes(parser);
236
+ if (lanes == null) return void 0;
237
+ const consumptionGroup = {};
238
+ return lanes.map((lane) => ({
239
+ priority: lane.priority,
240
+ zeroConsumptionGroup: lane.zeroConsumptionGroup,
241
+ settlesZeroConsumption: lane.settlesZeroConsumption,
242
+ leadingNames: lane.leadingNames,
243
+ acceptingAnyToken: false,
244
+ requiredConsumptionGroups: [...lane.requiredConsumptionGroups?.map((group) => ({
245
+ id: group.id,
246
+ ...group.isActive == null ? {} : { isActive(state) {
247
+ const innerState = Array.isArray(state) ? state[0] : parser.initialState;
248
+ return group.isActive?.(innerState) ?? true;
249
+ } }
250
+ })) ?? [], {
251
+ id: consumptionGroup,
252
+ isActive(state) {
253
+ return Array.isArray(state);
254
+ }
255
+ }],
256
+ parse(context) {
257
+ const innerState = deriveOptionalInnerParseState(context.state, parser);
258
+ const innerContext = {
259
+ ...context,
260
+ state: innerState
261
+ };
262
+ return dispatchByMode(parser.mode, () => {
263
+ const result = lane.parse(innerContext);
264
+ return processOptionalStyleResult(result, innerState, context);
265
+ }, async () => {
266
+ const result = await lane.parse(innerContext);
267
+ return processOptionalStyleResult(result, innerState, context);
268
+ });
269
+ }
270
+ }));
271
+ }
234
272
  /**
235
273
  * Creates a `shouldDeferCompletion` adapter that unwraps the outer state
236
274
  * shape (`[TState] | undefined`) used by {@link optional} and
@@ -420,6 +458,7 @@ function optional(parser) {
420
458
  const composed = composeDependencyMetadata(parser.dependencyMetadata, "optional");
421
459
  if (composed != null) optionalParser.dependencyMetadata = composed;
422
460
  }
461
+ defineParseLanes(optionalParser, adaptOptionalStyleParseLanes(parser));
423
462
  defineInheritedAnnotationParser(optionalParser);
424
463
  defineSourceBindingOnlyAnnotationCompletionParser(optionalParser);
425
464
  return fluent(optionalParser);
@@ -654,6 +693,7 @@ function withDefault(parser, defaultValue, options) {
654
693
  } });
655
694
  if (composed != null) withDefaultParser.dependencyMetadata = composed;
656
695
  }
696
+ defineParseLanes(withDefaultParser, adaptOptionalStyleParseLanes(parser));
657
697
  defineInheritedAnnotationParser(withDefaultParser);
658
698
  defineSourceBindingOnlyAnnotationCompletionParser(withDefaultParser);
659
699
  return fluent(withDefaultParser);
@@ -793,6 +833,7 @@ function map(parser, transform) {
793
833
  return parser.getDocFragments(state, void 0);
794
834
  }
795
835
  };
836
+ defineParseLanes(mappedParser, getOwnParseLanes(parser));
796
837
  delete mappedParser.normalizeValue;
797
838
  delete mappedParser.validateValue;
798
839
  if ("placeholder" in parser) Object.defineProperty(mappedParser, "placeholder", {
@@ -1680,6 +1721,7 @@ function multiple(parser, options = {}) {
1680
1721
  function nonEmpty(parser) {
1681
1722
  const syncParser = parser;
1682
1723
  const initialState = parser.initialState;
1724
+ const consumptionGroup = {};
1683
1725
  const processNonEmptyResult = (result) => {
1684
1726
  if (!result.success) return result;
1685
1727
  if (result.consumed.length === 0) return {
@@ -1732,6 +1774,17 @@ function nonEmpty(parser) {
1732
1774
  return syncParser.getDocFragments(state, defaultValue);
1733
1775
  }
1734
1776
  };
1777
+ defineParseLanes(nonEmptyParser, getOwnParseLanes(parser)?.map((lane) => ({
1778
+ priority: lane.priority,
1779
+ zeroConsumptionGroup: lane.zeroConsumptionGroup,
1780
+ settlesZeroConsumption: lane.settlesZeroConsumption,
1781
+ leadingNames: lane.leadingNames,
1782
+ acceptingAnyToken: lane.acceptingAnyToken,
1783
+ requiredConsumptionGroups: [...lane.requiredConsumptionGroups ?? [], { id: consumptionGroup }],
1784
+ parse(context) {
1785
+ return lane.parse(context);
1786
+ }
1787
+ })));
1735
1788
  if ("placeholder" in parser) Object.defineProperty(nonEmptyParser, "placeholder", {
1736
1789
  get() {
1737
1790
  return parser.placeholder;
@@ -620,6 +620,71 @@ function string(options = {}) {
620
620
  }
621
621
  };
622
622
  }
623
+ /**
624
+ * Creates a {@link ValueParser} that compiles regular expression sources.
625
+ *
626
+ * The entire input is treated as the source. Slash-delimited notation such
627
+ * as `/pattern/flags` is not interpreted; use {@link RegExpOptions.flags} to
628
+ * configure fixed flags for the parser.
629
+ *
630
+ * **Security note**: Compiling a source does not establish that it is safe to
631
+ * execute. Patterns from untrusted input can cause Regular Expression Denial
632
+ * of Service (ReDoS) when matched. Limit pattern and subject lengths, execute
633
+ * matches in an environment that can be terminated, or use a linear-time
634
+ * regular expression engine when accepting untrusted patterns.
635
+ *
636
+ * @param options Configuration options for the regular expression parser.
637
+ * @returns A sync value parser producing JavaScript {@link RegExp} objects.
638
+ * @throws {TypeError} If `options.metavar` is an empty string or
639
+ * `options.flags` is not a string.
640
+ * @throws {SyntaxError} If `options.flags` contains invalid, duplicate, or
641
+ * incompatible regular expression flags.
642
+ * @since 1.3.0
643
+ */
644
+ function regExp(options = {}) {
645
+ const metavar$1 = options.metavar ?? "REGEXP";
646
+ require_nonempty.ensureNonEmptyString(metavar$1);
647
+ if (options.flags !== void 0 && typeof options.flags !== "string") throw new TypeError(`Expected flags to be a string, but got ${typeof options.flags}: ${String(options.flags)}.`);
648
+ const flags = new RegExp("", options.flags ?? "").flags;
649
+ const invalidRegExp = options.errors?.invalidRegExp;
650
+ const parseRegExp = (input) => {
651
+ try {
652
+ return {
653
+ success: true,
654
+ value: new RegExp(input, flags)
655
+ };
656
+ } catch (error) {
657
+ if (!(error instanceof SyntaxError)) throw error;
658
+ return {
659
+ success: false,
660
+ error: invalidRegExp ? typeof invalidRegExp === "function" ? invalidRegExp(input) : invalidRegExp : require_message.message`Invalid regular expression: ${input}.`
661
+ };
662
+ }
663
+ };
664
+ return {
665
+ mode: "sync",
666
+ metavar: metavar$1,
667
+ get placeholder() {
668
+ return new RegExp("", flags);
669
+ },
670
+ parse: parseRegExp,
671
+ validate(value) {
672
+ if (!(value instanceof RegExp)) return {
673
+ success: false,
674
+ error: require_message.message`Expected a RegExp value.`
675
+ };
676
+ return parseRegExp(value.source);
677
+ },
678
+ normalize(value) {
679
+ if (!(value instanceof RegExp)) return value;
680
+ const result = parseRegExp(value.source);
681
+ return result.success ? result.value : value;
682
+ },
683
+ format(value) {
684
+ return value.source;
685
+ }
686
+ };
687
+ }
623
688
  function keyValue(options = {}) {
624
689
  const separator = options.separator ?? "=";
625
690
  if (typeof separator !== "string") throw new TypeError(`Expected separator to be a string, but got ${typeof separator}: ${String(separator)}.`);
@@ -6628,6 +6693,7 @@ exports.locale = locale;
6628
6693
  exports.macAddress = macAddress;
6629
6694
  exports.port = port;
6630
6695
  exports.portRange = portRange;
6696
+ exports.regExp = regExp;
6631
6697
  exports.semVer = semVer;
6632
6698
  exports.socketAddress = socketAddress;
6633
6699
  exports.string = string;
@@ -498,6 +498,64 @@ declare function checkEnumOption<T extends object>(options: T | undefined, key:
498
498
  * `RegExp` instance.
499
499
  */
500
500
  declare function string(options?: StringOptions): ValueParser<"sync", string>;
501
+ /**
502
+ * Options for creating a {@link regExp} value parser.
503
+ *
504
+ * @since 1.3.0
505
+ */
506
+ interface RegExpOptions {
507
+ /**
508
+ * The metavariable name for this parser. This is used in help messages to
509
+ * indicate what kind of value this parser expects.
510
+ * @default `"REGEXP"`
511
+ * @since 1.3.0
512
+ */
513
+ readonly metavar?: NonEmptyString;
514
+ /**
515
+ * Fixed flags used to compile every input source.
516
+ * @default `""`
517
+ * @since 1.3.0
518
+ */
519
+ readonly flags?: string;
520
+ /**
521
+ * Custom error messages for regular expression parsing failures.
522
+ * @since 1.3.0
523
+ */
524
+ readonly errors?: {
525
+ /**
526
+ * Custom error message when the input is not a valid regular expression
527
+ * source. Can be a static message or a function that receives the input.
528
+ *
529
+ * **Security note**: Successful compilation does not guarantee safe
530
+ * execution. Vulnerable patterns can cause catastrophic backtracking when
531
+ * later matched against untrusted data.
532
+ * @since 1.3.0
533
+ */
534
+ readonly invalidRegExp?: Message | ((input: string) => Message);
535
+ };
536
+ }
537
+ /**
538
+ * Creates a {@link ValueParser} that compiles regular expression sources.
539
+ *
540
+ * The entire input is treated as the source. Slash-delimited notation such
541
+ * as `/pattern/flags` is not interpreted; use {@link RegExpOptions.flags} to
542
+ * configure fixed flags for the parser.
543
+ *
544
+ * **Security note**: Compiling a source does not establish that it is safe to
545
+ * execute. Patterns from untrusted input can cause Regular Expression Denial
546
+ * of Service (ReDoS) when matched. Limit pattern and subject lengths, execute
547
+ * matches in an environment that can be terminated, or use a linear-time
548
+ * regular expression engine when accepting untrusted patterns.
549
+ *
550
+ * @param options Configuration options for the regular expression parser.
551
+ * @returns A sync value parser producing JavaScript {@link RegExp} objects.
552
+ * @throws {TypeError} If `options.metavar` is an empty string or
553
+ * `options.flags` is not a string.
554
+ * @throws {SyntaxError} If `options.flags` contains invalid, duplicate, or
555
+ * incompatible regular expression flags.
556
+ * @since 1.3.0
557
+ */
558
+ declare function regExp(options?: RegExpOptions): ValueParser<"sync", RegExp>;
501
559
  interface KeyValueOptionsBase {
502
560
  /**
503
561
  * The metavariable name for this parser. Used in help messages to
@@ -3279,4 +3337,4 @@ declare function firstOf<const TParsers extends readonly [ValueParser<"sync", un
3279
3337
  */
3280
3338
  declare function firstOf<const TParsers extends readonly ValueParser<"sync", unknown>[]>(parsers: TParsers, options?: FirstOfOptions): ValueParser<"sync", ValueParserValue<TParsers[number]>>;
3281
3339
  //#endregion
3282
- export { ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, CidrOptions, CidrValue, Color, ColorFormat, ColorOptions, CronExpression, CronExpressionForOptions, CronOptions, DeferredMap, DomainOptions, EmailOptions, FileSizeOptions, FileSizeOptionsBigInt, FileSizeOptionsNumber, FileSizeUnit, FirstOfOptions, FloatOptions, HostnameOptions, IntegerOptionsBigInt, IntegerOptionsNumber, IpOptions, Ipv4Options, Ipv6Options, Json, JsonOptions, KeyValueOptions, LocaleOptions, MacAddressOptions, type Mode, type ModeIterable, type ModeValue, type NonEmptyString, PortOptionsBigInt, PortOptionsNumber, PortRangeOptionsBigInt, PortRangeOptionsNumber, PortRangeValueBigInt, PortRangeValueNumber, SemVer, SemVerOptionsObject, SemVerOptionsString, SemVerString, SocketAddressOptions, SocketAddressValue, StringOptions, TransformMapping, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, biject, checkBooleanOption, checkEnumOption, choice, cidr, color, cron, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, keyValue, locale, macAddress, port, portRange, semVer, socketAddress, string, transform, url, uuid };
3340
+ export { ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, CidrOptions, CidrValue, Color, ColorFormat, ColorOptions, CronExpression, CronExpressionForOptions, CronOptions, DeferredMap, DomainOptions, EmailOptions, FileSizeOptions, FileSizeOptionsBigInt, FileSizeOptionsNumber, FileSizeUnit, FirstOfOptions, FloatOptions, HostnameOptions, IntegerOptionsBigInt, IntegerOptionsNumber, IpOptions, Ipv4Options, Ipv6Options, Json, JsonOptions, KeyValueOptions, LocaleOptions, MacAddressOptions, type Mode, type ModeIterable, type ModeValue, type NonEmptyString, PortOptionsBigInt, PortOptionsNumber, PortRangeOptionsBigInt, PortRangeOptionsNumber, PortRangeValueBigInt, PortRangeValueNumber, RegExpOptions, SemVer, SemVerOptionsObject, SemVerOptionsString, SemVerString, SocketAddressOptions, SocketAddressValue, StringOptions, TransformMapping, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, biject, checkBooleanOption, checkEnumOption, choice, cidr, color, cron, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, keyValue, locale, macAddress, port, portRange, regExp, semVer, socketAddress, string, transform, url, uuid };
@@ -498,6 +498,64 @@ declare function checkEnumOption<T extends object>(options: T | undefined, key:
498
498
  * `RegExp` instance.
499
499
  */
500
500
  declare function string(options?: StringOptions): ValueParser<"sync", string>;
501
+ /**
502
+ * Options for creating a {@link regExp} value parser.
503
+ *
504
+ * @since 1.3.0
505
+ */
506
+ interface RegExpOptions {
507
+ /**
508
+ * The metavariable name for this parser. This is used in help messages to
509
+ * indicate what kind of value this parser expects.
510
+ * @default `"REGEXP"`
511
+ * @since 1.3.0
512
+ */
513
+ readonly metavar?: NonEmptyString;
514
+ /**
515
+ * Fixed flags used to compile every input source.
516
+ * @default `""`
517
+ * @since 1.3.0
518
+ */
519
+ readonly flags?: string;
520
+ /**
521
+ * Custom error messages for regular expression parsing failures.
522
+ * @since 1.3.0
523
+ */
524
+ readonly errors?: {
525
+ /**
526
+ * Custom error message when the input is not a valid regular expression
527
+ * source. Can be a static message or a function that receives the input.
528
+ *
529
+ * **Security note**: Successful compilation does not guarantee safe
530
+ * execution. Vulnerable patterns can cause catastrophic backtracking when
531
+ * later matched against untrusted data.
532
+ * @since 1.3.0
533
+ */
534
+ readonly invalidRegExp?: Message | ((input: string) => Message);
535
+ };
536
+ }
537
+ /**
538
+ * Creates a {@link ValueParser} that compiles regular expression sources.
539
+ *
540
+ * The entire input is treated as the source. Slash-delimited notation such
541
+ * as `/pattern/flags` is not interpreted; use {@link RegExpOptions.flags} to
542
+ * configure fixed flags for the parser.
543
+ *
544
+ * **Security note**: Compiling a source does not establish that it is safe to
545
+ * execute. Patterns from untrusted input can cause Regular Expression Denial
546
+ * of Service (ReDoS) when matched. Limit pattern and subject lengths, execute
547
+ * matches in an environment that can be terminated, or use a linear-time
548
+ * regular expression engine when accepting untrusted patterns.
549
+ *
550
+ * @param options Configuration options for the regular expression parser.
551
+ * @returns A sync value parser producing JavaScript {@link RegExp} objects.
552
+ * @throws {TypeError} If `options.metavar` is an empty string or
553
+ * `options.flags` is not a string.
554
+ * @throws {SyntaxError} If `options.flags` contains invalid, duplicate, or
555
+ * incompatible regular expression flags.
556
+ * @since 1.3.0
557
+ */
558
+ declare function regExp(options?: RegExpOptions): ValueParser<"sync", RegExp>;
501
559
  interface KeyValueOptionsBase {
502
560
  /**
503
561
  * The metavariable name for this parser. Used in help messages to
@@ -3279,4 +3337,4 @@ declare function firstOf<const TParsers extends readonly [ValueParser<"sync", un
3279
3337
  */
3280
3338
  declare function firstOf<const TParsers extends readonly ValueParser<"sync", unknown>[]>(parsers: TParsers, options?: FirstOfOptions): ValueParser<"sync", ValueParserValue<TParsers[number]>>;
3281
3339
  //#endregion
3282
- export { ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, CidrOptions, CidrValue, Color, ColorFormat, ColorOptions, CronExpression, CronExpressionForOptions, CronOptions, DeferredMap, DomainOptions, EmailOptions, FileSizeOptions, FileSizeOptionsBigInt, FileSizeOptionsNumber, FileSizeUnit, FirstOfOptions, FloatOptions, HostnameOptions, IntegerOptionsBigInt, IntegerOptionsNumber, IpOptions, Ipv4Options, Ipv6Options, Json, JsonOptions, KeyValueOptions, LocaleOptions, MacAddressOptions, type Mode, type ModeIterable, type ModeValue, type NonEmptyString, PortOptionsBigInt, PortOptionsNumber, PortRangeOptionsBigInt, PortRangeOptionsNumber, PortRangeValueBigInt, PortRangeValueNumber, SemVer, SemVerOptionsObject, SemVerOptionsString, SemVerString, SocketAddressOptions, SocketAddressValue, StringOptions, TransformMapping, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, biject, checkBooleanOption, checkEnumOption, choice, cidr, color, cron, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, keyValue, locale, macAddress, port, portRange, semVer, socketAddress, string, transform, url, uuid };
3340
+ export { ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, CidrOptions, CidrValue, Color, ColorFormat, ColorOptions, CronExpression, CronExpressionForOptions, CronOptions, DeferredMap, DomainOptions, EmailOptions, FileSizeOptions, FileSizeOptionsBigInt, FileSizeOptionsNumber, FileSizeUnit, FirstOfOptions, FloatOptions, HostnameOptions, IntegerOptionsBigInt, IntegerOptionsNumber, IpOptions, Ipv4Options, Ipv6Options, Json, JsonOptions, KeyValueOptions, LocaleOptions, MacAddressOptions, type Mode, type ModeIterable, type ModeValue, type NonEmptyString, PortOptionsBigInt, PortOptionsNumber, PortRangeOptionsBigInt, PortRangeOptionsNumber, PortRangeValueBigInt, PortRangeValueNumber, RegExpOptions, SemVer, SemVerOptionsObject, SemVerOptionsString, SemVerString, SocketAddressOptions, SocketAddressValue, StringOptions, TransformMapping, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, biject, checkBooleanOption, checkEnumOption, choice, cidr, color, cron, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, keyValue, locale, macAddress, port, portRange, regExp, semVer, socketAddress, string, transform, url, uuid };
@@ -620,6 +620,71 @@ function string(options = {}) {
620
620
  }
621
621
  };
622
622
  }
623
+ /**
624
+ * Creates a {@link ValueParser} that compiles regular expression sources.
625
+ *
626
+ * The entire input is treated as the source. Slash-delimited notation such
627
+ * as `/pattern/flags` is not interpreted; use {@link RegExpOptions.flags} to
628
+ * configure fixed flags for the parser.
629
+ *
630
+ * **Security note**: Compiling a source does not establish that it is safe to
631
+ * execute. Patterns from untrusted input can cause Regular Expression Denial
632
+ * of Service (ReDoS) when matched. Limit pattern and subject lengths, execute
633
+ * matches in an environment that can be terminated, or use a linear-time
634
+ * regular expression engine when accepting untrusted patterns.
635
+ *
636
+ * @param options Configuration options for the regular expression parser.
637
+ * @returns A sync value parser producing JavaScript {@link RegExp} objects.
638
+ * @throws {TypeError} If `options.metavar` is an empty string or
639
+ * `options.flags` is not a string.
640
+ * @throws {SyntaxError} If `options.flags` contains invalid, duplicate, or
641
+ * incompatible regular expression flags.
642
+ * @since 1.3.0
643
+ */
644
+ function regExp(options = {}) {
645
+ const metavar$1 = options.metavar ?? "REGEXP";
646
+ ensureNonEmptyString(metavar$1);
647
+ if (options.flags !== void 0 && typeof options.flags !== "string") throw new TypeError(`Expected flags to be a string, but got ${typeof options.flags}: ${String(options.flags)}.`);
648
+ const flags = new RegExp("", options.flags ?? "").flags;
649
+ const invalidRegExp = options.errors?.invalidRegExp;
650
+ const parseRegExp = (input) => {
651
+ try {
652
+ return {
653
+ success: true,
654
+ value: new RegExp(input, flags)
655
+ };
656
+ } catch (error) {
657
+ if (!(error instanceof SyntaxError)) throw error;
658
+ return {
659
+ success: false,
660
+ error: invalidRegExp ? typeof invalidRegExp === "function" ? invalidRegExp(input) : invalidRegExp : message`Invalid regular expression: ${input}.`
661
+ };
662
+ }
663
+ };
664
+ return {
665
+ mode: "sync",
666
+ metavar: metavar$1,
667
+ get placeholder() {
668
+ return new RegExp("", flags);
669
+ },
670
+ parse: parseRegExp,
671
+ validate(value) {
672
+ if (!(value instanceof RegExp)) return {
673
+ success: false,
674
+ error: message`Expected a RegExp value.`
675
+ };
676
+ return parseRegExp(value.source);
677
+ },
678
+ normalize(value) {
679
+ if (!(value instanceof RegExp)) return value;
680
+ const result = parseRegExp(value.source);
681
+ return result.success ? result.value : value;
682
+ },
683
+ format(value) {
684
+ return value.source;
685
+ }
686
+ };
687
+ }
623
688
  function keyValue(options = {}) {
624
689
  const separator = options.separator ?? "=";
625
690
  if (typeof separator !== "string") throw new TypeError(`Expected separator to be a string, but got ${typeof separator}: ${String(separator)}.`);
@@ -6602,4 +6667,4 @@ function plainObjectsEqual(a, b) {
6602
6667
  }
6603
6668
 
6604
6669
  //#endregion
6605
- export { biject, checkBooleanOption, checkEnumOption, choice, cidr, color, cron, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, keyValue, locale, macAddress, port, portRange, semVer, socketAddress, string, transform, url, uuid };
6670
+ export { biject, checkBooleanOption, checkEnumOption, choice, cidr, color, cron, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, keyValue, locale, macAddress, port, portRange, regExp, semVer, socketAddress, string, transform, url, uuid };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.3.0-dev.2366",
3
+ "version": "1.3.0-dev.2375",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",
@@ -221,7 +221,7 @@
221
221
  },
222
222
  "sideEffects": false,
223
223
  "devDependencies": {
224
- "@optique/env": "1.3.0-dev.2366+c83510d8",
224
+ "@optique/env": "1.3.0-dev.2375+820d01c0",
225
225
  "@types/node": "^24.0.0",
226
226
  "fast-check": "^4.7.0",
227
227
  "tsdown": "^0.13.0",
@@ -40,11 +40,12 @@ Core rules
40
40
  - Use `message` from *@optique/core/message* for descriptions, help text, and
41
41
  custom errors. Prefer semantic message helpers such as `optionName()` and
42
42
  `metavar()` over string concatenation when naming CLI elements.
43
- - Use value parsers such as `integer()`, `choice()`, `biject()`, `url()`,
44
- and `uuid()` instead of validating raw strings after parsing. Use
45
- `biject()` for one-to-one string-to-value choices, and use `transform()`
46
- when an existing value parser describes the accepted CLI spelling but your
47
- app needs a different result type. Use `path()` from
43
+ - Use value parsers such as `integer()`, `choice()`, `biject()`, `regExp()`,
44
+ `url()`, and `uuid()` instead of validating raw strings after parsing. Use
45
+ `regExp({ flags })` for user-supplied regular expression sources,
46
+ `biject()` for one-to-one string-to-value choices, and `transform()` when
47
+ an existing value parser describes the accepted CLI spelling but your app
48
+ needs a different result type. Use `path()` from
48
49
  `@optique/run/valueparser` for file-system paths. Write a custom
49
50
  `{ mode, metavar, parse, format }` value parser only when the catalog does
50
51
  not cover the domain.