or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application.mdcommands-and-routing.mdconfiguration-and-context.mddocumentation-and-help.mderror-handling.mdexit-codes.mdflag-parameters.mdindex.mdparameter-parsers.mdpositional-parameters.mdtext-and-localization.md

parameter-parsers.mddocs/

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

// Email

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