@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.
- package/README.md +475 -0
- package/dist/doc.cjs +104 -0
- package/dist/doc.d.cts +129 -0
- package/dist/doc.d.ts +129 -0
- package/dist/doc.js +104 -0
- package/dist/facade.cjs +98 -0
- package/dist/facade.d.cts +113 -0
- package/dist/facade.d.ts +113 -0
- package/dist/facade.js +97 -0
- package/dist/index.cjs +42 -0
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8 -0
- package/dist/message.cjs +202 -0
- package/dist/message.d.cts +199 -0
- package/dist/message.d.ts +199 -0
- package/dist/message.js +194 -0
- package/dist/parser.cjs +1065 -0
- package/dist/parser.d.cts +580 -0
- package/dist/parser.d.ts +580 -0
- package/dist/parser.js +1053 -0
- package/dist/usage.cjs +242 -0
- package/dist/usage.d.cts +217 -0
- package/dist/usage.d.ts +217 -0
- package/dist/usage.js +239 -0
- package/dist/valueparser.cjs +332 -0
- package/dist/valueparser.d.cts +332 -0
- package/dist/valueparser.d.ts +332 -0
- package/dist/valueparser.js +325 -0
- package/package.json +117 -0
package/dist/parser.cjs
ADDED
|
@@ -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;
|