@optique/core 0.1.0-dev.1

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.
@@ -0,0 +1,1065 @@
1
+ const require_message = require('./message.cjs');
2
+ const require_usage = require('./usage.cjs');
3
+ const require_valueparser = require('./valueparser.cjs');
4
+
5
+ //#region src/parser.ts
6
+ /**
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 (require_valueparser.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 (require_valueparser.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: require_message.message`Missing option ${require_message.optionNames(optionNames$1)}.`
79
+ },
80
+ parse(context) {
81
+ if (context.optionsTerminated) return {
82
+ success: false,
83
+ consumed: 0,
84
+ error: require_message.message`No more options can be parsed.`
85
+ };
86
+ else if (context.buffer.length < 1) return {
87
+ success: false,
88
+ consumed: 0,
89
+ error: require_message.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: require_message.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: require_message.message`Option ${require_message.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: require_message.message`${require_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: require_message.message`Option ${require_message.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: require_message.message`${require_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: require_message.message`No matched option for ${require_message.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: require_message.message`Missing option ${require_message.optionNames(optionNames$1)}.`
196
+ };
197
+ if (state.success) return state;
198
+ return {
199
+ success: false,
200
+ error: require_message.message`${require_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 ? 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 that expects a single argument value.
226
+ * This parser is typically used for positional arguments
227
+ * that are not options or flags.
228
+ * @template T The type of the value produced by the parser.
229
+ * @param valueParser The {@link ValueParser} that defines how to parse
230
+ * the argument value.
231
+ * @param options Optional configuration for the argument parser,
232
+ * allowing you to specify a description or other metadata.
233
+ * @returns A {@link Parser} that expects a single argument value and produces
234
+ * the parsed value of type {@link T}.
235
+ */
236
+ function argument(valueParser, options = {}) {
237
+ const optionPattern = /^--?[a-z0-9-]+$/i;
238
+ const term = {
239
+ type: "argument",
240
+ metavar: valueParser.metavar
241
+ };
242
+ return {
243
+ $valueType: [],
244
+ $stateType: [],
245
+ priority: 5,
246
+ usage: [term],
247
+ initialState: void 0,
248
+ parse(context) {
249
+ if (context.buffer.length < 1) return {
250
+ success: false,
251
+ consumed: 0,
252
+ error: require_message.message`Expected an argument, but got end of input.`
253
+ };
254
+ let i = 0;
255
+ let optionsTerminated = context.optionsTerminated;
256
+ if (!optionsTerminated) {
257
+ if (context.buffer[i] === "--") {
258
+ optionsTerminated = true;
259
+ i++;
260
+ } else if (context.buffer[i].match(optionPattern)) return {
261
+ success: false,
262
+ consumed: i,
263
+ error: require_message.message`Expected an argument, but got an option: ${require_message.optionName(context.buffer[i])}.`
264
+ };
265
+ }
266
+ if (context.buffer.length < i + 1) return {
267
+ success: false,
268
+ consumed: i,
269
+ error: require_message.message`Expected an argument, but got end of input.`
270
+ };
271
+ if (context.state != null) return {
272
+ success: false,
273
+ consumed: i,
274
+ error: require_message.message`The argument ${require_message.metavar(valueParser.metavar)} cannot be used multiple times.`
275
+ };
276
+ const result = valueParser.parse(context.buffer[i]);
277
+ return {
278
+ success: true,
279
+ next: {
280
+ ...context,
281
+ buffer: context.buffer.slice(i + 1),
282
+ state: result,
283
+ optionsTerminated
284
+ },
285
+ consumed: context.buffer.slice(0, i + 1)
286
+ };
287
+ },
288
+ complete(state) {
289
+ if (state == null) return {
290
+ success: false,
291
+ error: require_message.message`Expected a ${require_message.metavar(valueParser.metavar)}, but too few arguments.`
292
+ };
293
+ else if (state.success) return state;
294
+ return {
295
+ success: false,
296
+ error: require_message.message`${require_message.metavar(valueParser.metavar)}: ${state.error}`
297
+ };
298
+ },
299
+ getDocFragments(_state, defaultValue) {
300
+ const fragments = [{
301
+ type: "entry",
302
+ term,
303
+ description: options.description,
304
+ default: defaultValue == null ? void 0 : valueParser.format(defaultValue)
305
+ }];
306
+ return {
307
+ fragments,
308
+ description: options.description
309
+ };
310
+ },
311
+ [Symbol.for("Deno.customInspect")]() {
312
+ return `argument()`;
313
+ }
314
+ };
315
+ }
316
+ /**
317
+ * Creates a parser that makes another parser optional, allowing it to succeed
318
+ * without consuming input if the wrapped parser fails to match.
319
+ * If the wrapped parser succeeds, this returns its value.
320
+ * If the wrapped parser fails, this returns `undefined` without consuming input.
321
+ * @template TValue The type of the value returned by the wrapped parser.
322
+ * @template TState The type of the state used by the wrapped parser.
323
+ * @param parser The {@link Parser} to make optional.
324
+ * @returns A {@link Parser} that produces either the result of the wrapped parser
325
+ * or `undefined` if the wrapped parser fails to match.
326
+ */
327
+ function optional(parser) {
328
+ return {
329
+ $valueType: [],
330
+ $stateType: [],
331
+ priority: parser.priority,
332
+ usage: [{
333
+ type: "optional",
334
+ terms: parser.usage
335
+ }],
336
+ initialState: void 0,
337
+ parse(context) {
338
+ const result = parser.parse({
339
+ ...context,
340
+ state: typeof context.state === "undefined" ? parser.initialState : context.state[0]
341
+ });
342
+ if (result.success) return {
343
+ success: true,
344
+ next: {
345
+ ...result.next,
346
+ state: [result.next.state]
347
+ },
348
+ consumed: result.consumed
349
+ };
350
+ return result;
351
+ },
352
+ complete(state) {
353
+ if (typeof state === "undefined") return {
354
+ success: true,
355
+ value: void 0
356
+ };
357
+ return parser.complete(state[0]);
358
+ },
359
+ getDocFragments(state, defaultValue) {
360
+ return parser.getDocFragments(typeof state === "undefined" ? parser.initialState : state[0], defaultValue);
361
+ }
362
+ };
363
+ }
364
+ /**
365
+ * Creates a parser that makes another parser use a default value when it fails
366
+ * to match or consume input. This is similar to {@link optional}, but instead
367
+ * of returning `undefined` when the wrapped parser doesn't match, it returns
368
+ * a specified default value.
369
+ * @template TValue The type of the value returned by the wrapped parser.
370
+ * @template TState The type of the state used by the wrapped parser.
371
+ * @param parser The {@link Parser} to wrap with default behavior.
372
+ * @param defaultValue The default value to return when the wrapped parser
373
+ * doesn't match or consume input. Can be a value of type
374
+ * {@link TValue} or a function that returns such a value.
375
+ * @returns A {@link Parser} that produces either the result of the wrapped parser
376
+ * or the default value if the wrapped parser fails to match.
377
+ */
378
+ function withDefault(parser, defaultValue) {
379
+ return {
380
+ $valueType: [],
381
+ $stateType: [],
382
+ priority: parser.priority,
383
+ usage: [{
384
+ type: "optional",
385
+ terms: parser.usage
386
+ }],
387
+ initialState: void 0,
388
+ parse(context) {
389
+ const result = parser.parse({
390
+ ...context,
391
+ state: typeof context.state === "undefined" ? parser.initialState : context.state[0]
392
+ });
393
+ if (result.success) return {
394
+ success: true,
395
+ next: {
396
+ ...result.next,
397
+ state: [result.next.state]
398
+ },
399
+ consumed: result.consumed
400
+ };
401
+ return result;
402
+ },
403
+ complete(state) {
404
+ if (typeof state === "undefined") return {
405
+ success: true,
406
+ value: typeof defaultValue === "function" ? defaultValue() : defaultValue
407
+ };
408
+ return parser.complete(state[0]);
409
+ },
410
+ getDocFragments(state, upperDefaultValue) {
411
+ return parser.getDocFragments(typeof state === "undefined" ? parser.initialState : state[0], upperDefaultValue == null ? typeof defaultValue === "function" ? defaultValue() : defaultValue : upperDefaultValue);
412
+ }
413
+ };
414
+ }
415
+ /**
416
+ * Creates a parser that allows multiple occurrences of a given parser.
417
+ * This parser can be used to parse multiple values of the same type,
418
+ * such as multiple command-line arguments or options.
419
+ * @template TValue The type of the value that the parser produces.
420
+ * @template TState The type of the state used by the parser.
421
+ * @param parser The {@link Parser} to apply multiple times.
422
+ * @param options Optional configuration for the parser,
423
+ * allowing you to specify the minimum and maximum number of
424
+ * occurrences allowed.
425
+ * @returns A {@link Parser} that produces an array of values
426
+ * of type {@link TValue} and an array of states
427
+ * of type {@link TState}.
428
+ */
429
+ function multiple(parser, options = {}) {
430
+ const { min = 0, max = Infinity } = options;
431
+ return {
432
+ $valueType: [],
433
+ $stateType: [],
434
+ priority: parser.priority,
435
+ usage: [{
436
+ type: "multiple",
437
+ terms: parser.usage,
438
+ min
439
+ }],
440
+ initialState: [],
441
+ parse(context) {
442
+ let added = context.state.length < 1;
443
+ let result = parser.parse({
444
+ ...context,
445
+ state: context.state.at(-1) ?? parser.initialState
446
+ });
447
+ if (!result.success) if (!added) {
448
+ result = parser.parse({
449
+ ...context,
450
+ state: parser.initialState
451
+ });
452
+ if (!result.success) return result;
453
+ added = true;
454
+ } else return result;
455
+ return {
456
+ success: true,
457
+ next: {
458
+ ...result.next,
459
+ state: [...added ? context.state : context.state.slice(0, -1), result.next.state]
460
+ },
461
+ consumed: result.consumed
462
+ };
463
+ },
464
+ complete(state) {
465
+ const result = [];
466
+ for (const s of state) {
467
+ const valueResult = parser.complete(s);
468
+ if (valueResult.success) result.push(valueResult.value);
469
+ else return {
470
+ success: false,
471
+ error: valueResult.error
472
+ };
473
+ }
474
+ if (result.length < min) return {
475
+ success: false,
476
+ error: require_message.message`Expected at least ${require_message.text(min.toLocaleString("en"))} values, but got only ${require_message.text(result.length.toLocaleString("en"))}.`
477
+ };
478
+ else if (result.length > max) return {
479
+ success: false,
480
+ error: require_message.message`Expected at most ${require_message.text(max.toLocaleString("en"))} values, but got ${require_message.text(result.length.toLocaleString("en"))}.`
481
+ };
482
+ return {
483
+ success: true,
484
+ value: result
485
+ };
486
+ },
487
+ getDocFragments(state, defaultValue) {
488
+ return parser.getDocFragments(state.at(-1) ?? parser.initialState, defaultValue != null && defaultValue.length > 0 ? defaultValue[0] : void 0);
489
+ }
490
+ };
491
+ }
492
+ function object(labelOrParsers, maybeParsers) {
493
+ const label = typeof labelOrParsers === "string" ? labelOrParsers : void 0;
494
+ const parsers = typeof labelOrParsers === "string" ? maybeParsers : labelOrParsers;
495
+ const parserPairs = Object.entries(parsers);
496
+ parserPairs.sort(([_, parserA], [__, parserB]) => parserB.priority - parserA.priority);
497
+ return {
498
+ $valueType: [],
499
+ $stateType: [],
500
+ priority: Math.max(...Object.values(parsers).map((p) => p.priority)),
501
+ usage: parserPairs.flatMap(([_, p]) => p.usage),
502
+ initialState: Object.fromEntries(Object.entries(parsers).map(([key, parser]) => [key, parser.initialState])),
503
+ parse(context) {
504
+ let error = {
505
+ consumed: 0,
506
+ error: context.buffer.length > 0 ? require_message.message`Unexpected option or argument: ${context.buffer[0]}.` : require_message.message`Expected an option or argument, but got end of input.`
507
+ };
508
+ for (const [field, parser] of parserPairs) {
509
+ const result = parser.parse({
510
+ ...context,
511
+ state: context.state[field]
512
+ });
513
+ if (result.success && result.consumed.length > 0) return {
514
+ success: true,
515
+ next: {
516
+ ...context,
517
+ buffer: result.next.buffer,
518
+ optionsTerminated: result.next.optionsTerminated,
519
+ state: {
520
+ ...context.state,
521
+ [field]: result.next.state
522
+ }
523
+ },
524
+ consumed: result.consumed
525
+ };
526
+ else if (!result.success && error.consumed < result.consumed) error = result;
527
+ }
528
+ return {
529
+ ...error,
530
+ success: false
531
+ };
532
+ },
533
+ complete(state) {
534
+ const result = {};
535
+ for (const field in state) {
536
+ if (!(field in parsers)) continue;
537
+ const valueResult = parsers[field].complete(state[field]);
538
+ if (valueResult.success) result[field] = valueResult.value;
539
+ else return {
540
+ success: false,
541
+ error: valueResult.error
542
+ };
543
+ }
544
+ return {
545
+ success: true,
546
+ value: result
547
+ };
548
+ },
549
+ getDocFragments(state, defaultValue) {
550
+ const fragments = parserPairs.flatMap(([field, p]) => p.getDocFragments(state[field], defaultValue?.[field]).fragments);
551
+ const entries = fragments.filter((d) => d.type === "entry");
552
+ const sections = [];
553
+ for (const fragment of fragments) {
554
+ if (fragment.type !== "section") continue;
555
+ if (fragment.title == null) entries.push(...fragment.entries);
556
+ else sections.push(fragment);
557
+ }
558
+ const section = {
559
+ title: label,
560
+ entries
561
+ };
562
+ sections.push(section);
563
+ return { fragments: sections.map((s) => ({
564
+ ...s,
565
+ type: "section"
566
+ })) };
567
+ }
568
+ };
569
+ }
570
+ function tuple(labelOrParsers, maybeParsers) {
571
+ const label = typeof labelOrParsers === "string" ? labelOrParsers : void 0;
572
+ const parsers = typeof labelOrParsers === "string" ? maybeParsers : labelOrParsers;
573
+ return {
574
+ $valueType: [],
575
+ $stateType: [],
576
+ usage: parsers.toSorted((a, b) => b.priority - a.priority).flatMap((p) => p.usage),
577
+ priority: parsers.length > 0 ? Math.max(...parsers.map((p) => p.priority)) : 0,
578
+ initialState: parsers.map((parser) => parser.initialState),
579
+ parse(context) {
580
+ let currentContext = context;
581
+ const allConsumed = [];
582
+ const matchedParsers = /* @__PURE__ */ new Set();
583
+ while (matchedParsers.size < parsers.length) {
584
+ let foundMatch = false;
585
+ let error = {
586
+ consumed: 0,
587
+ error: require_message.message`No remaining parsers could match the input.`
588
+ };
589
+ const remainingParsers = parsers.map((parser, index) => [parser, index]).filter(([_, index]) => !matchedParsers.has(index)).sort(([parserA], [parserB]) => parserB.priority - parserA.priority);
590
+ for (const [parser, index] of remainingParsers) {
591
+ const result = parser.parse({
592
+ ...currentContext,
593
+ state: currentContext.state[index]
594
+ });
595
+ if (result.success && result.consumed.length > 0) {
596
+ currentContext = {
597
+ ...currentContext,
598
+ buffer: result.next.buffer,
599
+ optionsTerminated: result.next.optionsTerminated,
600
+ state: currentContext.state.map((s, idx) => idx === index ? result.next.state : s)
601
+ };
602
+ allConsumed.push(...result.consumed);
603
+ matchedParsers.add(index);
604
+ foundMatch = true;
605
+ break;
606
+ } else if (!result.success && error.consumed < result.consumed) error = result;
607
+ }
608
+ if (!foundMatch) for (const [parser, index] of remainingParsers) {
609
+ const result = parser.parse({
610
+ ...currentContext,
611
+ state: currentContext.state[index]
612
+ });
613
+ if (result.success && result.consumed.length < 1) {
614
+ currentContext = {
615
+ ...currentContext,
616
+ state: currentContext.state.map((s, idx) => idx === index ? result.next.state : s)
617
+ };
618
+ matchedParsers.add(index);
619
+ foundMatch = true;
620
+ break;
621
+ } else if (!result.success && result.consumed < 1) {
622
+ matchedParsers.add(index);
623
+ foundMatch = true;
624
+ break;
625
+ }
626
+ }
627
+ if (!foundMatch) return {
628
+ ...error,
629
+ success: false
630
+ };
631
+ }
632
+ return {
633
+ success: true,
634
+ next: currentContext,
635
+ consumed: allConsumed
636
+ };
637
+ },
638
+ complete(state) {
639
+ const result = [];
640
+ for (let i = 0; i < parsers.length; i++) {
641
+ const valueResult = parsers[i].complete(state[i]);
642
+ if (valueResult.success) result[i] = valueResult.value;
643
+ else return {
644
+ success: false,
645
+ error: valueResult.error
646
+ };
647
+ }
648
+ return {
649
+ success: true,
650
+ value: result
651
+ };
652
+ },
653
+ getDocFragments(state, defaultValue) {
654
+ const fragments = parsers.flatMap((p, i) => p.getDocFragments(state[i], defaultValue?.[i]).fragments);
655
+ const entries = fragments.filter((d) => d.type === "entry");
656
+ const sections = [];
657
+ for (const fragment of fragments) {
658
+ if (fragment.type !== "section") continue;
659
+ if (fragment.title == null) entries.push(...fragment.entries);
660
+ else sections.push(fragment);
661
+ }
662
+ const section = {
663
+ title: label,
664
+ entries
665
+ };
666
+ sections.push(section);
667
+ return { fragments: sections.map((s) => ({
668
+ ...s,
669
+ type: "section"
670
+ })) };
671
+ },
672
+ [Symbol.for("Deno.customInspect")]() {
673
+ const parsersStr = parsers.length === 1 ? `[1 parser]` : `[${parsers.length} parsers]`;
674
+ return label ? `tuple(${JSON.stringify(label)}, ${parsersStr})` : `tuple(${parsersStr})`;
675
+ }
676
+ };
677
+ }
678
+ function or(...parsers) {
679
+ return {
680
+ $valueType: [],
681
+ $stateType: [],
682
+ priority: Math.max(...parsers.map((p) => p.priority)),
683
+ usage: [{
684
+ type: "exclusive",
685
+ terms: parsers.map((p) => p.usage)
686
+ }],
687
+ initialState: void 0,
688
+ complete(state) {
689
+ if (state == null) return {
690
+ success: false,
691
+ error: require_message.message`No parser matched.`
692
+ };
693
+ const [i, result] = state;
694
+ if (result.success) return parsers[i].complete(result.next.state);
695
+ return {
696
+ success: false,
697
+ error: result.error
698
+ };
699
+ },
700
+ parse(context) {
701
+ let error = {
702
+ consumed: 0,
703
+ error: context.buffer.length < 1 ? require_message.message`No parser matched.` : require_message.message`Unexpected option or subcommand: ${require_message.optionName(context.buffer[0])}.`
704
+ };
705
+ const orderedParsers = parsers.map((p, i) => [p, i]);
706
+ orderedParsers.sort(([_, a], [__, b]) => context.state?.[0] === a ? -1 : context.state?.[0] === b ? 1 : a - b);
707
+ for (const [parser, i] of orderedParsers) {
708
+ const result = parser.parse({
709
+ ...context,
710
+ state: context.state == null || context.state[0] !== i || !context.state[1].success ? parser.initialState : context.state[1].next.state
711
+ });
712
+ if (result.success && result.consumed.length > 0) {
713
+ if (context.state?.[0] !== i && context.state?.[1].success) return {
714
+ success: false,
715
+ consumed: context.buffer.length - result.next.buffer.length,
716
+ error: require_message.message`${require_message.values(context.state[1].consumed)} and ${require_message.values(result.consumed)} cannot be used together.`
717
+ };
718
+ return {
719
+ success: true,
720
+ next: {
721
+ ...context,
722
+ buffer: result.next.buffer,
723
+ optionsTerminated: result.next.optionsTerminated,
724
+ state: [i, result]
725
+ },
726
+ consumed: result.consumed
727
+ };
728
+ } else if (!result.success && error.consumed < result.consumed) error = result;
729
+ }
730
+ return {
731
+ ...error,
732
+ success: false
733
+ };
734
+ },
735
+ getDocFragments(state, _defaultValue) {
736
+ let description;
737
+ let fragments;
738
+ if (state == null) fragments = parsers.flatMap((p) => p.getDocFragments(p.initialState, void 0).fragments);
739
+ else {
740
+ const [index, parserResult] = state;
741
+ const docFragments = parsers[index].getDocFragments(parserResult.success ? parserResult.next.state : parsers[index].initialState, void 0);
742
+ description = docFragments.description;
743
+ fragments = docFragments.fragments;
744
+ }
745
+ const entries = fragments.filter((f) => f.type === "entry");
746
+ const sections = [];
747
+ for (const fragment of fragments) {
748
+ if (fragment.type !== "section") continue;
749
+ if (fragment.title == null) entries.push(...fragment.entries);
750
+ else sections.push(fragment);
751
+ }
752
+ return {
753
+ description,
754
+ fragments: [...sections.map((s) => ({
755
+ ...s,
756
+ type: "section"
757
+ })), {
758
+ type: "section",
759
+ entries
760
+ }]
761
+ };
762
+ }
763
+ };
764
+ }
765
+ function merge(...parsers) {
766
+ parsers = parsers.toSorted((a, b) => b.priority - a.priority);
767
+ const initialState = {};
768
+ for (const parser of parsers) for (const field in parser.initialState) initialState[field] = parser.initialState[field];
769
+ return {
770
+ $valueType: [],
771
+ $stateType: [],
772
+ priority: Math.max(...parsers.map((p) => p.priority)),
773
+ usage: parsers.flatMap((p) => p.usage),
774
+ initialState,
775
+ parse(context) {
776
+ for (const parser of parsers) {
777
+ const result = parser.parse(context);
778
+ if (result.success) return {
779
+ success: true,
780
+ next: {
781
+ ...context,
782
+ buffer: result.next.buffer,
783
+ optionsTerminated: result.next.optionsTerminated,
784
+ state: {
785
+ ...context.state,
786
+ ...result.next.state
787
+ }
788
+ },
789
+ consumed: result.consumed
790
+ };
791
+ else if (result.consumed < 1) continue;
792
+ else return result;
793
+ }
794
+ return {
795
+ success: false,
796
+ consumed: 0,
797
+ error: require_message.message`No parser matched the input.`
798
+ };
799
+ },
800
+ complete(state) {
801
+ const object$1 = {};
802
+ for (const parser of parsers) {
803
+ const result = parser.complete(state);
804
+ if (!result.success) return result;
805
+ for (const field in result.value) object$1[field] = result.value[field];
806
+ }
807
+ return {
808
+ success: true,
809
+ value: object$1
810
+ };
811
+ },
812
+ getDocFragments(state, _defaultValue) {
813
+ const fragments = parsers.flatMap((p) => p.getDocFragments(state, void 0).fragments);
814
+ const entries = fragments.filter((f) => f.type === "entry");
815
+ const sections = [];
816
+ for (const fragment of fragments) {
817
+ if (fragment.type !== "section") continue;
818
+ if (fragment.title == null) entries.push(...fragment.entries);
819
+ else sections.push(fragment);
820
+ }
821
+ return { fragments: [...sections.map((s) => ({
822
+ ...s,
823
+ type: "section"
824
+ })), {
825
+ type: "section",
826
+ entries
827
+ }] };
828
+ }
829
+ };
830
+ }
831
+ /**
832
+ * Creates a parser that matches a specific subcommand name and then applies
833
+ * an inner parser to the remaining arguments.
834
+ * This is useful for building CLI tools with subcommands like git, npm, etc.
835
+ * @template T The type of the value returned by the inner parser.
836
+ * @template TState The type of the state used by the inner parser.
837
+ * @param name The subcommand name to match (e.g., `"show"`, `"edit"`).
838
+ * @param parser The {@link Parser} to apply after the command is matched.
839
+ * @param options Optional configuration for the command parser, such as
840
+ * a description for documentation.
841
+ * @returns A {@link Parser} that matches the command name and delegates
842
+ * to the inner parser for the remaining arguments.
843
+ */
844
+ function command(name, parser, options = {}) {
845
+ return {
846
+ $valueType: [],
847
+ $stateType: [],
848
+ priority: 15,
849
+ usage: [{
850
+ type: "command",
851
+ name
852
+ }, ...parser.usage],
853
+ initialState: void 0,
854
+ parse(context) {
855
+ if (context.state === void 0) {
856
+ if (context.buffer.length < 1 || context.buffer[0] !== name) return {
857
+ success: false,
858
+ consumed: 0,
859
+ error: require_message.message`Expected command ${require_message.optionName(name)}, but got ${context.buffer.length > 0 ? context.buffer[0] : "end of input"}.`
860
+ };
861
+ return {
862
+ success: true,
863
+ next: {
864
+ ...context,
865
+ buffer: context.buffer.slice(1),
866
+ state: ["matched", name]
867
+ },
868
+ consumed: context.buffer.slice(0, 1)
869
+ };
870
+ } else if (context.state[0] === "matched") {
871
+ const result = parser.parse({
872
+ ...context,
873
+ state: parser.initialState
874
+ });
875
+ if (result.success) return {
876
+ success: true,
877
+ next: {
878
+ ...result.next,
879
+ state: ["parsing", result.next.state]
880
+ },
881
+ consumed: result.consumed
882
+ };
883
+ return result;
884
+ } else if (context.state[0] === "parsing") {
885
+ const result = parser.parse({
886
+ ...context,
887
+ state: context.state[1]
888
+ });
889
+ if (result.success) return {
890
+ success: true,
891
+ next: {
892
+ ...result.next,
893
+ state: ["parsing", result.next.state]
894
+ },
895
+ consumed: result.consumed
896
+ };
897
+ return result;
898
+ }
899
+ return {
900
+ success: false,
901
+ consumed: 0,
902
+ error: require_message.message`Invalid command state.`
903
+ };
904
+ },
905
+ complete(state) {
906
+ if (typeof state === "undefined") return {
907
+ success: false,
908
+ error: require_message.message`Command ${require_message.optionName(name)} was not matched.`
909
+ };
910
+ else if (state[0] === "matched") return parser.complete(parser.initialState);
911
+ else if (state[0] === "parsing") return parser.complete(state[1]);
912
+ return {
913
+ success: false,
914
+ error: require_message.message`Invalid command state during completion.`
915
+ };
916
+ },
917
+ getDocFragments(state, defaultValue) {
918
+ if (typeof state === "undefined") return {
919
+ description: options.description,
920
+ fragments: [{
921
+ type: "entry",
922
+ term: {
923
+ type: "command",
924
+ name
925
+ },
926
+ description: options.description
927
+ }]
928
+ };
929
+ const innerFragments = parser.getDocFragments(state[0] === "parsing" ? state[1] : parser.initialState, defaultValue);
930
+ return {
931
+ ...innerFragments,
932
+ description: innerFragments.description ?? options.description
933
+ };
934
+ },
935
+ [Symbol.for("Deno.customInspect")]() {
936
+ return `command(${JSON.stringify(name)})`;
937
+ }
938
+ };
939
+ }
940
+ /**
941
+ * Parses an array of command-line arguments using the provided combined parser.
942
+ * This function processes the input arguments, applying the parser to each
943
+ * argument until all arguments are consumed or an error occurs.
944
+ * @template T The type of the value produced by the parser.
945
+ * @param parser The combined {@link Parser} to use for parsing the input
946
+ * arguments.
947
+ * @param args The array of command-line arguments to parse. Usually this is
948
+ * `process.argv.slice(2)` in Node.js or `Deno.args` in Deno.
949
+ * @returns A {@link Result} object indicating whether the parsing was
950
+ * successful or not. If successful, it contains the parsed value of
951
+ * type `T`. If not, it contains an error message describing the
952
+ * failure.
953
+ */
954
+ function parse(parser, args) {
955
+ let context = {
956
+ buffer: args,
957
+ optionsTerminated: false,
958
+ state: parser.initialState
959
+ };
960
+ do {
961
+ const result = parser.parse(context);
962
+ if (!result.success) return {
963
+ success: false,
964
+ error: result.error
965
+ };
966
+ context = result.next;
967
+ } while (context.buffer.length > 0);
968
+ const endResult = parser.complete(context.state);
969
+ return endResult.success ? {
970
+ success: true,
971
+ value: endResult.value
972
+ } : {
973
+ success: false,
974
+ error: endResult.error
975
+ };
976
+ }
977
+ /**
978
+ * Generates a documentation page for a parser based on its current state after
979
+ * attempting to parse the provided arguments. This function is useful for
980
+ * creating help documentation that reflects the current parsing context.
981
+ *
982
+ * The function works by:
983
+ * 1. Attempting to parse the provided arguments to determine the current state
984
+ * 2. Generating documentation fragments from the parser's current state
985
+ * 3. Organizing fragments into entries and sections
986
+ * 4. Resolving command usage terms based on parsed arguments
987
+ *
988
+ * @param parser The parser to generate documentation for
989
+ * @param args Optional array of command-line arguments that have been parsed
990
+ * so far. Defaults to an empty array. This is used to determine
991
+ * the current parsing context and generate contextual documentation.
992
+ * @returns A {@link DocPage} containing usage information, sections, and
993
+ * optional description, or `undefined` if no documentation can be
994
+ * generated.
995
+ *
996
+ * @example
997
+ * ```typescript
998
+ * const parser = object({
999
+ * verbose: option("-v", "--verbose"),
1000
+ * port: option("-p", "--port", integer())
1001
+ * });
1002
+ *
1003
+ * // Get documentation for the root parser
1004
+ * const rootDoc = getDocPage(parser);
1005
+ *
1006
+ * // Get documentation after parsing some arguments
1007
+ * const contextDoc = getDocPage(parser, ["-v"]);
1008
+ * ```
1009
+ */
1010
+ function getDocPage(parser, args = []) {
1011
+ let context = {
1012
+ buffer: args,
1013
+ optionsTerminated: false,
1014
+ state: parser.initialState
1015
+ };
1016
+ do {
1017
+ const result = parser.parse(context);
1018
+ if (!result.success) break;
1019
+ context = result.next;
1020
+ } while (context.buffer.length > 0);
1021
+ const { description, fragments } = parser.getDocFragments(context.state, void 0);
1022
+ const entries = fragments.filter((f) => f.type === "entry");
1023
+ const sections = [];
1024
+ for (const fragment of fragments) {
1025
+ if (fragment.type !== "section") continue;
1026
+ if (fragment.title == null) entries.push(...fragment.entries);
1027
+ else sections.push(fragment);
1028
+ }
1029
+ if (entries.length > 0) sections.push({ entries });
1030
+ const usage = [...require_usage.normalizeUsage(parser.usage)];
1031
+ let i = 0;
1032
+ for (const arg of args) {
1033
+ const term = usage[i];
1034
+ if (usage.length > i && term.type === "exclusive") for (const termGroup of term.terms) {
1035
+ const firstTerm = termGroup[0];
1036
+ if (firstTerm?.type !== "command" || firstTerm.name !== arg) continue;
1037
+ usage.splice(i, 1, ...termGroup);
1038
+ break;
1039
+ }
1040
+ i++;
1041
+ }
1042
+ return description == null ? {
1043
+ usage,
1044
+ sections
1045
+ } : {
1046
+ usage,
1047
+ sections,
1048
+ description
1049
+ };
1050
+ }
1051
+
1052
+ //#endregion
1053
+ exports.argument = argument;
1054
+ exports.command = command;
1055
+ exports.constant = constant;
1056
+ exports.getDocPage = getDocPage;
1057
+ exports.merge = merge;
1058
+ exports.multiple = multiple;
1059
+ exports.object = object;
1060
+ exports.option = option;
1061
+ exports.optional = optional;
1062
+ exports.or = or;
1063
+ exports.parse = parse;
1064
+ exports.tuple = tuple;
1065
+ exports.withDefault = withDefault;