first commit
This commit is contained in:
92
docs/devtools/tooling/cli/src/parser/parser.md
Normal file
92
docs/devtools/tooling/cli/src/parser/parser.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# parser
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
The `parser` module turns a raw `argv` token array into a structured `ParsedArgs` object with typed options, named positional arguments, and a `rest` passthrough channel. It is the low-level argument-parsing engine that higher-level command definitions build on, designed to model the behaviour CLI users expect from mature tools like `git`, `npm`, and `cargo`.
|
||||
|
||||
The module is deliberately imperative and framework-free: no reflection, no decorators, no hidden state. Every behaviour — type coercion, `--no-*` negation, short-flag resolution, choice validation, required-option checking — is enforced in a single linear pass over `argv`, and every failure mode raises a `CliError` with a stable code drawn from `CLI_CODES.PARSE.*` so callers can branch on machine-readable codes instead of message strings.
|
||||
|
||||
## Features
|
||||
|
||||
- Boolean flags (`--verbose`) and negation (`--no-verbose`)
|
||||
- String/number/array options via `--key value`, `--key=value`, or short-flag (`-v`) forms
|
||||
- Short-flag mapping via `short: "v"` on the option config
|
||||
- Passthrough `rest` channel after `--`
|
||||
- Variadic positionals (`<files...>`) collected as arrays
|
||||
- Required positionals and required options with dedicated error codes
|
||||
- Choice validation with `INVALID_VALUE`
|
||||
- `allowUnknown: true` to silently ignore unrecognised flags
|
||||
- Defaults for options and positionals
|
||||
- Helper parsers for flag specs (`parseOptionFlags`) and positional specs (`parseArgumentSpec`)
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { parseArgs } from "@suaveplan/cli/parser";
|
||||
|
||||
const result = parseArgs(["--verbose", "src/file.ts"], {
|
||||
options: { verbose: { type: "boolean" } },
|
||||
arguments: [{ name: "file", required: true, variadic: false }],
|
||||
});
|
||||
|
||||
console.log(result.options.verbose); // true
|
||||
console.log(result.positionals.file); // "src/file.ts"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { parseArgs, parseOptionFlags, parseArgumentSpec } from "@suaveplan/cli/parser";
|
||||
|
||||
const flag = parseOptionFlags("-o, --output <file>", "Output file");
|
||||
const posArg = parseArgumentSpec("<sources...>", "Source files");
|
||||
|
||||
const result = parseArgs(
|
||||
["-o", "out.txt", "--format=json", "a.ts", "b.ts", "--", "--not-a-flag"],
|
||||
{
|
||||
options: {
|
||||
[flag.name]: flag.config,
|
||||
format: { type: "string", choices: ["json", "text"], default: "text" },
|
||||
retries: { type: "number", default: 3 },
|
||||
},
|
||||
arguments: [posArg],
|
||||
}
|
||||
);
|
||||
|
||||
// result.options.output === "out.txt"
|
||||
// result.options.format === "json"
|
||||
// result.positionals.sources === ["a.ts", "b.ts"]
|
||||
// result.rest === ["--not-a-flag"]
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `parseArgs(argv, config?)`
|
||||
|
||||
- `argv: string[]` — raw CLI tokens (without the node/bun prefix)
|
||||
- `config?: ParserConfig` — optional options/arguments definitions, `allowUnknown`, `stopEarly`
|
||||
- Returns `ParsedArgs` with `options`, `positionals`, and `rest`
|
||||
- Throws `CliError` with codes `UNKNOWN_OPTION`, `MISSING_VALUE`, `INVALID_VALUE`, `MISSING_ARGUMENT`, `MISSING_REQUIRED_OPTION`
|
||||
|
||||
### `parseOptionFlags(flags, description?, defaultValue?)`
|
||||
|
||||
- `flags: string` — commander-style spec, e.g. `"-v, --verbose"` or `"--output <file>"`
|
||||
- Returns `{ name, config }` where `name` is the camelCased long-flag name
|
||||
- Throws `CliError(UNKNOWN_OPTION)` if no long flag is provided
|
||||
|
||||
### `parseArgumentSpec(spec, description?)`
|
||||
|
||||
- `spec: string` — `"<name>"`, `"[name]"`, `"<name...>"`, or `"[name...]"`
|
||||
- Returns an `ArgumentConfig` with `required`, `variadic`, `name`, and optional `description`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- Single-pass state machine: tokens are consumed left-to-right with a small `restMode` flag that flips when `--` is encountered. There are no lookaheads beyond peeking at `argv[i + 1]` to decide whether a flag's value is the next token.
|
||||
- `--no-*` handling strips the literal `--no-` prefix (not the camelCased form) and then camelCases the remainder, so `--no-dry-run` correctly maps to the `dryRun` option.
|
||||
- Values are only consumed as the next token when that token does not start with `-`, preventing `--foo --bar` from stealing `--bar` as the value of `--foo`.
|
||||
- `number` type uses `Number(raw)` and rejects `NaN` so strings like `"abc"` raise `INVALID_VALUE` rather than silently becoming `NaN`.
|
||||
- All errors are `CliError` instances carrying `context` metadata (option name, offending value, allowed choices) so UIs and logs can render actionable diagnostics without re-parsing message strings.
|
||||
- Schemas mirror the TypeScript types exactly and are exported separately so callers can validate untrusted `ParserConfig` inputs at the boundary without pulling Zod into hot paths.
|
||||
Reference in New Issue
Block a user