CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-stricli--core

Build complex CLIs with type safety and no dependencies

Overview
Eval results
Files

parameter-parsers.mddocs/

Parameter Parsers

Built-in parsers for common types and utilities for building custom parsers. Supports synchronous and asynchronous parsing.

Quick Reference

// Built-in parsers
booleanParser          // "true" | "false" (strict)
looseBooleanParser     // "yes|no|on|off|1|0|true|false|y|n|t|f"
numberParser           // Any number (int or float)
String                 // No-op parser (just returns string)

// Build choice parser
buildChoiceParser(["dev", "staging", "prod"])

// Custom parser
const portParser: InputParser<number> = (input) => {
  const port = parseInt(input, 10);
  if (isNaN(port) || port < 1 || port > 65535) {
    throw new Error("Port must be 1-65535");
  }
  return port;
}

// Async parser with context
const userIdParser: InputParser<string, MyContext> = async function(input) {
  const exists = await this.database.userExists(input);
  if (!exists) throw new Error(`User not found: ${input}`);
  return input;
}

InputParser Type

Generic function that synchronously or asynchronously parses a string to an arbitrary type.

/**
 * Generic function for parsing string input to type T
 * @param this - Command context (provides access to process, custom context, etc.)
 * @param input - Raw string input from command line
 * @returns Parsed value of type T (or Promise of T)
 */
type InputParser<T, CONTEXT extends CommandContext = CommandContext> = (
  this: CONTEXT,
  input: string
) => T | Promise<T>;

Usage Example:

import { InputParser, buildCommand } from "@stricli/core";

// Simple synchronous parser
const portParser: InputParser<number> = (input: string): number => {
  const port = parseInt(input, 10);
  if (isNaN(port) || port < 1 || port > 65535) {
    throw new Error(`Invalid port: ${input}`);
  }
  return port;
};

// Async parser with context
interface MyContext extends CommandContext {
  configService: {
    validatePath: (path: string) => Promise<boolean>;
  };
}

const pathParser: InputParser<string, MyContext> = async function(input: string) {
  const isValid = await this.configService.validatePath(input);
  if (!isValid) {
    throw new Error(`Invalid path: ${input}`);
  }
  return input;
};

const command = buildCommand({
  func: async function(flags) {
    this.process.stdout.write(`Listening on port ${flags.port}\n`);
  },
  parameters: {
    flags: {
      port: {
        kind: "parsed",
        parse: portParser,
        brief: "Server port (1-65535)"
      }
    }
  },
  docs: {
    brief: "Start server"
  }
});

Built-in Parsers

booleanParser

Parses input strings as booleans. Transforms to lowercase then checks against "true" and "false". Throws for invalid inputs.

/**
 * Parses input strings as booleans (strict)
 * @param input - Input string to parse
 * @returns true for "true", false for "false"
 * @throws SyntaxError if input is not "true" or "false" (case-insensitive)
 */
const booleanParser: (input: string) => boolean;

Usage Example:

import { booleanParser, buildCommand } from "@stricli/core";

const command = buildCommand({
  func: async function(flags) {
    this.process.stdout.write(`Enabled: ${flags.enabled}\n`);
  },
  parameters: {
    flags: {
      enabled: {
        kind: "parsed",
        parse: booleanParser,
        brief: "Enable feature (true/false)"
      }
    }
  },
  docs: {
    brief: "Configure feature"
  }
});

// Usage:
// myapp --enabled true
// myapp --enabled false
// myapp --enabled TRUE  (case-insensitive)
// myapp --enabled yes   (throws error - use looseBooleanParser)

looseBooleanParser

Parses input strings as booleans loosely, accepting multiple truthy and falsy values.

/**
 * Parses input strings as booleans (loose)
 * @param input - Input string to parse
 * @returns true for truthy values, false for falsy values
 * @throws SyntaxError if input doesn't match any recognized value
 *
 * Truthy values: "true", "t", "yes", "y", "on", "1"
 * Falsy values: "false", "f", "no", "n", "off", "0"
 * All comparisons are case-insensitive
 */
const looseBooleanParser: (input: string) => boolean;

Usage Example:

import { looseBooleanParser, buildCommand } from "@stricli/core";

const command = buildCommand({
  func: async function(flags) {
    if (flags.confirm) {
      this.process.stdout.write("Operation confirmed\n");
      // Proceed with operation
    } else {
      this.process.stdout.write("Operation cancelled\n");
    }
  },
  parameters: {
    flags: {
      confirm: {
        kind: "parsed",
        parse: looseBooleanParser,
        brief: "Confirm operation (yes/no/on/off/1/0)"
      }
    }
  },
  docs: {
    brief: "Process operation"
  }
});

// Usage:
// myapp --confirm yes
// myapp --confirm on
// myapp --confirm 1
// myapp --confirm no

numberParser

Parses numeric values from string input.

/**
 * Parse numeric values
 * @param input - Input string to parse
 * @returns Parsed number
 * @throws SyntaxError if input cannot be parsed as a number
 */
const numberParser: (input: string) => number;

buildChoiceParser

Builds a parser that validates against a set of choices.

/**
 * Build a parser that validates against a set of choices
 * @param choices - Array of valid string choices
 * @returns Parser function that validates input against choices
 */
function buildChoiceParser<T extends string>(choices: readonly T[]): InputParser<T>;

Example:

const logLevelParser = buildChoiceParser(["debug", "info", "warn", "error"]);

flags: {
  logLevel: {
    kind: "parsed",
    parse: logLevelParser,
    brief: "Log level",
    default: "info"
  }
}

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.

Custom Parsers

Simple Synchronous Parser

const portParser: InputParser<number> = (input) => {
  const port = parseInt(input, 10);
  if (isNaN(port) || port < 1 || port > 65535) {
    throw new Error(`Invalid port: ${input}`);
  }
  return port;
};

Async Parser

const filePathParser: InputParser<string> = async (input) => {
  try {
    await access(input, constants.R_OK);
    return input;
  } catch {
    throw new Error(`File not found or not readable: ${input}`);
  }
};

Context-Aware Parser

interface MyContext extends CommandContext {
  database: { userExists: (id: string) => Promise<boolean> };
}

const userIdParser: InputParser<string, MyContext> = async function(input) {
  if (!/^[a-zA-Z0-9_-]+$/.test(input)) {
    throw new Error(`Invalid user ID format: ${input}`);
  }
  const exists = await this.database.userExists(input);
  if (!exists) {
    throw new Error(`User not found: ${input}`);
  }
  return input;
};

Complex Type Parser

interface Range {
  min: number;
  max: number;
}

const rangeParser: InputParser<Range> = (input) => {
  const match = /^(\d+)-(\d+)$/.exec(input);
  if (!match) {
    throw new Error(`Invalid range format, expected "min-max": ${input}`);
  }
  const min = parseInt(match[1], 10);
  const max = parseInt(match[2], 10);
  if (min > max) {
    throw new Error(`Invalid range, min must be <= max: ${input}`);
  }
  return { min, max };
};

Common Parser Patterns

// URL
const urlParser: InputParser<URL> = (input) => {
  try {
    return new URL(input);
  } catch {
    throw new Error(`Invalid URL: ${input}`);
  }
};

// Date (ISO format)
const isoDateParser: InputParser<Date> = (input) => {
  if (!/^\d{4}-\d{2}-\d{2}$/.test(input)) {
    throw new Error(`Date must be YYYY-MM-DD: ${input}`);
  }
  const date = new Date(input);
  if (isNaN(date.getTime())) {
    throw new Error(`Invalid date: ${input}`);
  }
  return date;
};

// JSON
const jsonParser: InputParser<unknown> = (input) => {
  try {
    return JSON.parse(input);
  } catch (err) {
    throw new Error(`Invalid JSON: ${err.message}`);
  }
};

// Email
const emailParser: InputParser<string> = (input) => {
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input)) {
    throw new Error(`Invalid email: ${input}`);
  }
  return input.toLowerCase();
};

Complete Example

const command = buildCommand({
  func: async function(flags) {
    this.process.stdout.write(`Creating task:\n`);
    this.process.stdout.write(`  Title: ${flags.title}\n`);
    this.process.stdout.write(`  Assignee: ${flags.assignee}\n`);
    if (flags.dueInDays) {
      const due = new Date();
      due.setDate(due.getDate() + flags.dueInDays);
      this.process.stdout.write(`  Due: ${due.toDateString()}\n`);
    }
  },
  parameters: {
    flags: {
      title: { kind: "parsed", parse: String, brief: "Task title" },
      assignee: { kind: "parsed", parse: emailParser, brief: "Assignee email" },
      dueInDays: { kind: "parsed", parse: numberParser, brief: "Due date (days from now)", optional: true }
    },
    aliases: { t: "title", a: "assignee", d: "dueInDays" }
  },
  docs: { brief: "Create task" }
});

// Usage:
// myapp -t "Fix bug" -a john@example.com -d 3
// myapp --title "Review PR" --assignee jane@example.com

Additional Parser Examples

File Path Parser

import { access, constants } from "fs/promises";

const filePathParser: InputParser<string> = async (input) => {
  try {
    await access(input, constants.R_OK);
    return input;
  } catch {
    throw new Error(`File not found or not readable: ${input}`);
  }
};

URL Parser

const urlParser: InputParser<URL> = (input) => {
  try {
    return new URL(input);
  } catch {
    throw new Error(`Invalid URL: ${input}`);
  }
};

Date Parser

const isoDateParser: InputParser<Date> = (input) => {
  if (!/^\d{4}-\d{2}-\d{2}$/.test(input)) {
    throw new Error(`Date must be YYYY-MM-DD: ${input}`);
  }
  const date = new Date(input);
  if (isNaN(date.getTime())) {
    throw new Error(`Invalid date: ${input}`);
  }
  return date;
};

JSON Parser

const jsonParser: InputParser<unknown> = (input) => {
  try {
    return JSON.parse(input);
  } catch (err) {
    throw new Error(`Invalid JSON: ${err.message}`);
  }
};

Range Parser

interface Range {
  min: number;
  max: number;
}

const rangeParser: InputParser<Range> = (input) => {
  const match = /^(\d+)-(\d+)$/.exec(input);
  if (!match) {
    throw new Error(`Invalid range format, expected "min-max": ${input}`);
  }
  const min = parseInt(match[1], 10);
  const max = parseInt(match[2], 10);
  if (min > max) {
    throw new Error(`Invalid range, min must be <= max: ${input}`);
  }
  return { min, max };
};

Related Documentation

  • Flag Parameters - Using parsers with flags
  • Positional Parameters - Using parsers with positional args
  • Error Handling - Error handling in parsers

Install with Tessl CLI

npx tessl i tessl/npm-stricli--core

docs

application.md

commands-and-routing.md

configuration-and-context.md

documentation-and-help.md

error-handling.md

exit-codes.md

flag-parameters.md

index.md

parameter-parsers.md

positional-parameters.md

text-and-localization.md

tile.json