std.cli
std.cli
Section titled “std.cli”std.cli is the standard command-line parser for Janus programs.
It provides stable E42xx error codes, token helpers, and a zero-allocation
parser for long flags, short flags, short groups, values, positionals, and the
-- terminator.
Current API Shape
Section titled “Current API Shape”The v1 parser uses fixed-capacity builders:
use std.cli
var specs = cli.initSpecSet()_ = cli.addSpec(&specs, cli.boolFlag(str_slice("verbose", 0, 7), str_slice("v", 0, 1)))_ = cli.addSpec(&specs, cli.defaultValue( cli.valueName(cli.valueFlag(str_slice("output", 0, 6), str_slice("o", 0, 1)), str_slice("PATH", 0, 4)), str_slice("stdout", 0, 6)))_ = cli.addSpec(&specs, cli.valueName( cli.repeatableValueFlag(str_slice("include", 0, 7), str_slice("I", 0, 1)), str_slice("PATH", 0, 4)))_ = cli.addSpec(&specs, cli.defaultValue( cli.valueName(cli.valueFlag(str_slice("jobs", 0, 4), str_slice("j", 0, 1)), str_slice("N", 0, 1)), str_slice("4", 0, 1)))_ = cli.addSpec(&specs, cli.defaultValue( cli.valueName(cli.valueFlag(str_slice("ratio", 0, 5), str_slice("r", 0, 1)), str_slice("N", 0, 1)), str_slice("0.5", 0, 3)))_ = cli.addSpec(&specs, cli.defaultValue( cli.valueName(cli.valueFlag(str_slice("format", 0, 6), str_slice("f", 0, 1)), str_slice("KIND", 0, 4)), str_slice("text", 0, 4)))_ = cli.addSpec(&specs, cli.requireFlag(cli.valueFlag(str_slice("config", 0, 6), str_slice("c", 0, 1))))
var argv = cli.initArgList()_ = cli.addArg(&argv, str_slice("--verbose", 0, 9))_ = cli.addArg(&argv, str_slice("--output=file.txt", 0, 17))
let parsed = cli.parse(&argv, &specs, 0, 0)
let help = cli.buildHelp( str_slice("demo", 0, 4), str_slice("demo command", 0, 12), str_slice("[input]", 0, 7), &specs)This avoids heap allocation and avoids the compiler’s current nested slice-call
gap. A future adapter can accept raw [][]const u8 argv once that ABI is fully
closed.
Supported Forms
Section titled “Supported Forms”--verbose--color=false--no-color--output=file.txt--output file.txt--jobs -2--ratio=0.75-v-abc-ofile.txt--followed by positional arguments
Parse Results
Section titled “Parse Results”ParseResult returns:
okargserr_codeerr_messageerr_flag
Use hasFlag(&parsed.args, name) and valueOf(&parsed.args, name) to query
parsed flags.
Use valueOr(&parsed.args, name, fallback) when a caller should see a default
even if the flag was not provided. Parser-level defaults are injected into the
parsed flag list before success is returned.
Use positionalAt(&parsed.args, index) and
positionalOr(&parsed.args, index, fallback) for operands:
let input = cli.positionalAt(&parsed.args, 0)let output = cli.positionalOr(&parsed.args, 1, str_slice("stdout", 0, 6))This keeps positional reads on the same lookup surface as flags and avoids direct indexing at call sites.
Use repeatableValueFlag(long, short) for options that may appear more than
once, such as include paths or definitions. Read them with countOf and
valueAt:
_ = cli.addSpec(&specs, cli.repeatableValueFlag(str_slice("include", 0, 7), str_slice("I", 0, 1)))
let include_count = cli.countOf(&parsed.args, str_slice("include", 0, 7))let first_include = cli.valueAt(&parsed.args, str_slice("include", 0, 7), 0)let last_include = cli.lastValueOf(&parsed.args, str_slice("include", 0, 7))This supports -Istd, --include=lib, and --include vendor without heap
allocation. valueOf returns the first match; lastValueOf returns the last
match for last-wins command semantics.
Typed Values
Section titled “Typed Values”Use intValue(&parsed.args, name) when a value flag should be read as an
integer. It returns IntLookup:
foundtells you whether the flag was present after defaults were appliedoktells you whether parsing succeededvaluecontains the parsedi64whenokis trueerr_codeisE_INVALID_VALUEwhen the string is not a decimal integer
Use intValueInRange(&parsed.args, name, min, max) for numeric options with a
valid operating range. Out-of-range values return E_VALUE_OUT_OF_RANGE.
Negative integers may be passed in attached or spaced form, for example
--jobs=-2 or --jobs -2; the parser still treats nonnumeric flag-looking
tokens such as -v as a missing value boundary.
let jobs = cli.intValueInRange(&parsed.args, str_slice("jobs", 0, 4), 1, 16)if jobs.found and jobs.ok do run_with_jobs(jobs.value)endUse floatValue(&parsed.args, name) and
floatValueInRange(&parsed.args, name, min, max) for decimal f64 options:
let ratio = cli.floatValueInRange(&parsed.args, str_slice("ratio", 0, 5), 0.0, 1.0)if ratio.found and ratio.ok do tune_ratio(ratio.value)endThe current float reader accepts plain decimal text such as 0.75, 1,
1.0, .5, -2.5, and --ratio -2.5. It does not yet accept exponent
notation. Invalid decimal text returns E_INVALID_VALUE; out-of-range values
return E_VALUE_OUT_OF_RANGE.
Use boolValue(&parsed.args, name) when a boolean flag may be explicitly
enabled or disabled. Bare flags read as true; missing flags read as
found: false, ok: true, and value: false.
let color = cli.boolValue(&parsed.args, str_slice("color", 0, 5))Accepted long boolean forms include --color, --color=true,
--color=false, and --no-color. The parser accepts true, false, 1,
0, yes, no, on, and off for attached boolean values. Invalid boolean
text returns E_INVALID_VALUE.
Use ChoiceSet and choiceValue(&parsed.args, name, &choices) for enum-like
string values:
var formats = cli.initChoiceSet()_ = cli.addChoice(&formats, str_slice("text", 0, 4))_ = cli.addChoice(&formats, str_slice("json", 0, 4))_ = cli.addChoice(&formats, str_slice("table", 0, 5))
let format = cli.choiceValue(&parsed.args, str_slice("format", 0, 6), &formats)if format.found and format.ok do render(format.value)endInvalid choices return E_INVALID_CHOICE.
Validation Rules
Section titled “Validation Rules”Use ConflictSet and validateConflicts(&parsed.args, &conflicts) for
mutually exclusive flags:
var conflicts = cli.initConflictSet()_ = cli.addConflict(&conflicts, str_slice("json", 0, 4), str_slice("table", 0, 5))
let valid = cli.validateConflicts(&parsed.args, &conflicts)if !valid.ok do show_error(valid.err_code, valid.err_flag, valid.err_other)endConflicts return E_CONFLICTING_FLAGS and include both flag names in
err_flag and err_other.
Use FlagGroup when a command requires at least one flag from a set, or
exactly one mode from a set:
var input = cli.initFlagGroup(str_slice("input", 0, 5))_ = cli.addGroupFlag(&input, str_slice("stdin", 0, 5))_ = cli.addGroupFlag(&input, str_slice("file", 0, 4))_ = cli.addGroupFlag(&input, str_slice("url", 0, 3))
let has_input = cli.validateAnyFlag(&parsed.args, &input)let one_input = cli.validateExactlyOneFlag(&parsed.args, &input)Missing groups return E_MISSING_GROUP. validateExactlyOneFlag returns
E_CONFLICTING_FLAGS when two or more group members are present.
Metadata
Section titled “Metadata”FlagSpec carries metadata for the help layer:
requiredhas_defaultdefault_valuevalue_namehelp
The helper functions requireFlag, defaultValue, valueName, and helpText
return updated specs, so they can be composed around boolFlag, valueFlag, or
repeatableFlag.
Help Documents
Section titled “Help Documents”buildHelp(app_name, summary, usage_tail, &specs) returns a zero-copy HelpDoc
view over the existing SpecSet. It does not render terminal text yet. It gives
the future renderer a stable structural surface:
row_counthelpLong(&doc, i)helpShort(&doc, i)helpTakesValue(&doc, i)helpValueName(&doc, i)helpTextAt(&doc, i)helpRequired(&doc, i)helpRepeatable(&doc, i)helpHasDefault(&doc, i)helpDefaultValue(&doc, i)
The document is a view, so keep the SpecSet alive while using it.
Verification
Section titled “Verification”The stdlib proof target is:
./scripts/zb test-cli