These libraries are experimental. APIs may change without notice. Generated from source with koruc 0.1.7 on 11/4/2026.
Commander
@korulang/commander@0.0.1Command-line argument parsing for Koru — declarative flags, positionals, generated help, variant-arm parse outcomes
commander/index.kz · 11 tors
@korulang/commander — declarative command-line argument parsing · 46 more lines
@korulang/commander — declarative command-line argument parsing
Commander.js for Koru: a program declares its flags and positionals
once, `program.parse` matches argv against the spec, and the result
answers queries by name. Parse outcomes are variant arms — `ok`,
`help`, `version`, `err` — never a quiet exit.
import koru/commander
koru/commander:program.new(name: "app", version: "1.0.0",
description: "does a thing"): p
|> koru/commander:program.flag(p, long: "verbose", kind: "bool",
short: "v", description: "", required: false)
|> koru/commander:program.flag(p, long: "output", kind: "string",
short: "o", description: "", required: false)
|> koru/commander:program.parse(p)
| ok r |> ...
| help h |> std/io:print(h)
| version v |> std/io:print.ln(v)
| err e |> std/io:print.ln(e)
Accepted spellings on argv: `--name`, `--name=v`, `-n`, `-n=v`,
`-n v`, `--` (rest is positional). `-h`/`--help` and `-V`/`--version`
are built in — a declared flag of the same name wins. Value-less bool
flags never consume the next token. Stacked shorts (`-nv`) and glued
values (`-nvalue`) are refused as unknown options.
OWNERSHIP: string params are borrowed, not duped — pass literals or
strings that outlive the Spec. `*Result` borrows its Spec — discharge
`result.free` BEFORE `program.free`. The `help`/`err` arm texts are
slices into the Spec's scratch buffer; each program.* call may
rewrite them.
PIPE RULE: a `*Spec<live!>` result cannot ride a `|>` — no input
param can spell `live!`, and `live!` does not type-match `live`.
Bind the spec (`: p`) and pass it by name to builders; a piped
builder call must have every parameter written explicitly — a
defaulted trailing param reads to the compiler as the pipe's
target (KORU092 teaches this same spelling).
SPEC BUGS ARE LOUD: a bad `kind`, a two-char `short`, an overflowing
spec, or `result.int` on a non-int flag abort the program — these are
author errors, not user errors, so they panic rather than err.
NOT LIFTED (v0): subcommands, defaults, stacked/negated bools, multi
flags, env binding, and the compile-time declaration DSL.
Phantom lifecycles
Derived from the phantom labels in the declarations below — state! issues an
obligation the compiler will chase, !state discharges it, a bare state holds it without moving it. Nothing here is hand-drawn.
Spec 1 state live!Result 1 state live!// program.new — mint the spec handle
~pub tor program.new {
name: string,
version: string,
description: string
} -> *Spec<live!>// program.flag — declare an option. `kind` is "bool" | "string" | "int".
// `short` is "" (none) or one character. Bool flags take no value.
~pub tor program.flag {
p: *Spec<live>,
long: string,
kind: string,
short: string,
description: string,
required: bool
}// program.positional — declare a named positional, filled in argv order.
~pub tor program.positional {
p: *Spec<live>,
name: string,
description: string,
required: bool
}~pub tor program.help { p: *Spec<live> } -> string// program.parse — match the process argv against the spec. Reads
// std.os.argv directly (skipping argv[0]) — the same zero-alloc span
// philosophy as std/args. Scans into stack arrays; the Result is
// allocated only on `ok`, so err and help paths allocate nothing.
~pub tor program.parse { p: *Spec<live> }
| ok *Result<live!>
| help string
| version string
| err string// program.free — discharge the spec. Free the Result first.
~pub tor program.free { p: *Spec<!live> }// result queries — name-keyed answers off the parse.
// `get` hands back the raw text (int flags included); `int` hands back
// the parsed i64 and refuses a non-int name; `has` is presence.
~pub tor result.get { r: *Result<live>, name: string }
| value string
| missing
| unknown~pub tor result.int { r: *Result<live>, name: string }
| value i64
| missing
| unknown~pub tor result.has { r: *Result<live>, name: string }
| yes
| no
| unknown~pub tor result.pos { r: *Result<live>, name: string }
| value string
| missing
| unknown~pub tor result.free { r: *Result<!live> }