Cli
CLI: an applicative command-line parser as a first-class value.
A parser is a pure value describing what a command accepts: typed options (long/short, valued or switch, with defaults), typed positionals, and named subcommands. Parsers compose applicatively (build2/build3/build4 over map_p/ap_p/pure_p) into the user’s own result type, so a well-typed parser yields exactly that type or a rich error, never a half-filled record. The illegal state (a config with a missing required field, a subcommand without its arguments) is unrepresentable: the constructor cannot be applied until every part has parsed.
One description, three artifacts: the same Command value drives all three. It is the parser (run_argv), turning argv into the result type or an error. It is the help text (help_text), rendered from the option and argument specs the parser carries, so --help can never drift from what parses. And it is the errors (inside run_argv), which name the offending token, state the form expected, and print the usage line derived from those same specs.
Everything here is pure: a Parser reads a Tokens structure, and Tokens comes from lexing a List(String). The tie to the real command line is run_args, which feeds args() (the Env capability) to run_argv. Because argv arrives through that capability, a recorded run replays its arguments from the .replay trace like any other observation: CLI parsing is inside the determinism contract, argv and all.
Types
CliError
type CliError
= MissingFlag(String)
| MissingArg(String)
| NeedsValue(String)
| BadValue(String, String)
| UnknownFlag(String)
| UnknownCommand(String)
A parse failure, carrying enough to name the offending token and the form expected. Rendered to a message by describe.
OptSpec
type OptSpec = OptSpec {
long: String,
short: String,
meta: String,
help: String,
takes_value: Bool
}
The static description of one option, carried by the parser for help and to tell the lexer whether the flag consumes a following value.
ArgSpec
type ArgSpec = ArgSpec { meta: String, help: String }
The static description of one positional argument.
Tokens
type Tokens = Tokens { opts: List((String, String)), pos: List(String) }
argv after lexing: options canonicalized to their long name, and the positionals in order. --help/-h are handled before lexing (has_help), so they never appear here.
Parser
type Parser(a) = Parser {
opts: List(OptSpec),
args: List(ArgSpec),
run: (Tokens, Int) -> Result((a, Int), CliError)
}
A parser for a value of type a: the option and positional specs it accepts (the description help is rendered from) and a pure reader over lexed tokens threading the positional cursor. Build these with the combinators below, never by hand.
SubCmd
type SubCmd(a) = SubCmd { key: String, about: String, sub: Parser(a) }
One named subcommand: its key on the command line, a one-line description, and the parser that runs when it is chosen.
Body
type Body(a) = Plain(Parser(a)) | Group(List(SubCmd(a)))
A top-level command is either a single parser or a group of subcommands, all producing the same result type (so distinct subcommands map to distinct constructors of the user’s ADT).
Command
type Command(a) = Command { name: String, about: String, body: Body(a) }
A complete command: a program name and one-line description for usage, plus its body.
Outcome
type Outcome(a) = Parsed(a) | ShowHelp(String) | BadUsage(String)
The outcome of parsing argv: a value, a help request (carrying the rendered text), or a usage error (carrying the rendered message).
Functions and Values
dash
dash : Int
help_col
help_col : Int
pure_p
pure_p : forall a. (a) -> Cli.Parser(a)
A parser that consumes nothing and yields x. The applicative unit.
map_p
map_p : forall a b. ((b) -> a, Cli.Parser(b)) -> Cli.Parser(a)
Map f over a parser’s result, leaving what it consumes unchanged.
ap_p
ap_p : forall a b. (Cli.Parser((a) -> b), Cli.Parser(a)) -> Cli.Parser(b)
Applicative application: run pf then px, threading the positional cursor left to right, and apply the function to the argument. This is what lets a curried constructor be filled field by field.
build2
build2 : forall a b c. ((a) -> (c) -> b, Cli.Parser(a), Cli.Parser(c)) -> Cli.Parser(b)
Apply a two-argument curried constructor to two parsers.
build3
build3 : forall a b c d. ((a) -> (b) -> (d) -> c, Cli.Parser(a), Cli.Parser(b), Cli.Parser(d)) -> Cli.Parser(c)
Apply a three-argument curried constructor to three parsers.
build4
build4 : forall a b c d e. ((a) -> (b) -> (c) -> (e) -> d, Cli.Parser(a), Cli.Parser(b), Cli.Parser(c), Cli.Parser(e)) -> Cli.Parser(d)
Apply a four-argument curried constructor to four parsers.
opt_str
opt_str : (String, String, String, String, String) -> Cli.Parser(String)
An optional string flag with a default when absent.
req_str
req_str : (String, String, String, String) -> Cli.Parser(String)
A required string flag: an error when absent.
opt_int
opt_int : (String, String, String, String, Int) -> Cli.Parser(Int)
An optional integer flag with a default; a non-integer value is an error.
switch
switch : (String, String, String) -> Cli.Parser(Bool)
A boolean switch: true when present, false when absent, never valued.
arg_str
arg_str : (String, String) -> Cli.Parser(String)
A required string positional, consumed in declaration order.
arg_int
arg_int : (String, String) -> Cli.Parser(Int)
A required integer positional; a non-integer token is an error.
lex
lex : (List(Cli.OptSpec), List(String)) -> Result(Cli.Tokens, Cli.CliError)
Lex argv against the option specs (which say whether each flag takes a value). Options are canonicalized to their long name; anything not a flag is a positional. --help/-h are skipped here (detected earlier by has_help).
run_argv
run_argv : forall a. (Cli.Command(a), List(String)) -> Cli.Outcome(a)
Parse argv against a command, yielding a value, a help request, or a usage error. argv is the argument list only (no program name); cmd.name supplies the name for usage text.
help_text
help_text : forall a. (Cli.Command(a)) -> String
The rendered help for a command: dispatches to the plain or group form.
describe
describe : (Cli.CliError) -> String
A one-line description of a parse error, naming the offending token and the form expected.