0
# Parameter Parsers
1
2
Built-in parsers for common types and utilities for building custom parsers. Supports synchronous and asynchronous parsing.
3
4
## Quick Reference
5
6
```typescript
7
// Built-in parsers
8
booleanParser // "true" | "false" (strict)
9
looseBooleanParser // "yes|no|on|off|1|0|true|false|y|n|t|f"
10
numberParser // Any number (int or float)
11
String // No-op parser (just returns string)
12
13
// Build choice parser
14
buildChoiceParser(["dev", "staging", "prod"])
15
16
// Custom parser
17
const portParser: InputParser<number> = (input) => {
18
const port = parseInt(input, 10);
19
if (isNaN(port) || port < 1 || port > 65535) {
20
throw new Error("Port must be 1-65535");
21
}
22
return port;
23
}
24
25
// Async parser with context
26
const userIdParser: InputParser<string, MyContext> = async function(input) {
27
const exists = await this.database.userExists(input);
28
if (!exists) throw new Error(`User not found: ${input}`);
29
return input;
30
}
31
```
32
33
## InputParser Type
34
35
Generic function that synchronously or asynchronously parses a string to an arbitrary type.
36
37
```typescript { .api }
38
/**
39
* Generic function for parsing string input to type T
40
* @param this - Command context (provides access to process, custom context, etc.)
41
* @param input - Raw string input from command line
42
* @returns Parsed value of type T (or Promise of T)
43
*/
44
type InputParser<T, CONTEXT extends CommandContext = CommandContext> = (
45
this: CONTEXT,
46
input: string
47
) => T | Promise<T>;
48
```
49
50
**Usage Example:**
51
52
```typescript
53
import { InputParser, buildCommand } from "@stricli/core";
54
55
// Simple synchronous parser
56
const portParser: InputParser<number> = (input: string): number => {
57
const port = parseInt(input, 10);
58
if (isNaN(port) || port < 1 || port > 65535) {
59
throw new Error(`Invalid port: ${input}`);
60
}
61
return port;
62
};
63
64
// Async parser with context
65
interface MyContext extends CommandContext {
66
configService: {
67
validatePath: (path: string) => Promise<boolean>;
68
};
69
}
70
71
const pathParser: InputParser<string, MyContext> = async function(input: string) {
72
const isValid = await this.configService.validatePath(input);
73
if (!isValid) {
74
throw new Error(`Invalid path: ${input}`);
75
}
76
return input;
77
};
78
79
const command = buildCommand({
80
func: async function(flags) {
81
this.process.stdout.write(`Listening on port ${flags.port}\n`);
82
},
83
parameters: {
84
flags: {
85
port: {
86
kind: "parsed",
87
parse: portParser,
88
brief: "Server port (1-65535)"
89
}
90
}
91
},
92
docs: {
93
brief: "Start server"
94
}
95
});
96
```
97
98
## Built-in Parsers
99
100
### booleanParser
101
102
Parses input strings as booleans. Transforms to lowercase then checks against "true" and "false". Throws for invalid inputs.
103
104
```typescript { .api }
105
/**
106
* Parses input strings as booleans (strict)
107
* @param input - Input string to parse
108
* @returns true for "true", false for "false"
109
* @throws SyntaxError if input is not "true" or "false" (case-insensitive)
110
*/
111
const booleanParser: (input: string) => boolean;
112
```
113
114
**Usage Example:**
115
116
```typescript
117
import { booleanParser, buildCommand } from "@stricli/core";
118
119
const command = buildCommand({
120
func: async function(flags) {
121
this.process.stdout.write(`Enabled: ${flags.enabled}\n`);
122
},
123
parameters: {
124
flags: {
125
enabled: {
126
kind: "parsed",
127
parse: booleanParser,
128
brief: "Enable feature (true/false)"
129
}
130
}
131
},
132
docs: {
133
brief: "Configure feature"
134
}
135
});
136
137
// Usage:
138
// myapp --enabled true
139
// myapp --enabled false
140
// myapp --enabled TRUE (case-insensitive)
141
// myapp --enabled yes (throws error - use looseBooleanParser)
142
```
143
144
### looseBooleanParser
145
146
Parses input strings as booleans loosely, accepting multiple truthy and falsy values.
147
148
```typescript { .api }
149
/**
150
* Parses input strings as booleans (loose)
151
* @param input - Input string to parse
152
* @returns true for truthy values, false for falsy values
153
* @throws SyntaxError if input doesn't match any recognized value
154
*
155
* Truthy values: "true", "t", "yes", "y", "on", "1"
156
* Falsy values: "false", "f", "no", "n", "off", "0"
157
* All comparisons are case-insensitive
158
*/
159
const looseBooleanParser: (input: string) => boolean;
160
```
161
162
**Usage Example:**
163
164
```typescript
165
import { looseBooleanParser, buildCommand } from "@stricli/core";
166
167
const command = buildCommand({
168
func: async function(flags) {
169
if (flags.confirm) {
170
this.process.stdout.write("Operation confirmed\n");
171
// Proceed with operation
172
} else {
173
this.process.stdout.write("Operation cancelled\n");
174
}
175
},
176
parameters: {
177
flags: {
178
confirm: {
179
kind: "parsed",
180
parse: looseBooleanParser,
181
brief: "Confirm operation (yes/no/on/off/1/0)"
182
}
183
}
184
},
185
docs: {
186
brief: "Process operation"
187
}
188
});
189
190
// Usage:
191
// myapp --confirm yes
192
// myapp --confirm on
193
// myapp --confirm 1
194
// myapp --confirm no
195
```
196
197
### numberParser
198
199
Parses numeric values from string input.
200
201
```typescript { .api }
202
/**
203
* Parse numeric values
204
* @param input - Input string to parse
205
* @returns Parsed number
206
* @throws SyntaxError if input cannot be parsed as a number
207
*/
208
const numberParser: (input: string) => number;
209
```
210
211
### buildChoiceParser
212
213
Builds a parser that validates against a set of choices.
214
215
```typescript { .api }
216
/**
217
* Build a parser that validates against a set of choices
218
* @param choices - Array of valid string choices
219
* @returns Parser function that validates input against choices
220
*/
221
function buildChoiceParser<T extends string>(choices: readonly T[]): InputParser<T>;
222
```
223
224
**Example:**
225
226
```typescript
227
const logLevelParser = buildChoiceParser(["debug", "info", "warn", "error"]);
228
229
flags: {
230
logLevel: {
231
kind: "parsed",
232
parse: logLevelParser,
233
brief: "Log level",
234
default: "info"
235
}
236
}
237
```
238
239
**Note:** For string literal unions, prefer `enum` flags over `parsed` with `buildChoiceParser` for better UX. Use `buildChoiceParser` when you need custom error messages, complex validation, or non-string types.
240
241
## Custom Parsers
242
243
### Simple Synchronous Parser
244
245
```typescript
246
const portParser: InputParser<number> = (input) => {
247
const port = parseInt(input, 10);
248
if (isNaN(port) || port < 1 || port > 65535) {
249
throw new Error(`Invalid port: ${input}`);
250
}
251
return port;
252
};
253
```
254
255
### Async Parser
256
257
```typescript
258
const filePathParser: InputParser<string> = async (input) => {
259
try {
260
await access(input, constants.R_OK);
261
return input;
262
} catch {
263
throw new Error(`File not found or not readable: ${input}`);
264
}
265
};
266
```
267
268
### Context-Aware Parser
269
270
```typescript
271
interface MyContext extends CommandContext {
272
database: { userExists: (id: string) => Promise<boolean> };
273
}
274
275
const userIdParser: InputParser<string, MyContext> = async function(input) {
276
if (!/^[a-zA-Z0-9_-]+$/.test(input)) {
277
throw new Error(`Invalid user ID format: ${input}`);
278
}
279
const exists = await this.database.userExists(input);
280
if (!exists) {
281
throw new Error(`User not found: ${input}`);
282
}
283
return input;
284
};
285
```
286
287
### Complex Type Parser
288
289
```typescript
290
interface Range {
291
min: number;
292
max: number;
293
}
294
295
const rangeParser: InputParser<Range> = (input) => {
296
const match = /^(\d+)-(\d+)$/.exec(input);
297
if (!match) {
298
throw new Error(`Invalid range format, expected "min-max": ${input}`);
299
}
300
const min = parseInt(match[1], 10);
301
const max = parseInt(match[2], 10);
302
if (min > max) {
303
throw new Error(`Invalid range, min must be <= max: ${input}`);
304
}
305
return { min, max };
306
};
307
```
308
309
## Common Parser Patterns
310
311
```typescript
312
// URL
313
const urlParser: InputParser<URL> = (input) => {
314
try {
315
return new URL(input);
316
} catch {
317
throw new Error(`Invalid URL: ${input}`);
318
}
319
};
320
321
// Date (ISO format)
322
const isoDateParser: InputParser<Date> = (input) => {
323
if (!/^\d{4}-\d{2}-\d{2}$/.test(input)) {
324
throw new Error(`Date must be YYYY-MM-DD: ${input}`);
325
}
326
const date = new Date(input);
327
if (isNaN(date.getTime())) {
328
throw new Error(`Invalid date: ${input}`);
329
}
330
return date;
331
};
332
333
// JSON
334
const jsonParser: InputParser<unknown> = (input) => {
335
try {
336
return JSON.parse(input);
337
} catch (err) {
338
throw new Error(`Invalid JSON: ${err.message}`);
339
}
340
};
341
342
343
const emailParser: InputParser<string> = (input) => {
344
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input)) {
345
throw new Error(`Invalid email: ${input}`);
346
}
347
return input.toLowerCase();
348
};
349
```
350
351
## Complete Example
352
353
```typescript
354
const command = buildCommand({
355
func: async function(flags) {
356
this.process.stdout.write(`Creating task:\n`);
357
this.process.stdout.write(` Title: ${flags.title}\n`);
358
this.process.stdout.write(` Assignee: ${flags.assignee}\n`);
359
if (flags.dueInDays) {
360
const due = new Date();
361
due.setDate(due.getDate() + flags.dueInDays);
362
this.process.stdout.write(` Due: ${due.toDateString()}\n`);
363
}
364
},
365
parameters: {
366
flags: {
367
title: { kind: "parsed", parse: String, brief: "Task title" },
368
assignee: { kind: "parsed", parse: emailParser, brief: "Assignee email" },
369
dueInDays: { kind: "parsed", parse: numberParser, brief: "Due date (days from now)", optional: true }
370
},
371
aliases: { t: "title", a: "assignee", d: "dueInDays" }
372
},
373
docs: { brief: "Create task" }
374
});
375
376
// Usage:
377
// myapp -t "Fix bug" -a john@example.com -d 3
378
// myapp --title "Review PR" --assignee jane@example.com
379
```
380
381
## Additional Parser Examples
382
383
### File Path Parser
384
385
```typescript
386
import { access, constants } from "fs/promises";
387
388
const filePathParser: InputParser<string> = async (input) => {
389
try {
390
await access(input, constants.R_OK);
391
return input;
392
} catch {
393
throw new Error(`File not found or not readable: ${input}`);
394
}
395
};
396
```
397
398
### URL Parser
399
400
```typescript
401
const urlParser: InputParser<URL> = (input) => {
402
try {
403
return new URL(input);
404
} catch {
405
throw new Error(`Invalid URL: ${input}`);
406
}
407
};
408
```
409
410
### Date Parser
411
412
```typescript
413
const isoDateParser: InputParser<Date> = (input) => {
414
if (!/^\d{4}-\d{2}-\d{2}$/.test(input)) {
415
throw new Error(`Date must be YYYY-MM-DD: ${input}`);
416
}
417
const date = new Date(input);
418
if (isNaN(date.getTime())) {
419
throw new Error(`Invalid date: ${input}`);
420
}
421
return date;
422
};
423
```
424
425
### JSON Parser
426
427
```typescript
428
const jsonParser: InputParser<unknown> = (input) => {
429
try {
430
return JSON.parse(input);
431
} catch (err) {
432
throw new Error(`Invalid JSON: ${err.message}`);
433
}
434
};
435
```
436
437
### Range Parser
438
439
```typescript
440
interface Range {
441
min: number;
442
max: number;
443
}
444
445
const rangeParser: InputParser<Range> = (input) => {
446
const match = /^(\d+)-(\d+)$/.exec(input);
447
if (!match) {
448
throw new Error(`Invalid range format, expected "min-max": ${input}`);
449
}
450
const min = parseInt(match[1], 10);
451
const max = parseInt(match[2], 10);
452
if (min > max) {
453
throw new Error(`Invalid range, min must be <= max: ${input}`);
454
}
455
return { min, max };
456
};
457
```
458
459
## Related Documentation
460
461
- [Flag Parameters](./flag-parameters.md) - Using parsers with flags
462
- [Positional Parameters](./positional-parameters.md) - Using parsers with positional args
463
- [Error Handling](./error-handling.md) - Error handling in parsers
464