@optique/core 0.5.0-dev.79 → 0.5.0-dev.80

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/parser.js CHANGED
@@ -1,1497 +1,11 @@
1
- import { formatMessage, message, metavar, optionName, optionNames, text, values } from "./message.js";
1
+ import { message } from "./message.js";
2
+ import { concat, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
2
3
  import { normalizeUsage } from "./usage.js";
3
- import { isValueParser } from "./valueparser.js";
4
+ import { WithDefaultError, map, multiple, optional, withDefault } from "./modifiers.js";
5
+ import { argument, command, constant, flag, option } from "./primitives.js";
4
6
 
5
7
  //#region src/parser.ts
6
8
  /**
7
- * Creates a parser that always succeeds without consuming any input and
8
- * produces a constant value of the type {@link T}.
9
- * @template T The type of the constant value produced by the parser.
10
- */
11
- function constant(value) {
12
- return {
13
- $valueType: [],
14
- $stateType: [],
15
- priority: 0,
16
- usage: [],
17
- initialState: value,
18
- parse(context) {
19
- return {
20
- success: true,
21
- next: context,
22
- consumed: []
23
- };
24
- },
25
- complete(state) {
26
- return {
27
- success: true,
28
- value: state
29
- };
30
- },
31
- getDocFragments(_state, _defaultValue) {
32
- return { fragments: [] };
33
- }
34
- };
35
- }
36
- function option(...args) {
37
- const lastArg = args.at(-1);
38
- const secondLastArg = args.at(-2);
39
- let valueParser;
40
- let optionNames$1;
41
- let options = {};
42
- if (isValueParser(lastArg)) {
43
- valueParser = lastArg;
44
- optionNames$1 = args.slice(0, -1);
45
- } else if (typeof lastArg === "object" && lastArg != null) {
46
- options = lastArg;
47
- if (isValueParser(secondLastArg)) {
48
- valueParser = secondLastArg;
49
- optionNames$1 = args.slice(0, -2);
50
- } else {
51
- valueParser = void 0;
52
- optionNames$1 = args.slice(0, -1);
53
- }
54
- } else {
55
- optionNames$1 = args;
56
- valueParser = void 0;
57
- }
58
- return {
59
- $valueType: [],
60
- $stateType: [],
61
- priority: 10,
62
- usage: [valueParser == null ? {
63
- type: "optional",
64
- terms: [{
65
- type: "option",
66
- names: optionNames$1
67
- }]
68
- } : {
69
- type: "option",
70
- names: optionNames$1,
71
- metavar: valueParser.metavar
72
- }],
73
- initialState: valueParser == null ? {
74
- success: true,
75
- value: false
76
- } : {
77
- success: false,
78
- error: message`Missing option ${optionNames(optionNames$1)}.`
79
- },
80
- parse(context) {
81
- if (context.optionsTerminated) return {
82
- success: false,
83
- consumed: 0,
84
- error: message`No more options can be parsed.`
85
- };
86
- else if (context.buffer.length < 1) return {
87
- success: false,
88
- consumed: 0,
89
- error: message`Expected an option, but got end of input.`
90
- };
91
- if (context.buffer[0] === "--") return {
92
- success: true,
93
- next: {
94
- ...context,
95
- buffer: context.buffer.slice(1),
96
- state: context.state,
97
- optionsTerminated: true
98
- },
99
- consumed: context.buffer.slice(0, 1)
100
- };
101
- if (optionNames$1.includes(context.buffer[0])) {
102
- if (context.state.success && (valueParser != null || context.state.value)) return {
103
- success: false,
104
- consumed: 1,
105
- error: message`${context.buffer[0]} cannot be used multiple times.`
106
- };
107
- if (valueParser == null) return {
108
- success: true,
109
- next: {
110
- ...context,
111
- state: {
112
- success: true,
113
- value: true
114
- },
115
- buffer: context.buffer.slice(1)
116
- },
117
- consumed: context.buffer.slice(0, 1)
118
- };
119
- if (context.buffer.length < 2) return {
120
- success: false,
121
- consumed: 1,
122
- error: message`Option ${optionName(context.buffer[0])} requires a value, but got no value.`
123
- };
124
- const result = valueParser.parse(context.buffer[1]);
125
- return {
126
- success: true,
127
- next: {
128
- ...context,
129
- state: result,
130
- buffer: context.buffer.slice(2)
131
- },
132
- consumed: context.buffer.slice(0, 2)
133
- };
134
- }
135
- const prefixes = optionNames$1.filter((name) => name.startsWith("--") || name.startsWith("/")).map((name) => name.startsWith("/") ? `${name}:` : `${name}=`);
136
- for (const prefix of prefixes) {
137
- if (!context.buffer[0].startsWith(prefix)) continue;
138
- if (context.state.success && (valueParser != null || context.state.value)) return {
139
- success: false,
140
- consumed: 1,
141
- error: message`${optionName(prefix)} cannot be used multiple times.`
142
- };
143
- const value = context.buffer[0].slice(prefix.length);
144
- if (valueParser == null) return {
145
- success: false,
146
- consumed: 1,
147
- error: message`Option ${optionName(prefix)} is a Boolean flag, but got a value: ${value}.`
148
- };
149
- const result = valueParser.parse(value);
150
- return {
151
- success: true,
152
- next: {
153
- ...context,
154
- state: result,
155
- buffer: context.buffer.slice(1)
156
- },
157
- consumed: context.buffer.slice(0, 1)
158
- };
159
- }
160
- if (valueParser == null) {
161
- const shortOptions = optionNames$1.filter((name) => name.match(/^-[^-]$/));
162
- for (const shortOption of shortOptions) {
163
- if (!context.buffer[0].startsWith(shortOption)) continue;
164
- if (context.state.success && (valueParser != null || context.state.value)) return {
165
- success: false,
166
- consumed: 1,
167
- error: message`${optionName(shortOption)} cannot be used multiple times.`
168
- };
169
- return {
170
- success: true,
171
- next: {
172
- ...context,
173
- state: {
174
- success: true,
175
- value: true
176
- },
177
- buffer: [`-${context.buffer[0].slice(2)}`, ...context.buffer.slice(1)]
178
- },
179
- consumed: [context.buffer[0].slice(0, 2)]
180
- };
181
- }
182
- }
183
- return {
184
- success: false,
185
- consumed: 0,
186
- error: message`No matched option for ${optionName(context.buffer[0])}.`
187
- };
188
- },
189
- complete(state) {
190
- if (state == null) return valueParser == null ? {
191
- success: true,
192
- value: false
193
- } : {
194
- success: false,
195
- error: message`Missing option ${optionNames(optionNames$1)}.`
196
- };
197
- if (state.success) return state;
198
- return {
199
- success: false,
200
- error: message`${optionNames(optionNames$1)}: ${state.error}`
201
- };
202
- },
203
- getDocFragments(_state, defaultValue) {
204
- const fragments = [{
205
- type: "entry",
206
- term: {
207
- type: "option",
208
- names: optionNames$1,
209
- metavar: valueParser?.metavar
210
- },
211
- description: options.description,
212
- default: defaultValue != null && valueParser != null ? message`${valueParser.format(defaultValue)}` : void 0
213
- }];
214
- return {
215
- fragments,
216
- description: options.description
217
- };
218
- },
219
- [Symbol.for("Deno.customInspect")]() {
220
- return `option(${optionNames$1.map((o) => JSON.stringify(o)).join(", ")})`;
221
- }
222
- };
223
- }
224
- /**
225
- * Creates a parser for command-line flags that must be explicitly provided.
226
- * Unlike {@link option}, this parser fails if the flag is not present, making
227
- * it suitable for required boolean flags that don't have a meaningful default.
228
- *
229
- * The key difference from {@link option} is:
230
- * - {@link option} without a value parser: Returns `false` when not present
231
- * - {@link flag}: Fails parsing when not present, only produces `true`
232
- *
233
- * This is useful for dependent options where the presence of a flag changes
234
- * the shape of the result type.
235
- *
236
- * @param args The {@link OptionName}s to parse, followed by an optional
237
- * {@link FlagOptions} object that allows you to specify
238
- * a description or other metadata.
239
- * @returns A {@link Parser} that produces `true` when the flag is present
240
- * and fails when it is not present.
241
- *
242
- * @example
243
- * ```typescript
244
- * // Basic flag usage
245
- * const parser = flag("-f", "--force");
246
- * // Succeeds with true: parse(parser, ["-f"])
247
- * // Fails: parse(parser, [])
248
- *
249
- * // With description
250
- * const verboseFlag = flag("-v", "--verbose", {
251
- * description: "Enable verbose output"
252
- * });
253
- * ```
254
- */
255
- function flag(...args) {
256
- const lastArg = args.at(-1);
257
- let optionNames$1;
258
- let options = {};
259
- if (typeof lastArg === "object" && lastArg != null && !Array.isArray(lastArg)) {
260
- options = lastArg;
261
- optionNames$1 = args.slice(0, -1);
262
- } else optionNames$1 = args;
263
- return {
264
- $valueType: [],
265
- $stateType: [],
266
- priority: 10,
267
- usage: [{
268
- type: "option",
269
- names: optionNames$1
270
- }],
271
- initialState: void 0,
272
- parse(context) {
273
- if (context.optionsTerminated) return {
274
- success: false,
275
- consumed: 0,
276
- error: message`No more options can be parsed.`
277
- };
278
- else if (context.buffer.length < 1) return {
279
- success: false,
280
- consumed: 0,
281
- error: message`Expected an option, but got end of input.`
282
- };
283
- if (context.buffer[0] === "--") return {
284
- success: true,
285
- next: {
286
- ...context,
287
- buffer: context.buffer.slice(1),
288
- state: context.state,
289
- optionsTerminated: true
290
- },
291
- consumed: context.buffer.slice(0, 1)
292
- };
293
- if (optionNames$1.includes(context.buffer[0])) {
294
- if (context.state?.success) return {
295
- success: false,
296
- consumed: 1,
297
- error: message`${optionName(context.buffer[0])} cannot be used multiple times.`
298
- };
299
- return {
300
- success: true,
301
- next: {
302
- ...context,
303
- state: {
304
- success: true,
305
- value: true
306
- },
307
- buffer: context.buffer.slice(1)
308
- },
309
- consumed: context.buffer.slice(0, 1)
310
- };
311
- }
312
- const prefixes = optionNames$1.filter((name) => name.startsWith("--") || name.startsWith("/")).map((name) => name.startsWith("/") ? `${name}:` : `${name}=`);
313
- for (const prefix of prefixes) if (context.buffer[0].startsWith(prefix)) {
314
- const value = context.buffer[0].slice(prefix.length);
315
- return {
316
- success: false,
317
- consumed: 1,
318
- error: message`Flag ${optionName(prefix.slice(0, -1))} does not accept a value, but got: ${value}.`
319
- };
320
- }
321
- const shortOptions = optionNames$1.filter((name) => name.match(/^-[^-]$/));
322
- for (const shortOption of shortOptions) {
323
- if (!context.buffer[0].startsWith(shortOption)) continue;
324
- if (context.state?.success) return {
325
- success: false,
326
- consumed: 1,
327
- error: message`${optionName(shortOption)} cannot be used multiple times.`
328
- };
329
- return {
330
- success: true,
331
- next: {
332
- ...context,
333
- state: {
334
- success: true,
335
- value: true
336
- },
337
- buffer: [`-${context.buffer[0].slice(2)}`, ...context.buffer.slice(1)]
338
- },
339
- consumed: [context.buffer[0].slice(0, 2)]
340
- };
341
- }
342
- return {
343
- success: false,
344
- consumed: 0,
345
- error: message`No matched option for ${optionName(context.buffer[0])}.`
346
- };
347
- },
348
- complete(state) {
349
- if (state == null) return {
350
- success: false,
351
- error: message`Required flag ${optionNames(optionNames$1)} is missing.`
352
- };
353
- if (state.success) return {
354
- success: true,
355
- value: true
356
- };
357
- return {
358
- success: false,
359
- error: message`${optionNames(optionNames$1)}: ${state.error}`
360
- };
361
- },
362
- getDocFragments(_state, _defaultValue) {
363
- const fragments = [{
364
- type: "entry",
365
- term: {
366
- type: "option",
367
- names: optionNames$1
368
- },
369
- description: options.description
370
- }];
371
- return {
372
- fragments,
373
- description: options.description
374
- };
375
- },
376
- [Symbol.for("Deno.customInspect")]() {
377
- return `flag(${optionNames$1.map((o) => JSON.stringify(o)).join(", ")})`;
378
- }
379
- };
380
- }
381
- /**
382
- * Creates a parser that expects a single argument value.
383
- * This parser is typically used for positional arguments
384
- * that are not options or flags.
385
- * @template T The type of the value produced by the parser.
386
- * @param valueParser The {@link ValueParser} that defines how to parse
387
- * the argument value.
388
- * @param options Optional configuration for the argument parser,
389
- * allowing you to specify a description or other metadata.
390
- * @returns A {@link Parser} that expects a single argument value and produces
391
- * the parsed value of type {@link T}.
392
- */
393
- function argument(valueParser, options = {}) {
394
- const optionPattern = /^--?[a-z0-9-]+$/i;
395
- const term = {
396
- type: "argument",
397
- metavar: valueParser.metavar
398
- };
399
- return {
400
- $valueType: [],
401
- $stateType: [],
402
- priority: 5,
403
- usage: [term],
404
- initialState: void 0,
405
- parse(context) {
406
- if (context.buffer.length < 1) return {
407
- success: false,
408
- consumed: 0,
409
- error: message`Expected an argument, but got end of input.`
410
- };
411
- let i = 0;
412
- let optionsTerminated = context.optionsTerminated;
413
- if (!optionsTerminated) {
414
- if (context.buffer[i] === "--") {
415
- optionsTerminated = true;
416
- i++;
417
- } else if (context.buffer[i].match(optionPattern)) return {
418
- success: false,
419
- consumed: i,
420
- error: message`Expected an argument, but got an option: ${optionName(context.buffer[i])}.`
421
- };
422
- }
423
- if (context.buffer.length < i + 1) return {
424
- success: false,
425
- consumed: i,
426
- error: message`Expected an argument, but got end of input.`
427
- };
428
- if (context.state != null) return {
429
- success: false,
430
- consumed: i,
431
- error: message`The argument ${metavar(valueParser.metavar)} cannot be used multiple times.`
432
- };
433
- const result = valueParser.parse(context.buffer[i]);
434
- return {
435
- success: true,
436
- next: {
437
- ...context,
438
- buffer: context.buffer.slice(i + 1),
439
- state: result,
440
- optionsTerminated
441
- },
442
- consumed: context.buffer.slice(0, i + 1)
443
- };
444
- },
445
- complete(state) {
446
- if (state == null) return {
447
- success: false,
448
- error: message`Expected a ${metavar(valueParser.metavar)}, but too few arguments.`
449
- };
450
- else if (state.success) return state;
451
- return {
452
- success: false,
453
- error: message`${metavar(valueParser.metavar)}: ${state.error}`
454
- };
455
- },
456
- getDocFragments(_state, defaultValue) {
457
- const fragments = [{
458
- type: "entry",
459
- term,
460
- description: options.description,
461
- default: defaultValue == null ? void 0 : message`${valueParser.format(defaultValue)}`
462
- }];
463
- return {
464
- fragments,
465
- description: options.description
466
- };
467
- },
468
- [Symbol.for("Deno.customInspect")]() {
469
- return `argument()`;
470
- }
471
- };
472
- }
473
- /**
474
- * Creates a parser that makes another parser optional, allowing it to succeed
475
- * without consuming input if the wrapped parser fails to match.
476
- * If the wrapped parser succeeds, this returns its value.
477
- * If the wrapped parser fails, this returns `undefined` without consuming input.
478
- * @template TValue The type of the value returned by the wrapped parser.
479
- * @template TState The type of the state used by the wrapped parser.
480
- * @param parser The {@link Parser} to make optional.
481
- * @returns A {@link Parser} that produces either the result of the wrapped parser
482
- * or `undefined` if the wrapped parser fails to match.
483
- */
484
- function optional(parser) {
485
- return {
486
- $valueType: [],
487
- $stateType: [],
488
- priority: parser.priority,
489
- usage: [{
490
- type: "optional",
491
- terms: parser.usage
492
- }],
493
- initialState: void 0,
494
- parse(context) {
495
- const result = parser.parse({
496
- ...context,
497
- state: typeof context.state === "undefined" ? parser.initialState : context.state[0]
498
- });
499
- if (result.success) return {
500
- success: true,
501
- next: {
502
- ...result.next,
503
- state: [result.next.state]
504
- },
505
- consumed: result.consumed
506
- };
507
- return result;
508
- },
509
- complete(state) {
510
- if (typeof state === "undefined") return {
511
- success: true,
512
- value: void 0
513
- };
514
- return parser.complete(state[0]);
515
- },
516
- getDocFragments(state, defaultValue) {
517
- const innerState = state.kind === "unavailable" ? { kind: "unavailable" } : state.state === void 0 ? { kind: "unavailable" } : {
518
- kind: "available",
519
- state: state.state[0]
520
- };
521
- return parser.getDocFragments(innerState, defaultValue);
522
- }
523
- };
524
- }
525
- /**
526
- * Error type for structured error messages in {@link withDefault} default value callbacks.
527
- * Unlike regular errors that only support string messages, this error type accepts
528
- * a {@link Message} object that supports rich formatting, colors, and structured content.
529
- *
530
- * @example
531
- * ```typescript
532
- * withDefault(option("--url", url()), () => {
533
- * if (!process.env.INSTANCE_URL) {
534
- * throw new WithDefaultError(
535
- * message`Environment variable ${envVar("INSTANCE_URL")} is not set.`
536
- * );
537
- * }
538
- * return new URL(process.env.INSTANCE_URL);
539
- * })
540
- * ```
541
- *
542
- * @since 0.5.0
543
- */
544
- var WithDefaultError = class extends Error {
545
- /**
546
- * The structured message associated with this error.
547
- */
548
- errorMessage;
549
- /**
550
- * Creates a new WithDefaultError with a structured message.
551
- * @param message The structured {@link Message} describing the error.
552
- */
553
- constructor(message$1) {
554
- super(formatMessage(message$1));
555
- this.errorMessage = message$1;
556
- this.name = "WithDefaultError";
557
- }
558
- };
559
- function withDefault(parser, defaultValue, options) {
560
- return {
561
- $valueType: [],
562
- $stateType: [],
563
- priority: parser.priority,
564
- usage: [{
565
- type: "optional",
566
- terms: parser.usage
567
- }],
568
- initialState: void 0,
569
- parse(context) {
570
- const result = parser.parse({
571
- ...context,
572
- state: typeof context.state === "undefined" ? parser.initialState : context.state[0]
573
- });
574
- if (result.success) return {
575
- success: true,
576
- next: {
577
- ...result.next,
578
- state: [result.next.state]
579
- },
580
- consumed: result.consumed
581
- };
582
- return result;
583
- },
584
- complete(state) {
585
- if (typeof state === "undefined") try {
586
- const value = typeof defaultValue === "function" ? defaultValue() : defaultValue;
587
- return {
588
- success: true,
589
- value
590
- };
591
- } catch (error) {
592
- return {
593
- success: false,
594
- error: error instanceof WithDefaultError ? error.errorMessage : message`${text(String(error))}`
595
- };
596
- }
597
- return parser.complete(state[0]);
598
- },
599
- getDocFragments(state, upperDefaultValue) {
600
- const innerState = state.kind === "unavailable" ? { kind: "unavailable" } : state.state === void 0 ? { kind: "unavailable" } : {
601
- kind: "available",
602
- state: state.state[0]
603
- };
604
- const actualDefaultValue = upperDefaultValue != null ? upperDefaultValue : typeof defaultValue === "function" ? defaultValue() : defaultValue;
605
- const fragments = parser.getDocFragments(innerState, actualDefaultValue);
606
- if (options?.message) {
607
- const modifiedFragments = fragments.fragments.map((fragment) => {
608
- if (fragment.type === "entry") return {
609
- ...fragment,
610
- default: options.message
611
- };
612
- return fragment;
613
- });
614
- return {
615
- ...fragments,
616
- fragments: modifiedFragments
617
- };
618
- }
619
- return fragments;
620
- }
621
- };
622
- }
623
- /**
624
- * Creates a parser that transforms the result value of another parser using
625
- * a mapping function. This enables value transformation while preserving
626
- * the original parser's parsing logic and state management.
627
- *
628
- * The `map()` function is useful for:
629
- * - Converting parsed values to different types
630
- * - Applying transformations like string formatting or boolean inversion
631
- * - Computing derived values from parsed input
632
- * - Creating reusable transformations that can be applied to any parser
633
- *
634
- * @template T The type of the value produced by the original parser.
635
- * @template U The type of the value produced by the mapping function.
636
- * @template TState The type of the state used by the original parser.
637
- * @param parser The {@link Parser} whose result will be transformed.
638
- * @param transform A function that transforms the parsed value from type T to type U.
639
- * @returns A {@link Parser} that produces the transformed value of type U
640
- * while preserving the original parser's state type and parsing behavior.
641
- *
642
- * @example
643
- * ```typescript
644
- * // Transform boolean flag to its inverse
645
- * const parser = object({
646
- * disallow: map(option("--allow"), b => !b)
647
- * });
648
- *
649
- * // Transform string to uppercase
650
- * const upperParser = map(argument(string()), s => s.toUpperCase());
651
- *
652
- * // Transform number to formatted string
653
- * const prefixedParser = map(option("-n", integer()), n => `value: ${n}`);
654
- * ```
655
- */
656
- function map(parser, transform) {
657
- return {
658
- $valueType: [],
659
- $stateType: parser.$stateType,
660
- priority: parser.priority,
661
- usage: parser.usage,
662
- initialState: parser.initialState,
663
- parse: parser.parse.bind(parser),
664
- complete(state) {
665
- const result = parser.complete(state);
666
- if (result.success) return {
667
- success: true,
668
- value: transform(result.value)
669
- };
670
- return result;
671
- },
672
- getDocFragments(state, _defaultValue) {
673
- return parser.getDocFragments(state, void 0);
674
- }
675
- };
676
- }
677
- /**
678
- * Creates a parser that allows multiple occurrences of a given parser.
679
- * This parser can be used to parse multiple values of the same type,
680
- * such as multiple command-line arguments or options.
681
- * @template TValue The type of the value that the parser produces.
682
- * @template TState The type of the state used by the parser.
683
- * @param parser The {@link Parser} to apply multiple times.
684
- * @param options Optional configuration for the parser,
685
- * allowing you to specify the minimum and maximum number of
686
- * occurrences allowed.
687
- * @returns A {@link Parser} that produces an array of values
688
- * of type {@link TValue} and an array of states
689
- * of type {@link TState}.
690
- */
691
- function multiple(parser, options = {}) {
692
- const { min = 0, max = Infinity } = options;
693
- return {
694
- $valueType: [],
695
- $stateType: [],
696
- priority: parser.priority,
697
- usage: [{
698
- type: "multiple",
699
- terms: parser.usage,
700
- min
701
- }],
702
- initialState: [],
703
- parse(context) {
704
- let added = context.state.length < 1;
705
- let result = parser.parse({
706
- ...context,
707
- state: context.state.at(-1) ?? parser.initialState
708
- });
709
- if (!result.success) if (!added) {
710
- result = parser.parse({
711
- ...context,
712
- state: parser.initialState
713
- });
714
- if (!result.success) return result;
715
- added = true;
716
- } else return result;
717
- return {
718
- success: true,
719
- next: {
720
- ...result.next,
721
- state: [...added ? context.state : context.state.slice(0, -1), result.next.state]
722
- },
723
- consumed: result.consumed
724
- };
725
- },
726
- complete(state) {
727
- const result = [];
728
- for (const s of state) {
729
- const valueResult = parser.complete(s);
730
- if (valueResult.success) result.push(valueResult.value);
731
- else return {
732
- success: false,
733
- error: valueResult.error
734
- };
735
- }
736
- if (result.length < min) return {
737
- success: false,
738
- error: message`Expected at least ${text(min.toLocaleString("en"))} values, but got only ${text(result.length.toLocaleString("en"))}.`
739
- };
740
- else if (result.length > max) return {
741
- success: false,
742
- error: message`Expected at most ${text(max.toLocaleString("en"))} values, but got ${text(result.length.toLocaleString("en"))}.`
743
- };
744
- return {
745
- success: true,
746
- value: result
747
- };
748
- },
749
- getDocFragments(state, defaultValue) {
750
- const innerState = state.kind === "unavailable" ? { kind: "unavailable" } : state.state.length > 0 ? {
751
- kind: "available",
752
- state: state.state.at(-1)
753
- } : { kind: "unavailable" };
754
- return parser.getDocFragments(innerState, defaultValue != null && defaultValue.length > 0 ? defaultValue[0] : void 0);
755
- }
756
- };
757
- }
758
- function object(labelOrParsers, maybeParsers) {
759
- const label = typeof labelOrParsers === "string" ? labelOrParsers : void 0;
760
- const parsers = typeof labelOrParsers === "string" ? maybeParsers : labelOrParsers;
761
- const parserPairs = Object.entries(parsers);
762
- parserPairs.sort(([_, parserA], [__, parserB]) => parserB.priority - parserA.priority);
763
- return {
764
- $valueType: [],
765
- $stateType: [],
766
- priority: Math.max(...Object.values(parsers).map((p) => p.priority)),
767
- usage: parserPairs.flatMap(([_, p]) => p.usage),
768
- initialState: Object.fromEntries(Object.entries(parsers).map(([key, parser]) => [key, parser.initialState])),
769
- parse(context) {
770
- let error = {
771
- consumed: 0,
772
- error: context.buffer.length > 0 ? message`Unexpected option or argument: ${context.buffer[0]}.` : message`Expected an option or argument, but got end of input.`
773
- };
774
- let currentContext = context;
775
- let anySuccess = false;
776
- const allConsumed = [];
777
- let madeProgress = true;
778
- while (madeProgress && currentContext.buffer.length > 0) {
779
- madeProgress = false;
780
- for (const [field, parser] of parserPairs) {
781
- const result = parser.parse({
782
- ...currentContext,
783
- state: currentContext.state && typeof currentContext.state === "object" && field in currentContext.state ? currentContext.state[field] : parser.initialState
784
- });
785
- if (result.success && result.consumed.length > 0) {
786
- currentContext = {
787
- ...currentContext,
788
- buffer: result.next.buffer,
789
- optionsTerminated: result.next.optionsTerminated,
790
- state: {
791
- ...currentContext.state,
792
- [field]: result.next.state
793
- }
794
- };
795
- allConsumed.push(...result.consumed);
796
- anySuccess = true;
797
- madeProgress = true;
798
- break;
799
- } else if (!result.success && error.consumed < result.consumed) error = result;
800
- }
801
- }
802
- if (anySuccess) return {
803
- success: true,
804
- next: currentContext,
805
- consumed: allConsumed
806
- };
807
- if (context.buffer.length === 0) {
808
- let allCanComplete = true;
809
- for (const [field, parser] of parserPairs) {
810
- const fieldState = context.state && typeof context.state === "object" && field in context.state ? context.state[field] : parser.initialState;
811
- const completeResult = parser.complete(fieldState);
812
- if (!completeResult.success) {
813
- allCanComplete = false;
814
- break;
815
- }
816
- }
817
- if (allCanComplete) return {
818
- success: true,
819
- next: context,
820
- consumed: []
821
- };
822
- }
823
- return {
824
- ...error,
825
- success: false
826
- };
827
- },
828
- complete(state) {
829
- const result = {};
830
- for (const field in state) {
831
- if (!(field in parsers)) continue;
832
- const valueResult = parsers[field].complete(state[field]);
833
- if (valueResult.success) result[field] = valueResult.value;
834
- else return {
835
- success: false,
836
- error: valueResult.error
837
- };
838
- }
839
- return {
840
- success: true,
841
- value: result
842
- };
843
- },
844
- getDocFragments(state, defaultValue) {
845
- const fragments = parserPairs.flatMap(([field, p]) => {
846
- const fieldState = state.kind === "unavailable" ? { kind: "unavailable" } : {
847
- kind: "available",
848
- state: state.state[field]
849
- };
850
- return p.getDocFragments(fieldState, defaultValue?.[field]).fragments;
851
- });
852
- const entries = fragments.filter((d) => d.type === "entry");
853
- const sections = [];
854
- for (const fragment of fragments) {
855
- if (fragment.type !== "section") continue;
856
- if (fragment.title == null) entries.push(...fragment.entries);
857
- else sections.push(fragment);
858
- }
859
- const section = {
860
- title: label,
861
- entries
862
- };
863
- sections.push(section);
864
- return { fragments: sections.map((s) => ({
865
- ...s,
866
- type: "section"
867
- })) };
868
- }
869
- };
870
- }
871
- function tuple(labelOrParsers, maybeParsers) {
872
- const label = typeof labelOrParsers === "string" ? labelOrParsers : void 0;
873
- const parsers = typeof labelOrParsers === "string" ? maybeParsers : labelOrParsers;
874
- return {
875
- $valueType: [],
876
- $stateType: [],
877
- usage: parsers.toSorted((a, b) => b.priority - a.priority).flatMap((p) => p.usage),
878
- priority: parsers.length > 0 ? Math.max(...parsers.map((p) => p.priority)) : 0,
879
- initialState: parsers.map((parser) => parser.initialState),
880
- parse(context) {
881
- let currentContext = context;
882
- const allConsumed = [];
883
- const matchedParsers = /* @__PURE__ */ new Set();
884
- while (matchedParsers.size < parsers.length) {
885
- let foundMatch = false;
886
- let error = {
887
- consumed: 0,
888
- error: message`No remaining parsers could match the input.`
889
- };
890
- const remainingParsers = parsers.map((parser, index) => [parser, index]).filter(([_, index]) => !matchedParsers.has(index)).sort(([parserA], [parserB]) => parserB.priority - parserA.priority);
891
- for (const [parser, index] of remainingParsers) {
892
- const result = parser.parse({
893
- ...currentContext,
894
- state: currentContext.state[index]
895
- });
896
- if (result.success && result.consumed.length > 0) {
897
- currentContext = {
898
- ...currentContext,
899
- buffer: result.next.buffer,
900
- optionsTerminated: result.next.optionsTerminated,
901
- state: currentContext.state.map((s, idx) => idx === index ? result.next.state : s)
902
- };
903
- allConsumed.push(...result.consumed);
904
- matchedParsers.add(index);
905
- foundMatch = true;
906
- break;
907
- } else if (!result.success && error.consumed < result.consumed) error = result;
908
- }
909
- if (!foundMatch) for (const [parser, index] of remainingParsers) {
910
- const result = parser.parse({
911
- ...currentContext,
912
- state: currentContext.state[index]
913
- });
914
- if (result.success && result.consumed.length < 1) {
915
- currentContext = {
916
- ...currentContext,
917
- state: currentContext.state.map((s, idx) => idx === index ? result.next.state : s)
918
- };
919
- matchedParsers.add(index);
920
- foundMatch = true;
921
- break;
922
- } else if (!result.success && result.consumed < 1) {
923
- matchedParsers.add(index);
924
- foundMatch = true;
925
- break;
926
- }
927
- }
928
- if (!foundMatch) return {
929
- ...error,
930
- success: false
931
- };
932
- }
933
- return {
934
- success: true,
935
- next: currentContext,
936
- consumed: allConsumed
937
- };
938
- },
939
- complete(state) {
940
- const result = [];
941
- for (let i = 0; i < parsers.length; i++) {
942
- const valueResult = parsers[i].complete(state[i]);
943
- if (valueResult.success) result[i] = valueResult.value;
944
- else return {
945
- success: false,
946
- error: valueResult.error
947
- };
948
- }
949
- return {
950
- success: true,
951
- value: result
952
- };
953
- },
954
- getDocFragments(state, defaultValue) {
955
- const fragments = parsers.flatMap((p, i) => {
956
- const indexState = state.kind === "unavailable" ? { kind: "unavailable" } : {
957
- kind: "available",
958
- state: state.state[i]
959
- };
960
- return p.getDocFragments(indexState, defaultValue?.[i]).fragments;
961
- });
962
- const entries = fragments.filter((d) => d.type === "entry");
963
- const sections = [];
964
- for (const fragment of fragments) {
965
- if (fragment.type !== "section") continue;
966
- if (fragment.title == null) entries.push(...fragment.entries);
967
- else sections.push(fragment);
968
- }
969
- const section = {
970
- title: label,
971
- entries
972
- };
973
- sections.push(section);
974
- return { fragments: sections.map((s) => ({
975
- ...s,
976
- type: "section"
977
- })) };
978
- },
979
- [Symbol.for("Deno.customInspect")]() {
980
- const parsersStr = parsers.length === 1 ? `[1 parser]` : `[${parsers.length} parsers]`;
981
- return label ? `tuple(${JSON.stringify(label)}, ${parsersStr})` : `tuple(${parsersStr})`;
982
- }
983
- };
984
- }
985
- function or(...parsers) {
986
- return {
987
- $valueType: [],
988
- $stateType: [],
989
- priority: Math.max(...parsers.map((p) => p.priority)),
990
- usage: [{
991
- type: "exclusive",
992
- terms: parsers.map((p) => p.usage)
993
- }],
994
- initialState: void 0,
995
- complete(state) {
996
- if (state == null) return {
997
- success: false,
998
- error: message`No parser matched.`
999
- };
1000
- const [i, result] = state;
1001
- if (result.success) return parsers[i].complete(result.next.state);
1002
- return {
1003
- success: false,
1004
- error: result.error
1005
- };
1006
- },
1007
- parse(context) {
1008
- let error = {
1009
- consumed: 0,
1010
- error: context.buffer.length < 1 ? message`No parser matched.` : message`Unexpected option or subcommand: ${optionName(context.buffer[0])}.`
1011
- };
1012
- const orderedParsers = parsers.map((p, i) => [p, i]);
1013
- orderedParsers.sort(([_, a], [__, b]) => context.state?.[0] === a ? -1 : context.state?.[0] === b ? 1 : a - b);
1014
- for (const [parser, i] of orderedParsers) {
1015
- const result = parser.parse({
1016
- ...context,
1017
- state: context.state == null || context.state[0] !== i || !context.state[1].success ? parser.initialState : context.state[1].next.state
1018
- });
1019
- if (result.success && result.consumed.length > 0) {
1020
- if (context.state?.[0] !== i && context.state?.[1].success) return {
1021
- success: false,
1022
- consumed: context.buffer.length - result.next.buffer.length,
1023
- error: message`${values(context.state[1].consumed)} and ${values(result.consumed)} cannot be used together.`
1024
- };
1025
- return {
1026
- success: true,
1027
- next: {
1028
- ...context,
1029
- buffer: result.next.buffer,
1030
- optionsTerminated: result.next.optionsTerminated,
1031
- state: [i, result]
1032
- },
1033
- consumed: result.consumed
1034
- };
1035
- } else if (!result.success && error.consumed < result.consumed) error = result;
1036
- }
1037
- return {
1038
- ...error,
1039
- success: false
1040
- };
1041
- },
1042
- getDocFragments(state, _defaultValue) {
1043
- let description;
1044
- let fragments;
1045
- if (state.kind === "unavailable" || state.state == null) fragments = parsers.flatMap((p) => p.getDocFragments({ kind: "unavailable" }, void 0).fragments);
1046
- else {
1047
- const [index, parserResult] = state.state;
1048
- const innerState = parserResult.success ? {
1049
- kind: "available",
1050
- state: parserResult.next.state
1051
- } : { kind: "unavailable" };
1052
- const docFragments = parsers[index].getDocFragments(innerState, void 0);
1053
- description = docFragments.description;
1054
- fragments = docFragments.fragments;
1055
- }
1056
- const entries = fragments.filter((f) => f.type === "entry");
1057
- const sections = [];
1058
- for (const fragment of fragments) {
1059
- if (fragment.type !== "section") continue;
1060
- if (fragment.title == null) entries.push(...fragment.entries);
1061
- else sections.push(fragment);
1062
- }
1063
- return {
1064
- description,
1065
- fragments: [...sections.map((s) => ({
1066
- ...s,
1067
- type: "section"
1068
- })), {
1069
- type: "section",
1070
- entries
1071
- }]
1072
- };
1073
- }
1074
- };
1075
- }
1076
- function longestMatch(...parsers) {
1077
- return {
1078
- $valueType: [],
1079
- $stateType: [],
1080
- priority: Math.max(...parsers.map((p) => p.priority)),
1081
- usage: [{
1082
- type: "exclusive",
1083
- terms: parsers.map((p) => p.usage)
1084
- }],
1085
- initialState: void 0,
1086
- complete(state) {
1087
- if (state == null) return {
1088
- success: false,
1089
- error: message`No parser matched.`
1090
- };
1091
- const [i, result] = state;
1092
- if (result.success) return parsers[i].complete(result.next.state);
1093
- return {
1094
- success: false,
1095
- error: result.error
1096
- };
1097
- },
1098
- parse(context) {
1099
- let bestMatch = null;
1100
- let error = {
1101
- consumed: 0,
1102
- error: context.buffer.length < 1 ? message`No parser matched.` : message`Unexpected option or subcommand: ${optionName(context.buffer[0])}.`
1103
- };
1104
- for (let i = 0; i < parsers.length; i++) {
1105
- const parser = parsers[i];
1106
- const result = parser.parse({
1107
- ...context,
1108
- state: context.state == null || context.state[0] !== i || !context.state[1].success ? parser.initialState : context.state[1].next.state
1109
- });
1110
- if (result.success) {
1111
- const consumed = context.buffer.length - result.next.buffer.length;
1112
- if (bestMatch === null || consumed > bestMatch.consumed) bestMatch = {
1113
- index: i,
1114
- result,
1115
- consumed
1116
- };
1117
- } else if (error.consumed < result.consumed) error = result;
1118
- }
1119
- if (bestMatch && bestMatch.result.success) return {
1120
- success: true,
1121
- next: {
1122
- ...context,
1123
- buffer: bestMatch.result.next.buffer,
1124
- optionsTerminated: bestMatch.result.next.optionsTerminated,
1125
- state: [bestMatch.index, bestMatch.result]
1126
- },
1127
- consumed: bestMatch.result.consumed
1128
- };
1129
- return {
1130
- ...error,
1131
- success: false
1132
- };
1133
- },
1134
- getDocFragments(state, _defaultValue) {
1135
- let description;
1136
- let fragments;
1137
- if (state.kind === "unavailable" || state.state == null) fragments = parsers.flatMap((p) => p.getDocFragments({ kind: "unavailable" }).fragments);
1138
- else {
1139
- const [i, result] = state.state;
1140
- if (result.success) {
1141
- const docResult = parsers[i].getDocFragments({
1142
- kind: "available",
1143
- state: result.next.state
1144
- });
1145
- description = docResult.description;
1146
- fragments = docResult.fragments;
1147
- } else fragments = parsers.flatMap((p) => p.getDocFragments({ kind: "unavailable" }).fragments);
1148
- }
1149
- return {
1150
- description,
1151
- fragments
1152
- };
1153
- }
1154
- };
1155
- }
1156
- function merge(...args) {
1157
- const label = typeof args[0] === "string" ? args[0] : void 0;
1158
- let parsers = typeof args[0] === "string" ? args.slice(1) : args;
1159
- parsers = parsers.toSorted((a, b) => b.priority - a.priority);
1160
- const initialState = {};
1161
- for (const parser of parsers) if (parser.initialState && typeof parser.initialState === "object") for (const field in parser.initialState) initialState[field] = parser.initialState[field];
1162
- return {
1163
- $valueType: [],
1164
- $stateType: [],
1165
- priority: Math.max(...parsers.map((p) => p.priority)),
1166
- usage: parsers.flatMap((p) => p.usage),
1167
- initialState,
1168
- parse(context) {
1169
- for (let i = 0; i < parsers.length; i++) {
1170
- const parser = parsers[i];
1171
- let parserState;
1172
- if (parser.initialState === void 0) parserState = void 0;
1173
- else if (parser.initialState && typeof parser.initialState === "object") if (context.state && typeof context.state === "object") {
1174
- const extractedState = {};
1175
- for (const field in parser.initialState) extractedState[field] = field in context.state ? context.state[field] : parser.initialState[field];
1176
- parserState = extractedState;
1177
- } else parserState = parser.initialState;
1178
- else parserState = parser.initialState;
1179
- const result = parser.parse({
1180
- ...context,
1181
- state: parserState
1182
- });
1183
- if (result.success) {
1184
- let newState;
1185
- if (parser.initialState === void 0) newState = {
1186
- ...context.state,
1187
- [`__parser_${i}`]: result.next.state
1188
- };
1189
- else newState = {
1190
- ...context.state,
1191
- ...result.next.state
1192
- };
1193
- return {
1194
- success: true,
1195
- next: {
1196
- ...context,
1197
- buffer: result.next.buffer,
1198
- optionsTerminated: result.next.optionsTerminated,
1199
- state: newState
1200
- },
1201
- consumed: result.consumed
1202
- };
1203
- } else if (result.consumed < 1) continue;
1204
- else return result;
1205
- }
1206
- return {
1207
- success: false,
1208
- consumed: 0,
1209
- error: message`No parser matched the input.`
1210
- };
1211
- },
1212
- complete(state) {
1213
- const object$1 = {};
1214
- for (let i = 0; i < parsers.length; i++) {
1215
- const parser = parsers[i];
1216
- let parserState;
1217
- if (parser.initialState === void 0) {
1218
- const key = `__parser_${i}`;
1219
- if (state && typeof state === "object" && key in state) parserState = state[key];
1220
- else parserState = void 0;
1221
- } else if (parser.initialState && typeof parser.initialState === "object") if (state && typeof state === "object") {
1222
- const extractedState = {};
1223
- for (const field in parser.initialState) extractedState[field] = field in state ? state[field] : parser.initialState[field];
1224
- parserState = extractedState;
1225
- } else parserState = parser.initialState;
1226
- else parserState = parser.initialState;
1227
- const result = parser.complete(parserState);
1228
- if (!result.success) return result;
1229
- for (const field in result.value) object$1[field] = result.value[field];
1230
- }
1231
- return {
1232
- success: true,
1233
- value: object$1
1234
- };
1235
- },
1236
- getDocFragments(state, _defaultValue) {
1237
- const fragments = parsers.flatMap((p) => {
1238
- const parserState = p.initialState === void 0 ? { kind: "unavailable" } : state.kind === "unavailable" ? { kind: "unavailable" } : {
1239
- kind: "available",
1240
- state: state.state
1241
- };
1242
- return p.getDocFragments(parserState, void 0).fragments;
1243
- });
1244
- const entries = fragments.filter((f) => f.type === "entry");
1245
- const sections = [];
1246
- for (const fragment of fragments) {
1247
- if (fragment.type !== "section") continue;
1248
- if (fragment.title == null) entries.push(...fragment.entries);
1249
- else sections.push(fragment);
1250
- }
1251
- if (label) {
1252
- const labeledSection = {
1253
- title: label,
1254
- entries
1255
- };
1256
- sections.push(labeledSection);
1257
- return { fragments: sections.map((s) => ({
1258
- ...s,
1259
- type: "section"
1260
- })) };
1261
- }
1262
- return { fragments: [...sections.map((s) => ({
1263
- ...s,
1264
- type: "section"
1265
- })), {
1266
- type: "section",
1267
- entries
1268
- }] };
1269
- }
1270
- };
1271
- }
1272
- function concat(...parsers) {
1273
- const initialState = parsers.map((parser) => parser.initialState);
1274
- return {
1275
- $valueType: [],
1276
- $stateType: [],
1277
- priority: parsers.length > 0 ? Math.max(...parsers.map((p) => p.priority)) : 0,
1278
- usage: parsers.flatMap((p) => p.usage),
1279
- initialState,
1280
- parse(context) {
1281
- let currentContext = context;
1282
- const allConsumed = [];
1283
- const matchedParsers = /* @__PURE__ */ new Set();
1284
- while (matchedParsers.size < parsers.length) {
1285
- let foundMatch = false;
1286
- let error = {
1287
- consumed: 0,
1288
- error: message`No remaining parsers could match the input.`
1289
- };
1290
- const remainingParsers = parsers.map((parser, index) => [parser, index]).filter(([_, index]) => !matchedParsers.has(index)).sort(([parserA], [parserB]) => parserB.priority - parserA.priority);
1291
- for (const [parser, index] of remainingParsers) {
1292
- const result = parser.parse({
1293
- ...currentContext,
1294
- state: currentContext.state[index]
1295
- });
1296
- if (result.success && result.consumed.length > 0) {
1297
- currentContext = {
1298
- ...currentContext,
1299
- buffer: result.next.buffer,
1300
- optionsTerminated: result.next.optionsTerminated,
1301
- state: currentContext.state.map((s, idx) => idx === index ? result.next.state : s)
1302
- };
1303
- allConsumed.push(...result.consumed);
1304
- matchedParsers.add(index);
1305
- foundMatch = true;
1306
- break;
1307
- } else if (!result.success && error.consumed < result.consumed) error = result;
1308
- }
1309
- if (!foundMatch) for (const [parser, index] of remainingParsers) {
1310
- const result = parser.parse({
1311
- ...currentContext,
1312
- state: currentContext.state[index]
1313
- });
1314
- if (result.success && result.consumed.length < 1) {
1315
- currentContext = {
1316
- ...currentContext,
1317
- state: currentContext.state.map((s, idx) => idx === index ? result.next.state : s)
1318
- };
1319
- matchedParsers.add(index);
1320
- foundMatch = true;
1321
- break;
1322
- } else if (!result.success && result.consumed < 1) {
1323
- matchedParsers.add(index);
1324
- foundMatch = true;
1325
- break;
1326
- }
1327
- }
1328
- if (!foundMatch) return {
1329
- ...error,
1330
- success: false
1331
- };
1332
- }
1333
- return {
1334
- success: true,
1335
- next: currentContext,
1336
- consumed: allConsumed
1337
- };
1338
- },
1339
- complete(state) {
1340
- const results = [];
1341
- for (let i = 0; i < parsers.length; i++) {
1342
- const parser = parsers[i];
1343
- const parserState = state[i];
1344
- const result = parser.complete(parserState);
1345
- if (!result.success) return result;
1346
- if (Array.isArray(result.value)) results.push(...result.value);
1347
- else results.push(result.value);
1348
- }
1349
- return {
1350
- success: true,
1351
- value: results
1352
- };
1353
- },
1354
- getDocFragments(state, _defaultValue) {
1355
- const fragments = parsers.flatMap((p, index) => {
1356
- const indexState = state.kind === "unavailable" ? { kind: "unavailable" } : {
1357
- kind: "available",
1358
- state: state.state[index]
1359
- };
1360
- return p.getDocFragments(indexState, void 0).fragments;
1361
- });
1362
- const entries = fragments.filter((f) => f.type === "entry");
1363
- const sections = [];
1364
- for (const fragment of fragments) {
1365
- if (fragment.type !== "section") continue;
1366
- if (fragment.title == null) entries.push(...fragment.entries);
1367
- else sections.push(fragment);
1368
- }
1369
- const result = [...sections.map((s) => ({
1370
- ...s,
1371
- type: "section"
1372
- }))];
1373
- if (entries.length > 0) result.push({
1374
- type: "section",
1375
- entries
1376
- });
1377
- return { fragments: result };
1378
- }
1379
- };
1380
- }
1381
- /**
1382
- * Creates a parser that matches a specific subcommand name and then applies
1383
- * an inner parser to the remaining arguments.
1384
- * This is useful for building CLI tools with subcommands like git, npm, etc.
1385
- * @template T The type of the value returned by the inner parser.
1386
- * @template TState The type of the state used by the inner parser.
1387
- * @param name The subcommand name to match (e.g., `"show"`, `"edit"`).
1388
- * @param parser The {@link Parser} to apply after the command is matched.
1389
- * @param options Optional configuration for the command parser, such as
1390
- * a description for documentation.
1391
- * @returns A {@link Parser} that matches the command name and delegates
1392
- * to the inner parser for the remaining arguments.
1393
- */
1394
- function command(name, parser, options = {}) {
1395
- return {
1396
- $valueType: [],
1397
- $stateType: [],
1398
- priority: 15,
1399
- usage: [{
1400
- type: "command",
1401
- name
1402
- }, ...parser.usage],
1403
- initialState: void 0,
1404
- parse(context) {
1405
- if (context.state === void 0) {
1406
- if (context.buffer.length < 1 || context.buffer[0] !== name) return {
1407
- success: false,
1408
- consumed: 0,
1409
- error: message`Expected command ${optionName(name)}, but got ${context.buffer.length > 0 ? context.buffer[0] : "end of input"}.`
1410
- };
1411
- return {
1412
- success: true,
1413
- next: {
1414
- ...context,
1415
- buffer: context.buffer.slice(1),
1416
- state: ["matched", name]
1417
- },
1418
- consumed: context.buffer.slice(0, 1)
1419
- };
1420
- } else if (context.state[0] === "matched") {
1421
- const result = parser.parse({
1422
- ...context,
1423
- state: parser.initialState
1424
- });
1425
- if (result.success) return {
1426
- success: true,
1427
- next: {
1428
- ...result.next,
1429
- state: ["parsing", result.next.state]
1430
- },
1431
- consumed: result.consumed
1432
- };
1433
- return result;
1434
- } else if (context.state[0] === "parsing") {
1435
- const result = parser.parse({
1436
- ...context,
1437
- state: context.state[1]
1438
- });
1439
- if (result.success) return {
1440
- success: true,
1441
- next: {
1442
- ...result.next,
1443
- state: ["parsing", result.next.state]
1444
- },
1445
- consumed: result.consumed
1446
- };
1447
- return result;
1448
- }
1449
- return {
1450
- success: false,
1451
- consumed: 0,
1452
- error: message`Invalid command state.`
1453
- };
1454
- },
1455
- complete(state) {
1456
- if (typeof state === "undefined") return {
1457
- success: false,
1458
- error: message`Command ${optionName(name)} was not matched.`
1459
- };
1460
- else if (state[0] === "matched") return parser.complete(parser.initialState);
1461
- else if (state[0] === "parsing") return parser.complete(state[1]);
1462
- return {
1463
- success: false,
1464
- error: message`Invalid command state during completion.`
1465
- };
1466
- },
1467
- getDocFragments(state, defaultValue) {
1468
- if (state.kind === "unavailable" || typeof state.state === "undefined") return {
1469
- description: options.description,
1470
- fragments: [{
1471
- type: "entry",
1472
- term: {
1473
- type: "command",
1474
- name
1475
- },
1476
- description: options.description
1477
- }]
1478
- };
1479
- const innerState = state.state[0] === "parsing" ? {
1480
- kind: "available",
1481
- state: state.state[1]
1482
- } : { kind: "unavailable" };
1483
- const innerFragments = parser.getDocFragments(innerState, defaultValue);
1484
- return {
1485
- ...innerFragments,
1486
- description: innerFragments.description ?? options.description
1487
- };
1488
- },
1489
- [Symbol.for("Deno.customInspect")]() {
1490
- return `command(${JSON.stringify(name)})`;
1491
- }
1492
- };
1493
- }
1494
- /**
1495
9
  * Parses an array of command-line arguments using the provided combined parser.
1496
10
  * This function processes the input arguments, applying the parser to each
1497
11
  * argument until all arguments are consumed or an error occurs.
@@ -1534,81 +48,6 @@ function parse(parser, args) {
1534
48
  };
1535
49
  }
1536
50
  /**
1537
- * Wraps a parser with a group label for documentation purposes.
1538
- *
1539
- * The `group()` function is a documentation-only wrapper that applies a label
1540
- * to any parser for help text organization. This allows you to use clean code
1541
- * structure with combinators like {@link merge} while maintaining well-organized
1542
- * help text through group labeling.
1543
- *
1544
- * The wrapped parser has identical parsing behavior but generates documentation
1545
- * fragments wrapped in a labeled section. This is particularly useful when
1546
- * combining parsers using {@link merge}—you can wrap the merged result with
1547
- * `group()` to add a section header in help output.
1548
- *
1549
- * @example
1550
- * ```typescript
1551
- * const apiOptions = merge(
1552
- * object({ endpoint: option("--endpoint", string()) }),
1553
- * object({ timeout: option("--timeout", integer()) })
1554
- * );
1555
- *
1556
- * const groupedApiOptions = group("API Options", apiOptions);
1557
- * // Now produces a labeled "API Options" section in help text
1558
- * ```
1559
- *
1560
- * @example
1561
- * ```typescript
1562
- * // Can be used with any parser, not just merge()
1563
- * const verboseGroup = group("Verbosity", object({
1564
- * verbose: option("-v", "--verbose"),
1565
- * quiet: option("-q", "--quiet")
1566
- * }));
1567
- * ```
1568
- *
1569
- * @template TValue The value type of the wrapped parser.
1570
- * @template TState The state type of the wrapped parser.
1571
- * @param label A descriptive label for this parser group, used for
1572
- * documentation and help text organization.
1573
- * @param parser The parser to wrap with a group label.
1574
- * @returns A new parser that behaves identically to the input parser
1575
- * but generates documentation within a labeled section.
1576
- * @since 0.4.0
1577
- */
1578
- function group(label, parser) {
1579
- return {
1580
- $valueType: parser.$valueType,
1581
- $stateType: parser.$stateType,
1582
- priority: parser.priority,
1583
- usage: parser.usage,
1584
- initialState: parser.initialState,
1585
- parse: (context) => parser.parse(context),
1586
- complete: (state) => parser.complete(state),
1587
- getDocFragments: (state, defaultValue) => {
1588
- const { description, fragments } = parser.getDocFragments(state, defaultValue);
1589
- const allEntries = [];
1590
- const titledSections = [];
1591
- for (const fragment of fragments) if (fragment.type === "entry") allEntries.push(fragment);
1592
- else if (fragment.type === "section") if (fragment.title) titledSections.push(fragment);
1593
- else allEntries.push(...fragment.entries);
1594
- const labeledSection = {
1595
- title: label,
1596
- entries: allEntries
1597
- };
1598
- return {
1599
- description,
1600
- fragments: [...titledSections.map((s) => ({
1601
- ...s,
1602
- type: "section"
1603
- })), {
1604
- type: "section",
1605
- ...labeledSection
1606
- }]
1607
- };
1608
- }
1609
- };
1610
- }
1611
- /**
1612
51
  * Generates a documentation page for a parser based on its current state after
1613
52
  * attempting to parse the provided arguments. This function is useful for
1614
53
  * creating help documentation that reflects the current parsing context.