@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/constructs.cjs +754 -0
- package/dist/constructs.d.cts +1100 -0
- package/dist/constructs.d.ts +1100 -0
- package/dist/constructs.js +748 -0
- package/dist/facade.cjs +29 -26
- package/dist/facade.js +4 -1
- package/dist/index.cjs +20 -17
- package/dist/index.d.cts +5 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -1
- package/dist/modifiers.cjs +300 -0
- package/dist/modifiers.d.cts +192 -0
- package/dist/modifiers.d.ts +192 -0
- package/dist/modifiers.js +296 -0
- package/dist/parser.cjs +20 -1581
- package/dist/parser.d.cts +6 -1279
- package/dist/parser.d.ts +6 -1279
- package/dist/parser.js +4 -1565
- package/dist/primitives.cjs +595 -0
- package/dist/primitives.d.cts +274 -0
- package/dist/primitives.d.ts +274 -0
- package/dist/primitives.js +591 -0
- package/dist/valueparser.cjs +28 -28
- package/dist/valueparser.d.cts +144 -0
- package/dist/valueparser.d.ts +144 -0
- package/dist/valueparser.js +28 -28
- package/package.json +25 -1
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
import { message, metavar, optionName, optionNames } from "./message.js";
|
|
2
|
+
import { isValueParser } from "./valueparser.js";
|
|
3
|
+
|
|
4
|
+
//#region src/primitives.ts
|
|
5
|
+
/**
|
|
6
|
+
* Creates a parser that always succeeds without consuming any input and
|
|
7
|
+
* produces a constant value of the type {@link T}.
|
|
8
|
+
* @template T The type of the constant value produced by the parser.
|
|
9
|
+
*/
|
|
10
|
+
function constant(value) {
|
|
11
|
+
return {
|
|
12
|
+
$valueType: [],
|
|
13
|
+
$stateType: [],
|
|
14
|
+
priority: 0,
|
|
15
|
+
usage: [],
|
|
16
|
+
initialState: value,
|
|
17
|
+
parse(context) {
|
|
18
|
+
return {
|
|
19
|
+
success: true,
|
|
20
|
+
next: context,
|
|
21
|
+
consumed: []
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
complete(state) {
|
|
25
|
+
return {
|
|
26
|
+
success: true,
|
|
27
|
+
value: state
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
getDocFragments(_state, _defaultValue) {
|
|
31
|
+
return { fragments: [] };
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function option(...args) {
|
|
36
|
+
const lastArg = args.at(-1);
|
|
37
|
+
const secondLastArg = args.at(-2);
|
|
38
|
+
let valueParser;
|
|
39
|
+
let optionNames$1;
|
|
40
|
+
let options = {};
|
|
41
|
+
if (isValueParser(lastArg)) {
|
|
42
|
+
valueParser = lastArg;
|
|
43
|
+
optionNames$1 = args.slice(0, -1);
|
|
44
|
+
} else if (typeof lastArg === "object" && lastArg != null) {
|
|
45
|
+
options = lastArg;
|
|
46
|
+
if (isValueParser(secondLastArg)) {
|
|
47
|
+
valueParser = secondLastArg;
|
|
48
|
+
optionNames$1 = args.slice(0, -2);
|
|
49
|
+
} else {
|
|
50
|
+
valueParser = void 0;
|
|
51
|
+
optionNames$1 = args.slice(0, -1);
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
optionNames$1 = args;
|
|
55
|
+
valueParser = void 0;
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
$valueType: [],
|
|
59
|
+
$stateType: [],
|
|
60
|
+
priority: 10,
|
|
61
|
+
usage: [valueParser == null ? {
|
|
62
|
+
type: "optional",
|
|
63
|
+
terms: [{
|
|
64
|
+
type: "option",
|
|
65
|
+
names: optionNames$1
|
|
66
|
+
}]
|
|
67
|
+
} : {
|
|
68
|
+
type: "option",
|
|
69
|
+
names: optionNames$1,
|
|
70
|
+
metavar: valueParser.metavar
|
|
71
|
+
}],
|
|
72
|
+
initialState: valueParser == null ? {
|
|
73
|
+
success: true,
|
|
74
|
+
value: false
|
|
75
|
+
} : {
|
|
76
|
+
success: false,
|
|
77
|
+
error: options.errors?.missing ? typeof options.errors.missing === "function" ? options.errors.missing(optionNames$1) : options.errors.missing : message`Missing option ${optionNames(optionNames$1)}.`
|
|
78
|
+
},
|
|
79
|
+
parse(context) {
|
|
80
|
+
if (context.optionsTerminated) return {
|
|
81
|
+
success: false,
|
|
82
|
+
consumed: 0,
|
|
83
|
+
error: options.errors?.optionsTerminated ?? message`No more options can be parsed.`
|
|
84
|
+
};
|
|
85
|
+
else if (context.buffer.length < 1) return {
|
|
86
|
+
success: false,
|
|
87
|
+
consumed: 0,
|
|
88
|
+
error: options.errors?.endOfInput ?? message`Expected an option, but got end of input.`
|
|
89
|
+
};
|
|
90
|
+
if (context.buffer[0] === "--") return {
|
|
91
|
+
success: true,
|
|
92
|
+
next: {
|
|
93
|
+
...context,
|
|
94
|
+
buffer: context.buffer.slice(1),
|
|
95
|
+
state: context.state,
|
|
96
|
+
optionsTerminated: true
|
|
97
|
+
},
|
|
98
|
+
consumed: context.buffer.slice(0, 1)
|
|
99
|
+
};
|
|
100
|
+
if (optionNames$1.includes(context.buffer[0])) {
|
|
101
|
+
if (context.state.success && (valueParser != null || context.state.value)) return {
|
|
102
|
+
success: false,
|
|
103
|
+
consumed: 1,
|
|
104
|
+
error: options.errors?.duplicate ? typeof options.errors.duplicate === "function" ? options.errors.duplicate(context.buffer[0]) : options.errors.duplicate : message`${context.buffer[0]} cannot be used multiple times.`
|
|
105
|
+
};
|
|
106
|
+
if (valueParser == null) return {
|
|
107
|
+
success: true,
|
|
108
|
+
next: {
|
|
109
|
+
...context,
|
|
110
|
+
state: {
|
|
111
|
+
success: true,
|
|
112
|
+
value: true
|
|
113
|
+
},
|
|
114
|
+
buffer: context.buffer.slice(1)
|
|
115
|
+
},
|
|
116
|
+
consumed: context.buffer.slice(0, 1)
|
|
117
|
+
};
|
|
118
|
+
if (context.buffer.length < 2) return {
|
|
119
|
+
success: false,
|
|
120
|
+
consumed: 1,
|
|
121
|
+
error: message`Option ${optionName(context.buffer[0])} requires a value, but got no value.`
|
|
122
|
+
};
|
|
123
|
+
const result = valueParser.parse(context.buffer[1]);
|
|
124
|
+
return {
|
|
125
|
+
success: true,
|
|
126
|
+
next: {
|
|
127
|
+
...context,
|
|
128
|
+
state: result,
|
|
129
|
+
buffer: context.buffer.slice(2)
|
|
130
|
+
},
|
|
131
|
+
consumed: context.buffer.slice(0, 2)
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const prefixes = optionNames$1.filter((name) => name.startsWith("--") || name.startsWith("/")).map((name) => name.startsWith("/") ? `${name}:` : `${name}=`);
|
|
135
|
+
for (const prefix of prefixes) {
|
|
136
|
+
if (!context.buffer[0].startsWith(prefix)) continue;
|
|
137
|
+
if (context.state.success && (valueParser != null || context.state.value)) return {
|
|
138
|
+
success: false,
|
|
139
|
+
consumed: 1,
|
|
140
|
+
error: options.errors?.duplicate ? typeof options.errors.duplicate === "function" ? options.errors.duplicate(prefix) : options.errors.duplicate : message`${optionName(prefix)} cannot be used multiple times.`
|
|
141
|
+
};
|
|
142
|
+
const value = context.buffer[0].slice(prefix.length);
|
|
143
|
+
if (valueParser == null) return {
|
|
144
|
+
success: false,
|
|
145
|
+
consumed: 1,
|
|
146
|
+
error: options.errors?.unexpectedValue ? typeof options.errors.unexpectedValue === "function" ? options.errors.unexpectedValue(value) : options.errors.unexpectedValue : message`Option ${optionName(prefix)} is a Boolean flag, but got a value: ${value}.`
|
|
147
|
+
};
|
|
148
|
+
const result = valueParser.parse(value);
|
|
149
|
+
return {
|
|
150
|
+
success: true,
|
|
151
|
+
next: {
|
|
152
|
+
...context,
|
|
153
|
+
state: result,
|
|
154
|
+
buffer: context.buffer.slice(1)
|
|
155
|
+
},
|
|
156
|
+
consumed: context.buffer.slice(0, 1)
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (valueParser == null) {
|
|
160
|
+
const shortOptions = optionNames$1.filter((name) => name.match(/^-[^-]$/));
|
|
161
|
+
for (const shortOption of shortOptions) {
|
|
162
|
+
if (!context.buffer[0].startsWith(shortOption)) continue;
|
|
163
|
+
if (context.state.success && (valueParser != null || context.state.value)) return {
|
|
164
|
+
success: false,
|
|
165
|
+
consumed: 1,
|
|
166
|
+
error: options.errors?.duplicate ? typeof options.errors.duplicate === "function" ? options.errors.duplicate(shortOption) : options.errors.duplicate : message`${optionName(shortOption)} cannot be used multiple times.`
|
|
167
|
+
};
|
|
168
|
+
return {
|
|
169
|
+
success: true,
|
|
170
|
+
next: {
|
|
171
|
+
...context,
|
|
172
|
+
state: {
|
|
173
|
+
success: true,
|
|
174
|
+
value: true
|
|
175
|
+
},
|
|
176
|
+
buffer: [`-${context.buffer[0].slice(2)}`, ...context.buffer.slice(1)]
|
|
177
|
+
},
|
|
178
|
+
consumed: [context.buffer[0].slice(0, 2)]
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
success: false,
|
|
184
|
+
consumed: 0,
|
|
185
|
+
error: message`No matched option for ${optionName(context.buffer[0])}.`
|
|
186
|
+
};
|
|
187
|
+
},
|
|
188
|
+
complete(state) {
|
|
189
|
+
if (state == null) return valueParser == null ? {
|
|
190
|
+
success: true,
|
|
191
|
+
value: false
|
|
192
|
+
} : {
|
|
193
|
+
success: false,
|
|
194
|
+
error: options.errors?.missing ? typeof options.errors.missing === "function" ? options.errors.missing(optionNames$1) : options.errors.missing : message`Missing option ${optionNames(optionNames$1)}.`
|
|
195
|
+
};
|
|
196
|
+
if (state.success) return state;
|
|
197
|
+
return {
|
|
198
|
+
success: false,
|
|
199
|
+
error: options.errors?.invalidValue ? typeof options.errors.invalidValue === "function" ? options.errors.invalidValue(state.error) : options.errors.invalidValue : message`${optionNames(optionNames$1)}: ${state.error}`
|
|
200
|
+
};
|
|
201
|
+
},
|
|
202
|
+
getDocFragments(_state, defaultValue) {
|
|
203
|
+
const fragments = [{
|
|
204
|
+
type: "entry",
|
|
205
|
+
term: {
|
|
206
|
+
type: "option",
|
|
207
|
+
names: optionNames$1,
|
|
208
|
+
metavar: valueParser?.metavar
|
|
209
|
+
},
|
|
210
|
+
description: options.description,
|
|
211
|
+
default: defaultValue != null && valueParser != null ? message`${valueParser.format(defaultValue)}` : void 0
|
|
212
|
+
}];
|
|
213
|
+
return {
|
|
214
|
+
fragments,
|
|
215
|
+
description: options.description
|
|
216
|
+
};
|
|
217
|
+
},
|
|
218
|
+
[Symbol.for("Deno.customInspect")]() {
|
|
219
|
+
return `option(${optionNames$1.map((o) => JSON.stringify(o)).join(", ")})`;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Creates a parser for command-line flags that must be explicitly provided.
|
|
225
|
+
* Unlike {@link option}, this parser fails if the flag is not present, making
|
|
226
|
+
* it suitable for required boolean flags that don't have a meaningful default.
|
|
227
|
+
*
|
|
228
|
+
* The key difference from {@link option} is:
|
|
229
|
+
* - {@link option} without a value parser: Returns `false` when not present
|
|
230
|
+
* - {@link flag}: Fails parsing when not present, only produces `true`
|
|
231
|
+
*
|
|
232
|
+
* This is useful for dependent options where the presence of a flag changes
|
|
233
|
+
* the shape of the result type.
|
|
234
|
+
*
|
|
235
|
+
* @param args The {@link OptionName}s to parse, followed by an optional
|
|
236
|
+
* {@link FlagOptions} object that allows you to specify
|
|
237
|
+
* a description or other metadata.
|
|
238
|
+
* @returns A {@link Parser} that produces `true` when the flag is present
|
|
239
|
+
* and fails when it is not present.
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```typescript
|
|
243
|
+
* // Basic flag usage
|
|
244
|
+
* const parser = flag("-f", "--force");
|
|
245
|
+
* // Succeeds with true: parse(parser, ["-f"])
|
|
246
|
+
* // Fails: parse(parser, [])
|
|
247
|
+
*
|
|
248
|
+
* // With description
|
|
249
|
+
* const verboseFlag = flag("-v", "--verbose", {
|
|
250
|
+
* description: "Enable verbose output"
|
|
251
|
+
* });
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
function flag(...args) {
|
|
255
|
+
const lastArg = args.at(-1);
|
|
256
|
+
let optionNames$1;
|
|
257
|
+
let options = {};
|
|
258
|
+
if (typeof lastArg === "object" && lastArg != null && !Array.isArray(lastArg)) {
|
|
259
|
+
options = lastArg;
|
|
260
|
+
optionNames$1 = args.slice(0, -1);
|
|
261
|
+
} else optionNames$1 = args;
|
|
262
|
+
return {
|
|
263
|
+
$valueType: [],
|
|
264
|
+
$stateType: [],
|
|
265
|
+
priority: 10,
|
|
266
|
+
usage: [{
|
|
267
|
+
type: "option",
|
|
268
|
+
names: optionNames$1
|
|
269
|
+
}],
|
|
270
|
+
initialState: void 0,
|
|
271
|
+
parse(context) {
|
|
272
|
+
if (context.optionsTerminated) return {
|
|
273
|
+
success: false,
|
|
274
|
+
consumed: 0,
|
|
275
|
+
error: options.errors?.optionsTerminated ?? message`No more options can be parsed.`
|
|
276
|
+
};
|
|
277
|
+
else if (context.buffer.length < 1) return {
|
|
278
|
+
success: false,
|
|
279
|
+
consumed: 0,
|
|
280
|
+
error: options.errors?.endOfInput ?? message`Expected an option, but got end of input.`
|
|
281
|
+
};
|
|
282
|
+
if (context.buffer[0] === "--") return {
|
|
283
|
+
success: true,
|
|
284
|
+
next: {
|
|
285
|
+
...context,
|
|
286
|
+
buffer: context.buffer.slice(1),
|
|
287
|
+
state: context.state,
|
|
288
|
+
optionsTerminated: true
|
|
289
|
+
},
|
|
290
|
+
consumed: context.buffer.slice(0, 1)
|
|
291
|
+
};
|
|
292
|
+
if (optionNames$1.includes(context.buffer[0])) {
|
|
293
|
+
if (context.state?.success) return {
|
|
294
|
+
success: false,
|
|
295
|
+
consumed: 1,
|
|
296
|
+
error: options.errors?.duplicate ? typeof options.errors.duplicate === "function" ? options.errors.duplicate(context.buffer[0]) : options.errors.duplicate : message`${optionName(context.buffer[0])} cannot be used multiple times.`
|
|
297
|
+
};
|
|
298
|
+
return {
|
|
299
|
+
success: true,
|
|
300
|
+
next: {
|
|
301
|
+
...context,
|
|
302
|
+
state: {
|
|
303
|
+
success: true,
|
|
304
|
+
value: true
|
|
305
|
+
},
|
|
306
|
+
buffer: context.buffer.slice(1)
|
|
307
|
+
},
|
|
308
|
+
consumed: context.buffer.slice(0, 1)
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const prefixes = optionNames$1.filter((name) => name.startsWith("--") || name.startsWith("/")).map((name) => name.startsWith("/") ? `${name}:` : `${name}=`);
|
|
312
|
+
for (const prefix of prefixes) if (context.buffer[0].startsWith(prefix)) {
|
|
313
|
+
const value = context.buffer[0].slice(prefix.length);
|
|
314
|
+
return {
|
|
315
|
+
success: false,
|
|
316
|
+
consumed: 1,
|
|
317
|
+
error: message`Flag ${optionName(prefix.slice(0, -1))} does not accept a value, but got: ${value}.`
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
const shortOptions = optionNames$1.filter((name) => name.match(/^-[^-]$/));
|
|
321
|
+
for (const shortOption of shortOptions) {
|
|
322
|
+
if (!context.buffer[0].startsWith(shortOption)) continue;
|
|
323
|
+
if (context.state?.success) return {
|
|
324
|
+
success: false,
|
|
325
|
+
consumed: 1,
|
|
326
|
+
error: options.errors?.duplicate ? typeof options.errors.duplicate === "function" ? options.errors.duplicate(shortOption) : options.errors.duplicate : message`${optionName(shortOption)} cannot be used multiple times.`
|
|
327
|
+
};
|
|
328
|
+
return {
|
|
329
|
+
success: true,
|
|
330
|
+
next: {
|
|
331
|
+
...context,
|
|
332
|
+
state: {
|
|
333
|
+
success: true,
|
|
334
|
+
value: true
|
|
335
|
+
},
|
|
336
|
+
buffer: [`-${context.buffer[0].slice(2)}`, ...context.buffer.slice(1)]
|
|
337
|
+
},
|
|
338
|
+
consumed: [context.buffer[0].slice(0, 2)]
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
return {
|
|
342
|
+
success: false,
|
|
343
|
+
consumed: 0,
|
|
344
|
+
error: message`No matched option for ${optionName(context.buffer[0])}.`
|
|
345
|
+
};
|
|
346
|
+
},
|
|
347
|
+
complete(state) {
|
|
348
|
+
if (state == null) return {
|
|
349
|
+
success: false,
|
|
350
|
+
error: options.errors?.missing ? typeof options.errors.missing === "function" ? options.errors.missing(optionNames$1) : options.errors.missing : message`Required flag ${optionNames(optionNames$1)} is missing.`
|
|
351
|
+
};
|
|
352
|
+
if (state.success) return {
|
|
353
|
+
success: true,
|
|
354
|
+
value: true
|
|
355
|
+
};
|
|
356
|
+
return {
|
|
357
|
+
success: false,
|
|
358
|
+
error: message`${optionNames(optionNames$1)}: ${state.error}`
|
|
359
|
+
};
|
|
360
|
+
},
|
|
361
|
+
getDocFragments(_state, _defaultValue) {
|
|
362
|
+
const fragments = [{
|
|
363
|
+
type: "entry",
|
|
364
|
+
term: {
|
|
365
|
+
type: "option",
|
|
366
|
+
names: optionNames$1
|
|
367
|
+
},
|
|
368
|
+
description: options.description
|
|
369
|
+
}];
|
|
370
|
+
return {
|
|
371
|
+
fragments,
|
|
372
|
+
description: options.description
|
|
373
|
+
};
|
|
374
|
+
},
|
|
375
|
+
[Symbol.for("Deno.customInspect")]() {
|
|
376
|
+
return `flag(${optionNames$1.map((o) => JSON.stringify(o)).join(", ")})`;
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Creates a parser that expects a single argument value.
|
|
382
|
+
* This parser is typically used for positional arguments
|
|
383
|
+
* that are not options or flags.
|
|
384
|
+
* @template T The type of the value produced by the parser.
|
|
385
|
+
* @param valueParser The {@link ValueParser} that defines how to parse
|
|
386
|
+
* the argument value.
|
|
387
|
+
* @param options Optional configuration for the argument parser,
|
|
388
|
+
* allowing you to specify a description or other metadata.
|
|
389
|
+
* @returns A {@link Parser} that expects a single argument value and produces
|
|
390
|
+
* the parsed value of type {@link T}.
|
|
391
|
+
*/
|
|
392
|
+
function argument(valueParser, options = {}) {
|
|
393
|
+
const optionPattern = /^--?[a-z0-9-]+$/i;
|
|
394
|
+
const term = {
|
|
395
|
+
type: "argument",
|
|
396
|
+
metavar: valueParser.metavar
|
|
397
|
+
};
|
|
398
|
+
return {
|
|
399
|
+
$valueType: [],
|
|
400
|
+
$stateType: [],
|
|
401
|
+
priority: 5,
|
|
402
|
+
usage: [term],
|
|
403
|
+
initialState: void 0,
|
|
404
|
+
parse(context) {
|
|
405
|
+
if (context.buffer.length < 1) return {
|
|
406
|
+
success: false,
|
|
407
|
+
consumed: 0,
|
|
408
|
+
error: options.errors?.endOfInput ?? message`Expected an argument, but got end of input.`
|
|
409
|
+
};
|
|
410
|
+
let i = 0;
|
|
411
|
+
let optionsTerminated = context.optionsTerminated;
|
|
412
|
+
if (!optionsTerminated) {
|
|
413
|
+
if (context.buffer[i] === "--") {
|
|
414
|
+
optionsTerminated = true;
|
|
415
|
+
i++;
|
|
416
|
+
} else if (context.buffer[i].match(optionPattern)) return {
|
|
417
|
+
success: false,
|
|
418
|
+
consumed: i,
|
|
419
|
+
error: message`Expected an argument, but got an option: ${optionName(context.buffer[i])}.`
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
if (context.buffer.length < i + 1) return {
|
|
423
|
+
success: false,
|
|
424
|
+
consumed: i,
|
|
425
|
+
error: message`Expected an argument, but got end of input.`
|
|
426
|
+
};
|
|
427
|
+
if (context.state != null) return {
|
|
428
|
+
success: false,
|
|
429
|
+
consumed: i,
|
|
430
|
+
error: options.errors?.multiple ? typeof options.errors.multiple === "function" ? options.errors.multiple(valueParser.metavar) : options.errors.multiple : message`The argument ${metavar(valueParser.metavar)} cannot be used multiple times.`
|
|
431
|
+
};
|
|
432
|
+
const result = valueParser.parse(context.buffer[i]);
|
|
433
|
+
return {
|
|
434
|
+
success: true,
|
|
435
|
+
next: {
|
|
436
|
+
...context,
|
|
437
|
+
buffer: context.buffer.slice(i + 1),
|
|
438
|
+
state: result,
|
|
439
|
+
optionsTerminated
|
|
440
|
+
},
|
|
441
|
+
consumed: context.buffer.slice(0, i + 1)
|
|
442
|
+
};
|
|
443
|
+
},
|
|
444
|
+
complete(state) {
|
|
445
|
+
if (state == null) return {
|
|
446
|
+
success: false,
|
|
447
|
+
error: options.errors?.endOfInput ?? message`Expected a ${metavar(valueParser.metavar)}, but too few arguments.`
|
|
448
|
+
};
|
|
449
|
+
else if (state.success) return state;
|
|
450
|
+
return {
|
|
451
|
+
success: false,
|
|
452
|
+
error: options.errors?.invalidValue ? typeof options.errors.invalidValue === "function" ? options.errors.invalidValue(state.error) : options.errors.invalidValue : message`${metavar(valueParser.metavar)}: ${state.error}`
|
|
453
|
+
};
|
|
454
|
+
},
|
|
455
|
+
getDocFragments(_state, defaultValue) {
|
|
456
|
+
const fragments = [{
|
|
457
|
+
type: "entry",
|
|
458
|
+
term,
|
|
459
|
+
description: options.description,
|
|
460
|
+
default: defaultValue == null ? void 0 : message`${valueParser.format(defaultValue)}`
|
|
461
|
+
}];
|
|
462
|
+
return {
|
|
463
|
+
fragments,
|
|
464
|
+
description: options.description
|
|
465
|
+
};
|
|
466
|
+
},
|
|
467
|
+
[Symbol.for("Deno.customInspect")]() {
|
|
468
|
+
return `argument()`;
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Creates a parser that matches a specific subcommand name and then applies
|
|
474
|
+
* an inner parser to the remaining arguments.
|
|
475
|
+
* This is useful for building CLI tools with subcommands like git, npm, etc.
|
|
476
|
+
* @template T The type of the value returned by the inner parser.
|
|
477
|
+
* @template TState The type of the state used by the inner parser.
|
|
478
|
+
* @param name The subcommand name to match (e.g., `"show"`, `"edit"`).
|
|
479
|
+
* @param parser The {@link Parser} to apply after the command is matched.
|
|
480
|
+
* @param options Optional configuration for the command parser, such as
|
|
481
|
+
* a description for documentation.
|
|
482
|
+
* @returns A {@link Parser} that matches the command name and delegates
|
|
483
|
+
* to the inner parser for the remaining arguments.
|
|
484
|
+
*/
|
|
485
|
+
function command(name, parser, options = {}) {
|
|
486
|
+
return {
|
|
487
|
+
$valueType: [],
|
|
488
|
+
$stateType: [],
|
|
489
|
+
priority: 15,
|
|
490
|
+
usage: [{
|
|
491
|
+
type: "command",
|
|
492
|
+
name
|
|
493
|
+
}, ...parser.usage],
|
|
494
|
+
initialState: void 0,
|
|
495
|
+
parse(context) {
|
|
496
|
+
if (context.state === void 0) {
|
|
497
|
+
if (context.buffer.length < 1 || context.buffer[0] !== name) {
|
|
498
|
+
const actual = context.buffer.length > 0 ? context.buffer[0] : null;
|
|
499
|
+
const errorMessage = options.errors?.notMatched ?? message`Expected command ${optionName(name)}, but got ${actual ?? "end of input"}.`;
|
|
500
|
+
return {
|
|
501
|
+
success: false,
|
|
502
|
+
consumed: 0,
|
|
503
|
+
error: typeof errorMessage === "function" ? errorMessage(name, actual) : errorMessage
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
return {
|
|
507
|
+
success: true,
|
|
508
|
+
next: {
|
|
509
|
+
...context,
|
|
510
|
+
buffer: context.buffer.slice(1),
|
|
511
|
+
state: ["matched", name]
|
|
512
|
+
},
|
|
513
|
+
consumed: context.buffer.slice(0, 1)
|
|
514
|
+
};
|
|
515
|
+
} else if (context.state[0] === "matched") {
|
|
516
|
+
const result = parser.parse({
|
|
517
|
+
...context,
|
|
518
|
+
state: parser.initialState
|
|
519
|
+
});
|
|
520
|
+
if (result.success) return {
|
|
521
|
+
success: true,
|
|
522
|
+
next: {
|
|
523
|
+
...result.next,
|
|
524
|
+
state: ["parsing", result.next.state]
|
|
525
|
+
},
|
|
526
|
+
consumed: result.consumed
|
|
527
|
+
};
|
|
528
|
+
return result;
|
|
529
|
+
} else if (context.state[0] === "parsing") {
|
|
530
|
+
const result = parser.parse({
|
|
531
|
+
...context,
|
|
532
|
+
state: context.state[1]
|
|
533
|
+
});
|
|
534
|
+
if (result.success) return {
|
|
535
|
+
success: true,
|
|
536
|
+
next: {
|
|
537
|
+
...result.next,
|
|
538
|
+
state: ["parsing", result.next.state]
|
|
539
|
+
},
|
|
540
|
+
consumed: result.consumed
|
|
541
|
+
};
|
|
542
|
+
return result;
|
|
543
|
+
}
|
|
544
|
+
return {
|
|
545
|
+
success: false,
|
|
546
|
+
consumed: 0,
|
|
547
|
+
error: options.errors?.invalidState ?? message`Invalid command state.`
|
|
548
|
+
};
|
|
549
|
+
},
|
|
550
|
+
complete(state) {
|
|
551
|
+
if (typeof state === "undefined") return {
|
|
552
|
+
success: false,
|
|
553
|
+
error: options.errors?.notFound ?? message`Command ${optionName(name)} was not matched.`
|
|
554
|
+
};
|
|
555
|
+
else if (state[0] === "matched") return parser.complete(parser.initialState);
|
|
556
|
+
else if (state[0] === "parsing") return parser.complete(state[1]);
|
|
557
|
+
return {
|
|
558
|
+
success: false,
|
|
559
|
+
error: options.errors?.invalidState ?? message`Invalid command state during completion.`
|
|
560
|
+
};
|
|
561
|
+
},
|
|
562
|
+
getDocFragments(state, defaultValue) {
|
|
563
|
+
if (state.kind === "unavailable" || typeof state.state === "undefined") return {
|
|
564
|
+
description: options.description,
|
|
565
|
+
fragments: [{
|
|
566
|
+
type: "entry",
|
|
567
|
+
term: {
|
|
568
|
+
type: "command",
|
|
569
|
+
name
|
|
570
|
+
},
|
|
571
|
+
description: options.description
|
|
572
|
+
}]
|
|
573
|
+
};
|
|
574
|
+
const innerState = state.state[0] === "parsing" ? {
|
|
575
|
+
kind: "available",
|
|
576
|
+
state: state.state[1]
|
|
577
|
+
} : { kind: "unavailable" };
|
|
578
|
+
const innerFragments = parser.getDocFragments(innerState, defaultValue);
|
|
579
|
+
return {
|
|
580
|
+
...innerFragments,
|
|
581
|
+
description: innerFragments.description ?? options.description
|
|
582
|
+
};
|
|
583
|
+
},
|
|
584
|
+
[Symbol.for("Deno.customInspect")]() {
|
|
585
|
+
return `command(${JSON.stringify(name)})`;
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
//#endregion
|
|
591
|
+
export { argument, command, constant, flag, option };
|