~/bend-docscommunity

src/cli.bend source

src/cli.bend on the hub · documented module

# shake/cli: the parser behind main.bend, internal. `parse` reads argv# against a Cli; `help` writes usage for a command path, and `help` is the# subcommand that asks for it (`--help` too, unless the command declares its# own long `help`). A `--` that reaches `parse` ends option parsing; in a# compiled program the runtime takes the first `--` itself.import Base# a flag, a valued option, an option that may repeat, a positional, or a rest# positionaltype ArgKind is Data:  Flag{}  Opt{}  Pos{}  Rest{}  Many{}# what an argument the words leave unbound falls back to: the value of its# env var, when `parse_env` is given one, else its defaulttype Fallback is Data:  Fallback{env: Maybe<&2, String>, default: Maybe<&2, String>}# one argument: name is the binding key; short/long are the CLI spellingstype Arg is Data:  Arg{    name: String,    short: Maybe<&2, String>,    long: Maybe<&2, String>,    kind: ArgKind,    help: String,    required: Bool,    fallback: Fallback,    choices: List<&2, String>  }# a nested commandtype Sub is Data:  Sub{    name: String,    about: String,    args: List<&2, Arg>,    subcommands: List<&2, Sub>  }# the program: name, about, optional version, top-level args and commandstype Cli is Data:  Cli{    name: String,    about: String,    version: Maybe<&2, String>,    args: List<&2, Arg>,    subcommands: List<&2, Sub>  }# an env var's name and its value, as `parse_env` is given themdef Var() -> Data:  Sigma<&2, &2, String, _ => String># one bound name and its texttype Bind is Data:  Bind{name: String, value: String}# one command above the current one: its arguments and the bindings made# while it was currenttype Level is Data:  Level{args: List<&2, Arg>, binds: List<&2, Bind>}# a successful parse, read one command at a time, as clap's ArgMatches: the# bindings the command made, in binding order, and, when a subcommand was# selected under it, that subcommand's name and Matchedtype Matched is Data:  Leaf{+binds: List<&2, Bind>}  Node{+binds: List<&2, Bind>, +name: String, +sub: Matched}# a failed parse. NeedHelp is `help` / `help <path>` (or `--help`); NoValue# is an option whose value never came, Missing a required argument nothing# bound. Every error but NeedHelp carries `at`, the command path selected# where it failedtype ParseErr is Data:  UnknownFlag{at: List<&2, String>, flag: String}  Missing{at: List<&2, String>, name: String}  NoValue{at: List<&2, String>, name: String}  BadValue{at: List<&2, String>, name: String, value: String}  NeedHelp{path: List<&2, String>}  Unexpected{at: List<&2, String>, arg: String}  Repeated{at: List<&2, String>, name: String}# how the next token is readtype Mode is Data:  Free{}  Need{name: String}  Help{}  Dead{err: ParseErr}# the walker: the current command's arguments, the commands above it (the# nearest first, one per name of the selected path), its subcommands, the# selected path, and the current command's own bindingstype St is Data:  St{    mode: Mode,    args: List<&2, Arg>,    up: List<&2, Level>,    subs: List<&2, Sub>,    path: List<&2, String>,    binds: List<&2, Bind>,    pos: List<&2, Arg>,    raw: Bool,    seen: Bool  }# the page a help walk has reachedtype How is Data:  How{    bin: String,    about: String,    version: Maybe<&2, String>,    args: List<&2, Arg>,    subs: List<&2, Sub>,    usage: String  }# a program specdef app(  +name: String,  +about: String,  version: Maybe<&2, String>,  args: List<&2, Arg>,  subs: List<&2, Sub>) -> Cli:  Cli{name, about, version, args, subs}# a nested commanddef sub(  # noqa: L001 a builder: its result is the Arg or Sub it names, nothing to state  +name: String,  +about: String,  args: List<&2, Arg>,  subs: List<&2, Sub>) -> Sub:  Sub{name, about, args, subs}# a boolean flagdef flag(  # noqa: L001 a builder: its result is the Arg or Sub it names, nothing to state  +name: String,  short: Maybe<&2, String>,  long: Maybe<&2, String>,  +help: String) -> Arg:  Arg{name, short, long, Flag{}, help, False{}, Fallback{None{}, None{}}, []}# a valued optiondef opt(  # noqa: L001 a builder: its result is the Arg or Sub it names, nothing to state  +name: String,  short: Maybe<&2, String>,  long: Maybe<&2, String>,  +help: String,  required: Bool,  default: Maybe<&2, String>,  choices: List<&2, String>) -> Arg:  Arg{name, short, long, Opt{}, help, required, Fallback{None{}, default}, choices}# an option that may be given more than once; `get_all` reads every valuedef many(  # noqa: L001 a builder: its result is the Arg or Sub it names, nothing to state  +name: String,  short: Maybe<&2, String>,  long: Maybe<&2, String>,  +help: String,  required: Bool,  default: Maybe<&2, String>,  choices: List<&2, String>) -> Arg:  Arg{name, short, long, Many{}, help, required, Fallback{None{}, default}, choices}# a positionaldef pos(  # noqa: L001 a builder: its result is the Arg or Sub it names, nothing to state  +name: String,  +help: String,  required: Bool,  default: Maybe<&2, String>,  choices: List<&2, String>) -> Arg:  Arg{name, None{}, None{}, Pos{}, help, required, Fallback{None{}, default}, choices}# a rest positional: every leftover word, as one namedef rest(  # noqa: L001 a builder: its result is the Arg or Sub it names, nothing to state  +name: String,  +help: String,  required: Bool,  default: Maybe<&2, String>,  choices: List<&2, String>) -> Arg:  Arg{name, None{}, None{}, Rest{}, help, required, Fallback{None{}, default}, choices}# this argument, falling back to the env var `var` (clap's `.env`)def env(aa: Arg, +var: String) -> Arg:  Arg{+n, +s, +l, kk, +h, +r, Fallback{_e, dd}, cs} = aa  Arg{n, s, l, kk, h, r, Fallback{Some{var}, dd}, cs}# the larger of two Natsdef help.max(+aa: Nat, +bb: Nat) -> Nat:  Bool.pick(Nat, Nat.is_le(aa, bb), bb, aa)# n spacesdef help.spaces(nn: Nat) -> String:  match nn:    case 0n:      ""    case 1n+p:      " " ++ help.spaces(p)# s padded on the right to width ndef help.pad(+ss: String, nn: Nat) -> String:  ss ++ help.spaces(Nat.sub(nn, String.length(ss)))# a Maybe string's text, or the defaultdef text.of(mm: Maybe<&2, String>, +dd: String) -> String:  Maybe.default(&2, String, mm, dd)# whether a string is a flag token (`-` alone is not)def looks_flag(+ss: String) -> Bool:  Bool.and(String.starts_with(ss, "-"), Bool.not(String.eq(ss, "-")))# chars reversed into a stringdef from.rev(cs: List<&2, Char>) -> String:  String.from_list(List.reverse(&2, Char, cs))# `name` and an optional `=value`, once the next char is knowndef cut_eq.pick(  eq: Bool,  acc: List<&2, Char>,  tt: List<&2, Char>,  rest: String & Maybe<&2, String>) -> String & Maybe<&2, String>:  match eq:    case True{}:      (from.rev(acc), Some{String.from_list(tt)})    case False{}:      rest# one char of a `name=value` cutdef cut_eq.chr(  +cc: Char,  tt: List<&2, Char>,  acc: List<&2, Char>,  rest: String & Maybe<&2, String>) -> String & Maybe<&2, String>:  cut_eq.pick(Char.is_eq(cc, '='), acc, tt, rest)# `name` and an optional `=value` from the body after `--`def cut_eq.go(  cs: List<&2, Char>,  +acc: List<&2, Char>) -> String & Maybe<&2, String>:  match cs:    case Nil{}:      (from.rev(acc), None{})    case Con{+c, +t}:      cut_eq.chr(c, t, acc, cut_eq.go(t, c <> acc))# `name` and an optional `=value`def cut_eq(ss: String) -> String & Maybe<&2, String>:  cut_eq.go(String.to_list(ss), [])# the value glued after a short letter: what follows one `=` when the rest# starts with it (possibly empty, as `--name=` is), else the rest when there# is anydef short_of.val(eq: Bool, +rest: String) -> Maybe<&2, String>:  match eq:    case True{}:      Some{String.drop(rest, 1n)}    case False{}:      Bool.pick(Maybe<&2, String>, String.is_empty(rest), None{}, Some{rest})# whether this Arg's long spelling is `name`def by_long.hit(aa: Arg, +name: String) -> Bool:  Arg{_n, _s, long, _k, _h, _r, _d, _c} = aa  String.eq(text.of(long, ""), name)# this Arg when the long spelling matches, else the restdef by_long.at(hit: Bool, hh: Arg, rest: Maybe<&2, Arg>) -> Maybe<&2, Arg>:  match hit:    case True{}:      Some{hh}    case False{}:      rest# the Arg with this long spelling, if anydef by_long(args: List<&2, Arg>, +name: String) -> Maybe<&2, Arg>:  match args:    case Nil{}:      None{}    case Con{+h, t}:      by_long.at(by_long.hit(h, name), h, by_long(t, name))# whether this Arg's short spelling is `name`def by_short.hit(aa: Arg, +name: String) -> Bool:  Arg{_n, short, _l, _k, _h, _r, _d, _c} = aa  String.eq(text.of(short, ""), name)# this Arg when the short spelling matches, else the restdef by_short.at(hit: Bool, hh: Arg, rest: Maybe<&2, Arg>) -> Maybe<&2, Arg>:  match hit:    case True{}:      Some{hh}    case False{}:      rest# the Arg with this short spelling, if anydef by_short(args: List<&2, Arg>, +name: String) -> Maybe<&2, Arg>:  match args:    case Nil{}:      None{}    case Con{+h, t}:      by_short.at(by_short.hit(h, name), h, by_short(t, name))# whether this Arg's name is `name`def by_name.hit(aa: Arg, +name: String) -> Bool:  Arg{+n, _s, _l, _k, _h, _r, _d, _c} = aa  String.eq(n, name)# this Arg when the binding name matches, else the restdef by_name.at(hit: Bool, hh: Arg, rest: Maybe<&2, Arg>) -> Maybe<&2, Arg>:  match hit:    case True{}:      Some{hh}    case False{}:      rest# the Arg with this binding name, if anydef by_name(args: List<&2, Arg>, +name: String) -> Maybe<&2, Arg>:  match args:    case Nil{}:      None{}    case Con{+h, t}:      by_name.at(by_name.hit(h, name), h, by_name(t, name))# this Sub when its name matches, else the restdef find_sub.at(ss: Sub, rest: Maybe<&2, Sub>, +name: String) -> Maybe<&2, Sub>:  Sub{+n, _a, _g, _c} = ss  Bool.pick(Maybe<&2, Sub>, String.eq(n, name), Some{ss}, rest)# the Sub with this name, if anydef find_sub(subs: List<&2, Sub>, +name: String) -> Maybe<&2, Sub>:  match subs:    case Nil{}:      None{}    case Con{+h, t}:      find_sub.at(h, find_sub(t, name), name)# whether this Arg is a positionaldef pos.is.kind(kk: ArgKind) -> Bool:  match kk:    case Pos{}:      True{}    case Rest{}:      True{}    case Flag{}:      False{}    case Opt{}:      False{}    case Many{}:      False{}# whether this ArgKind is restdef rest.is.kind(kk: ArgKind) -> Bool:  match kk:    case Rest{}:      True{}    case Pos{}:      False{}    case Flag{}:      False{}    case Opt{}:      False{}    case Many{}:      False{}# whether this Arg is a positionaldef pos.is(aa: Arg) -> Bool:  Arg{_n, _s, _l, kind, _h, _r, _d, _c} = aa  pos.is.kind(kind)# whether this Arg is a rest positionaldef rest.is(aa: Arg) -> Bool:  Arg{_n, _s, _l, kind, _h, _r, _d, _c} = aa  rest.is.kind(kind)# this Arg kept when it is a positionaldef pos_of.put(keep: Bool, hh: Arg, rest: List<&2, Arg>) -> List<&2, Arg>:  match keep:    case True{}:      hh <> rest    case False{}:      rest# the positionals of a spec, in orderdef pos_of(args: List<&2, Arg>) -> List<&2, Arg>:  match args:    case Nil{}:      Nil{}    case Con{+h, t}:      pos_of.put(pos.is(h), h, pos_of(t))# whether `v` is among the choices; an empty list accepts any valuedef allowed.ok.at(hit: Bool, rest: Bool) -> Bool:  match hit:    case True{}:      True{}    case False{}:      rest# whether `v` is listed; the empty list answers nodef allowed.ok(cs: List<&2, String>, +vv: String) -> Bool:  match cs:    case Nil{}:      False{}    case Con{+h, t}:      allowed.ok.at(String.eq(h, vv), allowed.ok(t, vv))# empty choices accept any value; otherwise `v` must be listeddef allowed(+cs: List<&2, String>, +vv: String) -> Bool:  Bool.or(List.is_empty(&2, String, cs), allowed.ok(cs, vv))# whether this bind's name is `name`def bound.hit(bb: Bind, +name: String) -> Bool:  Bind{+n, _v} = bb  String.eq(n, name)# True when the head matches, else the restdef bound.at(hit: Bool, rest: Bool) -> Bool:  match hit:    case True{}:      True{}    case False{}:      rest# whether `name` is already bounddef bound(bs: List<&2, Bind>, +name: String) -> Bool:  match bs:    case Nil{}:      False{}    case Con{+h, t}:      bound.at(bound.hit(h, name), bound(t, name))# this bind's value when the name matches, else the restdef get_all.bind.hit(  hit: Bool,  +vv: String,  more: List<&2, String>) -> List<&2, String>:  match hit:    case True{}:      vv <> more    case False{}:      more# this bind's value when the name matches, else the restdef get_all.bind.at(  bb: Bind,  more: List<&2, String>,  +name: String) -> List<&2, String>:  Bind{+n, v} = bb  get_all.bind.hit(String.eq(n, name), v, more)# every value bound to `name`def get_all.bind(bs: List<&2, Bind>, +name: String) -> List<&2, String>:  match bs:    case Nil{}:      []    case Con{+h, t}:      get_all.bind.at(h, get_all.bind(t, name), name)# the bindings this command made, in binding orderdef own(mm: Matched) -> List<&2, Bind>:  match mm:    case Leaf{bs}:      bs    case Node{bs, _n, _s}:      bs# every value this command bound to `name`def get_all(mm: Matched, +name: String) -> List<&2, String>:  get_all.bind(own(mm), name)# the last of a list of values, or `dflt` when it is emptydef get.last(vs: List<&2, String>, dflt: Maybe<&2, String>) -> Maybe<&2, String>:  match vs:    case Nil{}:      dflt    case Con{hh, tt}:      get.last(tt, Some{hh})# the value bound to `name` last, or None when nothing bound itdef get(mm: Matched, +name: String) -> Maybe<&2, String>:  get.last(get_all(mm, name), None{})# whether a value is `true`def on.of(mv: Maybe<&2, String>) -> Bool:  match mv:    case None{}:      False{}    case Some{vv}:      String.eq(vv, "true")# whether a flag named `name` was setdef on(mm: Matched, +name: String) -> Bool:  on.of(get(mm, name))# the names of the subcommands selected below this command, in orderdef path_of(mm: Matched) -> List<&2, String>:  match mm:    case Leaf{_b}:      []    case Node{_b, +name, next}:      name <> path_of(next)# the name of the subcommand selected under this commanddef sub_name(mm: Matched) -> Maybe<&2, String>:  match mm:    case Leaf{_b}:      None{}    case Node{_b, name, _s}:      Some{name}# a subcommand's Matched when it is the one asked fordef sub_of.pick(hit: Bool, next: Matched) -> Maybe<&2, Matched>:  match hit:    case True{}:      Some{next}    case False{}:      None{}# the Matched of subcommand `name`, when it is the one selecteddef sub_of(mm: Matched, +name: String) -> Maybe<&2, Matched>:  match mm:    case Leaf{_b}:      None{}    case Node{_b, nn, next}:      sub_of.pick(String.eq(nn, name), next)# one name further down, when there is still a Matched to go down fromdef at.step(cur: Maybe<&2, Matched>, +name: String) -> Maybe<&2, Matched>:  match cur:    case None{}:      None{}    case Some{mm}:      sub_of(mm, name)# the rest of a path, followed from where the walk down standsdef at.go(path: List<&2, String>, cur: Maybe<&2, Matched>) -> Maybe<&2, Matched>:  match path:    case Nil{}:      cur    case Con{+hh, tt}:      at.go(tt, at.step(cur, hh))# the Matched of the command `path` names below this one, name by name, or# None where a name is not the subcommand selecteddef at(mm: Matched, path: List<&2, String>) -> Maybe<&2, Matched>:  at.go(path, Some{mm})# the starting walker for a Clidef parse.start(app: Cli) -> St:  Cli{_n, _a, _v, +args, subs} = app  St{Free{}, args, [], subs, [], [], pos_of(args), False{}, False{}}# a failed walker that keeps the rest of the statedef parse.dead(  ee: ParseErr,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  pos: List<&2, Arg>,  +raw: Bool,  +seen: Bool) -> St:  St{Dead{ee}, args, up, subs, path, binds, pos, raw, seen}# a successful bind of `name` to `val`def parse.put(  +name: String,  +val: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +raw: Bool,  +seen: Bool) -> St:  St{Free{}, args, up, subs, path, Bind{name, val} <> binds, pos, raw, seen}# the choices of a found Arg, or nonedef parse.choices(mm: Maybe<&2, Arg>) -> List<&2, String>:  match mm:    case None{}:      []    case Some{a}:      Arg{_n, _s, _l, _k, _h, _r, _d, cs} = a      cs# bind `name` to `val` when the value is alloweddef parse.take_choice(  ok: Bool,  +name: String,  +val: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +raw: Bool,  +seen: Bool) -> St:  match ok:    case True{}:      parse.put(name, val, args, up, subs, path, binds, pos, raw, seen)    case False{}:      parse.dead(BadValue{path, name, val}, args, up, subs, path, binds, pos, raw, seen)# bind an option that already has its value, checked against the choices of# the current command's argument of that name: a parent's argument of the same# name has its owndef parse.take_opt_val(  +name: String,  +val: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +raw: Bool,  +seen: Bool) -> St:  parse.take_choice(allowed(parse.choices(by_name(args, name)), val), name, val,    args, up, subs, path, binds, pos, raw, seen)# a flag with no glued value is set; a glued value is unexpecteddef parse.take_flag(  val: Maybe<&2, String>,  +name: String,  +orig: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  pos: List<&2, Arg>,  +raw: Bool,  +seen: Bool) -> St:  match val:    case None{}:      parse.put(name, "true", args, up, subs, path, binds, pos, raw, seen)    case Some{_v}:      parse.dead(Unexpected{path, orig}, args, up, subs, path, binds, pos, raw, seen)# an option: glued value now, or the next tokendef parse.take_opt(  val: Maybe<&2, String>,  +name: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +raw: Bool,  +seen: Bool) -> St:  match val:    case None{}:      St{Need{name}, args, up, subs, path, binds, pos, raw, seen}    case Some{v}:      parse.take_opt_val(name, v, args, up, subs, path, binds, pos, raw, seen)# a flag or single-valued option given again is refused, whatever the walk# would have done with it; the walker keeps the rest of its statedef parse.once(again: Bool, st: St, +name: String, +path: List<&2, String>) -> St:  match again:    case True{}:      St{_m, +args, +up, +subs, _p, +binds, +pos, +raw, +seen} = st      St{Dead{Repeated{path, name}}, args, up, subs, path, binds, pos, raw, seen}    case False{}:      st# dispatch on the found Arg's kinddef parse.take_arg.kind(  kk: ArgKind,  +name: String,  val: Maybe<&2, String>,  +orig: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +raw: Bool,  +seen: Bool) -> St:  match kk:    case Flag{}:      parse.once(bound(binds, name), parse.take_flag(val, name, orig, args, up, subs, path,        binds, pos, raw, seen), name, path)    case Opt{}:      parse.once(bound(binds, name), parse.take_opt(val, name, args, up, subs, path, binds, pos,        raw, seen), name, path)    case Many{}:      parse.take_opt(val, name, args, up, subs, path, binds, pos, raw, seen)    case Pos{}:      parse.dead(UnknownFlag{path, orig}, args, up, subs, path, binds, pos, raw, seen)    case Rest{}:      parse.dead(UnknownFlag{path, orig}, args, up, subs, path, binds, pos, raw, seen)# a found Arg, or an unknown flagdef parse.take_arg(  found: Maybe<&2, Arg>,  val: Maybe<&2, String>,  +orig: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  pos: List<&2, Arg>,  +raw: Bool,  +seen: Bool) -> St:  match found:    case None{}:      parse.dead(UnknownFlag{path, orig}, args, up, subs, path, binds, pos, raw, seen)    case Some{a}:      Arg{+name, _s, _l, kind, _h, _r, _d, _c} = a      parse.take_arg.kind(kind, name, val, orig, args, up, subs, path, binds, pos,        raw, seen)# whether an unknown long option is a request for help: a bare `--help`,# with no `=` value, before any positional of the current command is bounddef long_help(+name: String, val: Maybe<&2, String>, seen: Bool) -> Bool:  match val:    case None{}:      Bool.and(String.eq(name, "help"), Bool.not(seen))    case Some{_v}:      False{}# an unknown long option: a request for help (`--help`, as `help`), or an# unknown flagdef parse.take_long.help(  hit: Bool,  +orig: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +raw: Bool,  +seen: Bool) -> St:  match hit:    case True{}:      St{Help{}, args, up, subs, path, binds, pos, raw, seen}    case False{}:      parse.dead(UnknownFlag{path, orig}, args, up, subs, path, binds, pos, raw, seen)# a long option the current command spells, or an unknown onedef parse.take_long.found(  found: Maybe<&2, Arg>,  +name: String,  val: Maybe<&2, String>,  +orig: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +raw: Bool,  +seen: Bool) -> St:  match found:    case None{}:      parse.take_long.help(long_help(name, val, seen), orig, args, up, subs, path,        binds, pos, raw, seen)    case Some{a}:      parse.take_arg(Some{a}, val, orig, args, up, subs, path, binds, pos, raw, seen)# a long option with a name, or `--=...`def parse.take_long.named(  empty: Bool,  +name: String,  val: Maybe<&2, String>,  +orig: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +raw: Bool,  +seen: Bool) -> St:  match empty:    case True{}:      parse.dead(UnknownFlag{path, orig}, args, up, subs, path, binds, pos, raw, seen)    case False{}:      parse.take_long.found(by_long(args, name), name, val, orig, args, up, subs,        path, binds, pos, raw, seen)# a long option after the `=` cutdef parse.take_long.cut(  nv: String & Maybe<&2, String>,  +orig: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  pos: List<&2, Arg>,  +raw: Bool,  +seen: Bool) -> St:  (+name, val) = nv  parse.take_long.named(String.is_empty(name), name, val, orig, args, up, subs,    path, binds, pos, raw, seen)# a long option body after `--`def parse.take_long(  body: String,  +orig: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +raw: Bool,  +seen: Bool) -> St:  parse.take_long.cut(cut_eq(body), orig, args, up, subs, path, binds, pos, raw,    seen)# a flag's value in a cluster: the rest when it starts with `=` (which# `take_arg` refuses), else none, so the next letter goes ondef cluster.val.flag(eq: Bool, +rest: String) -> Maybe<&2, String>:  match eq:    case True{}:      Some{rest}    case False{}:      None{}# the value a letter of this kind gives its argument: an option takes the# rest of the word, after one `=`def cluster.val.kind(kk: ArgKind, +rest: String) -> Maybe<&2, String>:  match kk:    case Flag{}:      cluster.val.flag(String.starts_with(rest, "="), rest)    case Opt{}:      short_of.val(String.starts_with(rest, "="), rest)    case Many{}:      short_of.val(String.starts_with(rest, "="), rest)    case Pos{}:      None{}    case Rest{}:      None{}# the value the letter's argument gets, or none when no argument has itdef cluster.val(found: Maybe<&2, Arg>, +rest: String) -> Maybe<&2, String>:  match found:    case None{}:      None{}    case Some{aa}:      Arg{_n, _s, _l, kind, _h, _r, _d, _c} = aa      cluster.val.kind(kind, rest)# whether a letter of this kind ends the cluster: every kind but a flag that# is not followed by `=`def cluster.ends.kind(kk: ArgKind, +rest: String) -> Bool:  match kk:    case Flag{}:      String.starts_with(rest, "=")    case Opt{}:      True{}    case Many{}:      True{}    case Pos{}:      True{}    case Rest{}:      True{}# whether the letters after this one are no longer read as letters: an# unknown letter, an option (they are its value) or a flag given `=`def cluster.ends(found: Maybe<&2, Arg>, +rest: String) -> Bool:  match found:    case None{}:      True{}    case Some{aa}:      Arg{_n, _s, _l, kind, _h, _r, _d, _c} = aa      cluster.ends.kind(kind, rest)# one letter against a walker an earlier letter failed changes nothing;# otherwise it goes through `take_arg`def cluster.apply.mode(  mode: Mode,  +cc: Char,  +rest: String,  +orig: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +raw: Bool,  +seen: Bool) -> St:  match mode:    case Dead{e}:      St{Dead{e}, args, up, subs, path, binds, pos, raw, seen}    case _other:      +found = by_short(args, SCon{cc, SNil{}})      parse.take_arg(found, cluster.val(found, rest), orig, args, up, subs, path, binds, pos,        raw, seen)# one letter against the walkerdef cluster.apply(st: St, +cc: Char, +rest: String, +orig: String) -> St:  St{mode, +args, +up, +subs, +path, +binds, +pos, +raw, +seen} = st  cluster.apply.mode(mode, cc, rest, orig, args, up, subs, path, binds, pos, raw, seen)# one letter of a cluster, unless an earlier one ended itdef cluster.letter(done: Bool, +cc: Char, +rest: String, +orig: String, st: St) -> St:  match done:    case True{}:      st    case False{}:      cluster.apply(st, cc, rest, orig)# whether the cluster has ended after this letterdef cluster.done(done: Bool, +cc: Char, +rest: String, st: St) -> Bool:  match done:    case True{}:      True{}    case False{}:      St{_m, +args, _g, _s, _p, _b, _o, _r, _n} = st      cluster.ends(by_short(args, SCon{cc, SNil{}}), rest)# every letter of a short cluster `-abc`, in order: a flag binds and the next# letter goes on; an option takes the rest of the word, after one `=`, or the# next word when nothing is left; an unknown letter, or a flag given `=`,# refuses the whole worddef cluster(body: String, +orig: String, +st: St, +done: Bool) -> St:  match body:    case SNil{}:      st    case SCon{+cc, +rest}:      cluster(rest, orig, cluster.letter(done, cc, rest, orig, st), cluster.done(done, cc, rest, st))# a short option body after `-`def parse.take_short(  body: String,  +orig: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +raw: Bool,  +seen: Bool) -> St:  cluster(body, orig, St{Free{}, args, up, subs, path, binds, pos, raw, seen}, False{})# a rest positional stays; a plain one is consumeddef parse.take_pos.keep(keep: Bool, hh: Arg, tt: List<&2, Arg>) -> List<&2, Arg>:  match keep:    case True{}:      hh <> tt    case False{}:      tt# the next remaining positional, or an unexpected worddef parse.take_pos(  +tok: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  pos: List<&2, Arg>,  raw: Bool) -> St:  match pos:    case Nil{}:      parse.dead(Unexpected{path, tok}, args, up, subs, path, binds, pos, raw, True{})    case Con{+h, t}:      Arg{+name, _s, _l, _k, _h, _r, _d, +cs} = h      parse.take_choice(allowed(cs, tok), name, tok, args, up, subs, path, binds,        parse.take_pos.keep(rest.is(h), h, t), raw, True{})# this Sub's arguments become current, with no bindings of its own; the# command it leaves keeps its arguments and bindings above itdef parse.enter(  ss: Sub,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +path: List<&2, String>,  binds: List<&2, Bind>) -> St:  Sub{+name, _a, +next, next_subs} = ss  St{Free{}, next, Level{args, binds} <> up, next_subs,    List.append(&2, String, path, [name]), [], pos_of(next), False{},    False{}}# required and no default → Some{name}def parse.req.go(req: Bool, bare: Bool, +nn: String) -> Maybe<&2, String>:  Bool.pick(Maybe<&2, String>, Bool.and(req, bare), Some{nn}, None{})# a required Arg without a default, as a missing namedef parse.req(aa: Arg) -> Maybe<&2, String>:  Arg{+n, _s, _l, _k, _h, +req, Fallback{_e, default}, _c} = aa  parse.req.go(req, Maybe.is_none(&2, String, default), n)# this missing name, else the restdef parse.first_req.at(  here: Maybe<&2, String>,  rest: Maybe<&2, String>) -> Maybe<&2, String>:  match here:    case Some{n}:      Some{n}    case None{}:      rest# a rest stays in the list, so it does not block a subcommanddef parse.first_req.skip(skip: Bool, aa: Arg) -> Maybe<&2, String>:  match skip:    case True{}:      None{}    case False{}:      parse.req(aa)# the first leftover required positionaldef parse.first_req(pos: List<&2, Arg>) -> Maybe<&2, String>:  match pos:    case Nil{}:      None{}    case Con{+h, t}:      parse.first_req.at(parse.first_req.skip(rest.is(h), h), parse.first_req(t))# a leftover required positional blocks entering a subcommanddef parse.enter_or(  miss: Maybe<&2, String>,  ss: Sub,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +raw: Bool,  +seen: Bool) -> St:  match miss:    case None{}:      parse.enter(ss, args, up, path, binds)    case Some{n}:      parse.dead(Missing{path, n}, args, up, subs, path, binds, pos, raw, seen)# a found subcommand, or a positionaldef parse.word.go(  found: Maybe<&2, Sub>,  +tok: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +raw: Bool,  +seen: Bool) -> St:  match found:    case None{}:      parse.take_pos(tok, args, up, subs, path, binds, pos, raw)    case Some{s}:      parse.enter_or(parse.first_req(pos), s, args, up, subs, path, binds, pos, raw,        seen)# a word: a subcommand, or a positionaldef parse.word(  +tok: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +raw: Bool,  +seen: Bool) -> St:  parse.word.go(find_sub(subs, tok), tok, args, up, subs, path, binds, pos,    raw, seen)# `-` as a word, or a short optiondef parse.step.short.go(  alone: Bool,  +tok: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +seen: Bool) -> St:  match alone:    case True{}:      parse.word(tok, args, up, subs, path, binds, pos, False{}, seen)    case False{}:      parse.take_short(String.drop(tok, 1n), tok, args, up, subs, path, binds, pos,        False{}, seen)# `-` alone is a positional; otherwise a short optiondef parse.step.short(  dash: Bool,  +tok: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +seen: Bool) -> St:  match dash:    case True{}:      parse.step.short.go(String.is_empty(String.drop(tok, 1n)), tok, args, up, subs, path, binds,        pos, seen)    case False{}:      parse.word(tok, args, up, subs, path, binds, pos, False{}, seen)# `--long` first, then `-s`, then a worddef parse.step.kind.go(  long: Bool,  dash: Bool,  +tok: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +seen: Bool) -> St:  match long:    case True{}:      parse.take_long(String.drop(tok, 2n), tok, args, up, subs, path, binds, pos,        False{}, seen)    case False{}:      parse.step.short(dash, tok, args, up, subs, path, binds, pos, seen)# a long option, a short option, or a worddef parse.step.kind(  +tok: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +seen: Bool) -> St:  parse.step.kind.go(String.starts_with(tok, "--"), String.starts_with(tok, "-"), tok,    args, up, subs, path, binds, pos, seen)# `help` in command position collects the rest of argv as a pathdef parse.step.help(  hit: Bool,  +tok: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +seen: Bool) -> St:  match hit:    case True{}:      St{Help{}, args, up, subs, path, binds, pos, False{}, seen}    case False{}:      parse.step.kind(tok, args, up, subs, path, binds, pos, seen)# `--` ends option parsingdef parse.step.end(  end: Bool,  +tok: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +seen: Bool) -> St:  match end:    case True{}:      St{Free{}, args, up, subs, path, binds, pos, True{}, seen}    case False{}:      parse.step.help(Bool.and(String.eq(tok, "help"), Bool.not(seen)), tok, args,        up, subs, path, binds, pos, seen)# after `--`, every token is a positionaldef parse.step.raw(  raw: Bool,  +tok: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +seen: Bool) -> St:  match raw:    case True{}:      parse.take_pos(tok, args, up, subs, path, binds, pos, True{})    case False{}:      parse.step.end(String.eq(tok, "--"), tok, args, up, subs, path, binds, pos,        seen)# a pending option value: a flag-shaped token is a missing valuedef parse.step.need(  flag: Bool,  +name: String,  +tok: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +raw: Bool,  +seen: Bool) -> St:  match flag:    case True{}:      parse.dead(NoValue{path, name}, args, up, subs, path, binds, pos, raw, seen)    case False{}:      parse.take_opt_val(name, tok, args, up, subs, path, binds, pos, raw, seen)# a word after `help`: the subcommand it names, whose own subcommands the# next word is looked up in, or an unexpected worddef parse.step.help_path(  found: Maybe<&2, Sub>,  +tok: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +raw: Bool,  +seen: Bool) -> St:  match found:    case None{}:      parse.dead(Unexpected{path, tok}, args, up, subs, path, binds, pos, raw, seen)    case Some{ss}:      Sub{_n, _a, _g, next_subs} = ss      St{Help{}, args, up, next_subs, List.append(&2, String, path, [tok]), binds,        pos, raw, seen}# one token against the walkerdef parse.step.mode(  mode: Mode,  +tok: String,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +subs: List<&2, Sub>,  +path: List<&2, String>,  +binds: List<&2, Bind>,  +pos: List<&2, Arg>,  +raw: Bool,  +seen: Bool) -> St:  match mode:    case Dead{e}:      St{Dead{e}, args, up, subs, path, binds, pos, raw, seen}    case Need{name}:      parse.step.need(looks_flag(tok), name, tok, args, up, subs, path, binds, pos,        raw, seen)    case Help{}:      parse.step.help_path(find_sub(subs, tok), tok, args, up, subs, path, binds, pos, raw,        seen)    case Free{}:      parse.step.raw(raw, tok, args, up, subs, path, binds, pos, seen)# one tokendef parse.step(+tok: String, st: St) -> St:  St{mode, args, up, subs, path, binds, pending, raw, seen} = st  parse.step.mode(mode, tok, args, up, subs, path, binds, pending, raw, seen)# every token, in orderdef parse.walk(argv: List<&2, String>, st: St) -> St:  match argv:    case Nil{}:      st    case Con{+h, t}:      parse.walk(t, parse.step(h, st))# a default value kept when the name is not already bounddef fill.one(  skip: Bool,  +name: String,  +val: String,  rest: List<&2, Bind>) -> List<&2, Bind>:  match skip:    case True{}:      rest    case False{}:      Bind{name, val} <> rest# a Some default is filled when unbounddef fill.arg.go(  dd: Maybe<&2, String>,  +name: String,  +binds: List<&2, Bind>) -> List<&2, Bind>:  match dd:    case None{}:      binds    case Some{v}:      fill.one(bound(binds, name), name, v, binds)# this Arg's default, when it has one and is not bounddef fill.arg(aa: Arg, +binds: List<&2, Bind>) -> List<&2, Bind>:  Arg{+n, _s, _l, _k, _h, _r, Fallback{_e, default}, _c} = aa  fill.arg.go(default, n, binds)# defaults of every spec Argdef fill.args(args: List<&2, Arg>, +binds: List<&2, Bind>) -> List<&2, Bind>:  match args:    case Nil{}:      binds    case Con{+h, t}:      fill.arg(h, fill.args(t, binds))# a required name that is not bounddef miss.one(  need: Bool,  +name: String,  rest: Maybe<&2, String>) -> Maybe<&2, String>:  match need:    case True{}:      Some{name}    case False{}:      rest# whether this Arg is required and unbounddef miss.arg(  aa: Arg,  +binds: List<&2, Bind>,  rest: Maybe<&2, String>) -> Maybe<&2, String>:  Arg{+n, _s, _l, _k, _h, +req, _d, _c} = aa  miss.one(Bool.and(req, Bool.not(bound(binds, n))), n, rest)# the first required name that is not bounddef miss.args(args: List<&2, Arg>, +binds: List<&2, Bind>) -> Maybe<&2, String>:  match args:    case Nil{}:      None{}    case Con{+h, t}:      miss.arg(h, binds, miss.args(t, binds))# each command's defaults, filled in among its own bindingsdef fill.levels(lvs: List<&2, Level>) -> List<&2, Level>:  match lvs:    case Nil{}:      []    case Con{hh, tt}:      Level{+args, binds} = hh      Level{args, fill.args(args, binds)} <> fill.levels(tt)# the first required argument a command left unbound, in list orderdef miss.levels(lvs: List<&2, Level>) -> Maybe<&2, String>:  match lvs:    case Nil{}:      None{}    case Con{hh, tt}:      Level{+args, +binds} = hh      parse.first_req.at(miss.args(args, binds), miss.levels(tt))# the bindings a command madedef level.binds(lv: Level) -> List<&2, Bind>:  Level{_a, binds} = lv  binds# each command's Matched, root first, with the name selected under itdef matched.build(lvs: List<&2, Level>, names: List<&2, String>) -> Matched:  match lvs:    case Nil{}:      Leaf{[]}    case Con{hh, tt}:      match names:        case Nil{}:          Leaf{List.reverse(&2, Bind, level.binds(hh))}        case Con{nn, ns}:          Node{List.reverse(&2, Bind, level.binds(hh)), nn, matched.build(tt, ns)}# a missing required name, or the matched resultdef parse.finish.miss(  mm: Maybe<&2, String>,  path: List<&2, String>,  +lvs: List<&2, Level>) -> Result<&2, &2, ParseErr, Matched>:  match mm:    case Some{n}:      Fail{Missing{path, n}}    case None{}:      Done{matched.build(lvs, path)}# every command's defaults, then its required arguments, root firstdef parse.finish.ok(+path: List<&2, String>, +lvs: List<&2, Level>) -> Result<&2, &2, ParseErr, Matched>:  parse.finish.miss(miss.levels(lvs), path, lvs)# a finished walker: help, a pending value, an error, or defaults + requireddef parse.finish.mode(  mode: Mode,  +args: List<&2, Arg>,  +up: List<&2, Level>,  +path: List<&2, String>,  +binds: List<&2, Bind>) -> Result<&2, &2, ParseErr, Matched>:  match mode:    case Dead{e}:      Fail{e}    case Need{name}:      Fail{NoValue{path, name}}    case Help{}:      Fail{NeedHelp{path}}    case Free{}:      parse.finish.ok(path, List.reverse(&2, Level, fill.levels(Level{args, binds} <> up)))# the walker, finisheddef parse.finish(st: St) -> Result<&2, &2, ParseErr, Matched>:  St{mode, args, up, _s, path, binds, _p, _r, _n} = st  parse.finish.mode(mode, args, up, path, binds)# argv against a Clidef parse(app: Cli, argv: List<&2, String>) -> Result<&2, &2, ParseErr, Matched>:  parse.finish(parse.walk(argv, parse.start(app)))# this pair's value when its name matches, else the restdef env.get.at(hit: Bool, +vv: String, rest: Maybe<&2, String>) -> Maybe<&2, String>:  match hit:    case True{}:      Some{vv}    case False{}:      rest# the value of the first pair named `name`def env.get(vars: List<&2, Var>, +name: String) -> Maybe<&2, String>:  match vars:    case Nil{}:      None{}    case Con{hh, tt}:      (+nn, +vv) = hh      env.get.at(String.eq(nn, name), vv, env.get(tt, name))# a value that is not emptydef env.full(mv: Maybe<&2, String>) -> Maybe<&2, String>:  match mv:    case None{}:      None{}    case Some{+vv}:      Bool.pick(Maybe<&2, String>, String.is_empty(vv), None{}, Some{vv})# the env value of an env var: its first pair's value, when that is not emptydef env.of(ev: Maybe<&2, String>, vars: List<&2, Var>) -> Maybe<&2, String>:  match ev:    case None{}:      None{}    case Some{+var}:      env.full(env.get(vars, var))# whether an env value turns a flag off, as clap's `SetTrue` reads itdef env.off(+vv: String) -> Bool:  allowed.ok(["0", "false", "no", "off", "n", "f"], String.to_lower(vv))# the default an env value gives an argument of this kind: a flag is set# unless the value turns it off; every other kind takes the valuedef env.kind(kk: ArgKind, +vv: String) -> Maybe<&2, String>:  match kk:    case Flag{}:      Bool.pick(Maybe<&2, String>, env.off(vv), None{}, Some{"true"})    case Opt{}:      Some{vv}    case Many{}:      Some{vv}    case Pos{}:      Some{vv}    case Rest{}:      Some{vv}# a fallback with its default replaced by what the env value gives, when# there is onedef resolve.with(  found: Maybe<&2, String>,  kk: ArgKind,  ev: Maybe<&2, String>,  dd: Maybe<&2, String>) -> Fallback:  match found:    case None{}:      Fallback{ev, dd}    case Some{+vv}:      Fallback{ev, env.kind(kk, vv)}# an argument whose default is its fallback under `vars`def resolve.arg(aa: Arg, vars: List<&2, Var>) -> Arg:  Arg{+n, +s, +l, +kk, +h, +r, Fallback{+ev, dd}, +cs} = aa  Arg{n, s, l, kk, h, r, resolve.with(env.of(ev, vars), kk, ev, dd), cs}# every argument's default is its fallbackdef resolve.args(args: List<&2, Arg>, +vars: List<&2, Var>) -> List<&2, Arg>:  match args:    case Nil{}:      []    case Con{hh, tt}:      resolve.arg(hh, vars) <> resolve.args(tt, vars)# every argument of every subcommand, its default its fallbackdef resolve.subs(subs: List<&2, Sub>, +vars: List<&2, Var>) -> List<&2, Sub>:  match subs:    case Nil{}:      []    case Con{hh, tt}:      Sub{+n, +a, args, kids} = hh      Sub{n, a, resolve.args(args, vars), resolve.subs(kids, vars)} <> resolve.subs(tt, vars)# the spec whose every argument's default is its fallback under `vars`def resolve(app: Cli, +vars: List<&2, Var>) -> Cli:  Cli{+n, +a, +v, args, subs} = app  Cli{n, a, v, resolve.args(args, vars), resolve.subs(subs, vars)}# whether this kind is a flagdef env.flag.is(kk: ArgKind) -> Bool:  match kk:    case Flag{}:      True{}    case Opt{}:      False{}    case Many{}:      False{}    case Pos{}:      False{}    case Rest{}:      False{}# an argument bound to an env value outside its choices: never a flag, and# only for a value its command's bindings holddef env.bad.val(  found: Maybe<&2, String>,  kk: ArgKind,  +name: String,  +cs: List<&2, String>,  +binds: List<&2, Bind>) -> Maybe<&2, Var>:  match found:    case None{}:      None{}    case Some{+vv}:      Bool.pick(Maybe<&2, Var>,        Bool.and(Bool.not(env.flag.is(kk)), Bool.and(Bool.not(allowed(cs, vv)),          List.contains(~String, ~String.eq, get_all.bind(binds, name), vv))),        Some{(name, vv)}, None{})# this argument's name and env value, when its command's bindings hold that# value and its choices do notdef env.bad.arg(aa: Arg, vars: List<&2, Var>, +binds: List<&2, Bind>) -> Maybe<&2, Var>:  Arg{+n, _s, _l, kk, _h, _r, Fallback{ev, _d}, +cs} = aa  env.bad.val(env.of(ev, vars), kk, n, cs, binds)# the first of two answers that is onedef env.first(here: Maybe<&2, Var>, rest: Maybe<&2, Var>) -> Maybe<&2, Var>:  match here:    case None{}:      rest    case Some{pp}:      Some{pp}# the first argument of a command bound to an env value outside its choicesdef env.bad.args(  args: List<&2, Arg>,  +vars: List<&2, Var>,  +binds: List<&2, Bind>) -> Maybe<&2, Var>:  match args:    case Nil{}:      None{}    case Con{hh, tt}:      env.first(env.bad.arg(hh, vars, binds), env.bad.args(tt, vars, binds))# the arguments of a found subcommand, or nonedef env.sub.args(found: Maybe<&2, Sub>) -> List<&2, Arg>:  match found:    case None{}:      []    case Some{ss}:      Sub{_n, _a, args, _k} = ss      args# the subcommands of a found subcommand, or nonedef env.sub.kids(found: Maybe<&2, Sub>) -> List<&2, Sub>:  match found:    case None{}:      []    case Some{ss}:      Sub{_n, _a, _g, kids} = ss      kids# the first argument bound to an env value outside its choices, the command# first, then down the selected pathdef env.bad.go(  mm: Matched,  +args: List<&2, Arg>,  +subs: List<&2, Sub>,  +vars: List<&2, Var>) -> Maybe<&2, Var>:  match mm:    case Leaf{+bs}:      env.bad.args(args, vars, bs)    case Node{+bs, +name, next}:      env.first(env.bad.args(args, vars, bs),        env.bad.go(next, env.sub.args(find_sub(subs, name)), env.sub.kids(find_sub(subs, name)), vars))# a success, or the first env value outside its choices that it bounddef env.check.done(bad: Maybe<&2, Var>, +mm: Matched) -> Result<&2, &2, ParseErr, Matched>:  match bad:    case None{}:      Done{mm}    case Some{pp}:      (+name, +vv) = pp      Fail{BadValue{path_of(mm), name, vv}}# a successful parse checked for env values outside their choicesdef env.check.ok(app: Cli, +vars: List<&2, Var>, +mm: Matched) -> Result<&2, &2, ParseErr, Matched>:  Cli{_n, _a, _v, +args, subs} = app  env.check.done(env.bad.go(mm, args, subs, vars), mm)# a failed parse as it is; a successful one checked for env values outside# their choicesdef env.check(  app: Cli,  +vars: List<&2, Var>,  got: Result<&2, &2, ParseErr, Matched>) -> Result<&2, &2, ParseErr, Matched>:  match got:    case Fail{ee}:      Fail{ee}    case Done{+mm}:      env.check.ok(app, vars, mm)# argv against a Cli, every argument falling back to its env value under# `vars`, then to its defaultdef parse_env(  +app: Cli,  argv: List<&2, String>,  +vars: List<&2, Var>) -> Result<&2, &2, ParseErr, Matched>:  env.check(app, vars, parse(resolve(app, vars), argv))# an env var in front of the names, when there is onedef env.names.put(ev: Maybe<&2, String>, rest: List<&2, String>) -> List<&2, String>:  match ev:    case None{}:      rest    case Some{var}:      var <> rest# the env vars of a command's arguments, in orderdef env.names.args(args: List<&2, Arg>) -> List<&2, String>:  match args:    case Nil{}:      []    case Con{hh, tt}:      Arg{_n, _s, _l, _k, _h, _r, Fallback{ev, _d}, _c} = hh      env.names.put(ev, env.names.args(tt))# the env vars of every subcommand's arguments, depth firstdef env.names.forest(subs: List<&2, Sub>) -> List<&2, String>:  match subs:    case Nil{}:      []    case Con{hh, tt}:      Sub{_n, _a, args, kids} = hh      List.append(&2, String, env.names.args(args),        List.append(&2, String, env.names.forest(kids), env.names.forest(tt)))# the env vars of a spec: the root's arguments', then every subcommand'sdef env.names(app: Cli) -> List<&2, String>:  Cli{_n, _a, _v, args, subs} = app  List.append(&2, String, env.names.args(args), env.names.forest(subs))# the short/long label of an option, plus a metavar for Optdef help.label.kind(kk: ArgKind, +name: String, +label: String) -> String:  match kk:    case Flag{}:      label    case Opt{}:      label ++ " <" ++ String.to_upper(name) ++ ">"    case Many{}:      label ++ " <" ++ String.to_upper(name) ++ ">..."    case Pos{}:      label    case Rest{}:      label# `-s, --long` or one of themdef help.label.pair(+short: String, +long: String) -> String:  Bool.pick(String, String.is_empty(short),    Bool.pick(String, String.is_empty(long), "", "    --" ++ long),    Bool.pick(String, String.is_empty(long), "  -" ++ short,      "  -" ++ short ++ ", --" ++ long))# the usage label of an optiondef help.label(aa: Arg) -> String:  Arg{+name, short, long, kind, _h, _r, _d, _c} = aa  help.label.kind(kind, name, help.label.pair(text.of(short, ""), text.of(long, "")))# extras after the help string: default and choicesdef help.extra.go(+dd: String, +cs: List<&2, String>) -> String:  Bool.pick(String, String.is_empty(dd),    Bool.pick(String, List.is_empty(&2, String, cs), "",      " [possible values: " ++ String.join(cs, ", ") ++ "]"),    Bool.pick(String, List.is_empty(&2, String, cs),      " [default: " ++ dd ++ "]",      " [default: " ++ dd ++ "] [possible values: " ++ String.join(cs, ", ") ++        "]"))# the env var after the help string, before the defaultdef help.env(ev: Maybe<&2, String>) -> String:  match ev:    case None{}:      ""    case Some{var}:      " [env: " ++ var ++ "]"# extras after the help string: env var, default and choicesdef help.extra(aa: Arg) -> String:  Arg{_n, _s, _l, _k, _h, _r, Fallback{ev, default}, cs} = aa  help.env(ev) ++ help.extra.go(text.of(default, ""), cs)# one option line, given the label widthdef help.opt_line(+aa: Arg, ww: Nat) -> String:  Arg{_n, _s, _l, _k, +h, _r, _d, _c} = aa  help.pad(help.label(aa), ww) ++ "  " ++ h ++ help.extra(aa)# the max option-label widthdef help.opt_width(args: List<&2, Arg>, nn: Nat) -> Nat:  match args:    case Nil{}:      nn    case Con{+h, t}:      help.opt_width(t, help.max(nn, String.length(help.label(h))))# a non-positional option linedef help.opt_lines.at(skip: Bool, hh: Arg, rest: String, ww: Nat) -> String:  match skip:    case True{}:      rest    case False{}:      help.opt_line(hh, ww) ++ "\n" ++ rest# option lines, skipping positionalsdef help.opt_lines(args: List<&2, Arg>, +ww: Nat) -> String:  match args:    case Nil{}:      ""    case Con{+h, t}:      help.opt_lines.at(pos.is(h), h, help.opt_lines(t, ww), ww)# wrap option lines in a headingdef help.opts.go(+body: String) -> String:  Bool.pick(String, String.is_empty(body), "", "Options:\n" ++ body ++ "\n")# the Options block, or empty when there are nonedef help.opts(+args: List<&2, Arg>) -> String:  help.opts.go(help.opt_lines(args, help.opt_width(args, 0n)))# a command's namedef help.sub_name(ss: Sub) -> String:  Sub{n, _a, _g, _c} = ss  n# a command's aboutdef help.sub_about(ss: Sub) -> String:  Sub{_n, a, _g, _c} = ss  a# max command-name width, including `help`def help.cmd_width(subs: List<&2, Sub>, nn: Nat) -> Nat:  match subs:    case Nil{}:      help.max(nn, String.length("help"))    case Con{+h, t}:      help.cmd_width(t, help.max(nn, String.length(help.sub_name(h))))# one command linedef help.cmd_line(+name: String, +about: String, ww: Nat) -> String:  "  " ++ help.pad(name, ww) ++ "  " ++ about ++ "\n"# command lines, then `help`def help.cmd_lines(subs: List<&2, Sub>, +ww: Nat) -> String:  match subs:    case Nil{}:      help.cmd_line("help",        "Print this message or the help of the given subcommand(s)", ww)    case Con{+h, t}:      help.cmd_line(help.sub_name(h), help.sub_about(h), ww) ++ help.cmd_lines(t, ww)# the Commands blockdef help.cmds(+subs: List<&2, Sub>) -> String:  "Commands:\n" ++ help.cmd_lines(subs, help.cmd_width(subs, 0n)) ++ "\n"# a rest mark: `...`, or emptydef help.pos.dots(many: Bool) -> String:  Bool.pick(String, many, "...", "")# a positional in a usage line: `<NAME>` or `[NAME]`def help.pos.wrap(req: Bool, +nn: String) -> String:  Bool.pick(String, req, " <" ++ nn ++ ">", " [" ++ nn ++ "]")# a positional in a usage line: `<NAME>` or `[NAME]`, plus `...` when restdef help.pos.one(aa: Arg) -> String:  Arg{+n, _s, _l, kind, _h, +req, _d, _c} = aa  help.pos.wrap(req, String.to_upper(n)) ++ help.pos.dots(rest.is.kind(kind))# this positional's hint, else the restdef help.pos.at(keep: Bool, hh: Arg, rest: String) -> String:  match keep:    case True{}:      help.pos.one(hh) ++ rest    case False{}:      rest# every positional in a usage linedef help.pos(args: List<&2, Arg>) -> String:  match args:    case Nil{}:      ""    case Con{+h, t}:      help.pos.at(pos.is(h), h, help.pos(t))# the bracketed options word when the spec has a flag or optiondef help.opts_hint.go(has: Bool) -> String:  Bool.pick(String, has, " [" ++ String.to_upper("options") ++ "]", "")# True when this Arg is a flag or option, else the restdef help.has_opt.at(is_pos: Bool, rest: Bool) -> Bool:  match is_pos:    case True{}:      rest    case False{}:      True{}# whether any Arg is not a positionaldef help.has_opt(args: List<&2, Arg>) -> Bool:  match args:    case Nil{}:      False{}    case Con{+h, t}:      help.has_opt.at(pos.is(h), help.has_opt(t))# the options hint when neededdef help.opts_hint(args: List<&2, Arg>) -> String:  help.opts_hint.go(help.has_opt(args))# `[COMMAND]` when there are subcommandsdef help.cmd_hint(+subs: List<&2, Sub>) -> String:  Bool.pick(String, List.is_empty(&2, Sub, subs), "", " [COMMAND]")# `Usage: name ...`def help.usage(  +name: String,  +args: List<&2, Arg>,  +subs: List<&2, Sub>) -> String:  "Usage: " ++ name ++ help.opts_hint(args) ++ help.pos(args) ++ help.cmd_hint(subs)# `name` or `name version`def title.go(+name: String, vv: Maybe<&2, String>) -> String:  match vv:    case None{}:      name    case Some{ver}:      name ++ " " ++ ver# one help pagedef help.page(  +title: String,  +about: String,  +usage: String,  +args: List<&2, Arg>,  +subs: List<&2, Sub>) -> String:  title ++ "\n" ++ about ++ "\n\n" ++ usage ++ "\n\n" ++ help.cmds(subs) ++    help.opts(args)# a found child becomes the page; an unknown name staysdef help.step.go(  found: Maybe<&2, Sub>,  +bin: String,  +about: String,  version: Maybe<&2, String>,  args: List<&2, Arg>,  subs: List<&2, Sub>,  +next: String,  +usage: String) -> How:  match found:    case None{}:      How{bin, about, version, args, subs, usage}    case Some{s}:      Sub{+name, +about2, args2, subs2} = s      How{name, about2, None{}, args2, subs2, usage ++ " " ++ next}# one path segment against the current pagedef help.step(how: How, +name: String) -> How:  How{+bin, +about, version, args, +subs, +usage} = how  help.step.go(find_sub(subs, name), bin, about, version, args, subs, name,    usage)# the page at the end of a shrinking pathdef help.go(path: List<&2, String>, how: How) -> String:  match path:    case Nil{}:      How{+bin, +about, version, +args, +subs, +usage} = how      help.page(title.go(bin, version), about, help.usage(usage, args, subs),        args, subs)    case Con{+h, t}:      help.go(t, help.step(how, h))# usage for `path` under `app`; an unknown segment shows the last found pagedef help(+app: Cli, path: List<&2, String>) -> String:  Cli{+name, +about, version, args, subs} = app  help.go(path, How{name, about, version, args, subs, name})# the usage line of the page at the end of a shrinking pathdef usage.go(path: List<&2, String>, how: How) -> String:  match path:    case Nil{}:      How{_b, _a, _v, +args, +subs, +usage} = how      help.usage(usage, args, subs)    case Con{+hh, tt}:      usage.go(tt, help.step(how, hh))# the usage line of the command at `path`, as `help` shows itdef usage.at(+app: Cli, path: List<&2, String>) -> String:  Cli{+name, +about, version, args, subs} = app  usage.go(path, How{name, about, version, args, subs, name})# where to look for more: `help`, and the path when there is onedef err.hint(+path: List<&2, String>) -> String:  "For more information, try 'help" ++    Bool.pick(String, List.is_empty(&2, String, path), "", " " ++ String.join(path, " ")) ++ "'.\n"# a short error, the usage line of the command where it happened, and the hintdef err.wrap(+app: Cli, +at: List<&2, String>, +msg: String) -> String:  msg ++ "\n\n" ++ usage.at(app, at) ++ "\n\n" ++ err.hint(at)# the head of a row, or 0 past its enddef osa.head(xs: List<&2, Nat>) -> Nat:  match xs:    case Nil{}:      0n    case Con{hh, _t}:      hh# a row after its headdef osa.tail(xs: List<&2, Nat>) -> List<&2, Nat>:  match xs:    case Nil{}:      []    case Con{_h, tt}:      tt# the last cell of a row, `dd` for an empty onedef osa.last(xs: List<&2, Nat>, dd: Nat) -> Nat:  match xs:    case Nil{}:      dd    case Con{hh, tt}:      osa.last(tt, hh)# the first row: `ii`, then one more for each chardef osa.first(bs: List<&2, Char>, +ii: Nat) -> List<&2, Nat>:  match bs:    case Nil{}:      [ii]    case Con{_c, tt}:      ii <> osa.first(tt, 1n+ii)# whether the char before `cb` is `ca`, once the char before `ca` is `xa`def osa.swap.b(+xa: Char, pb: Maybe<&2, Char>, +ca: Char, +cb: Char) -> Bool:  match pb:    case None{}:      False{}    case Some{xb}:      Bool.and(Char.is_eq(ca, xb), Char.is_eq(xa, cb))# whether the two chars before these, swapped, are these twodef osa.swap(pa: Maybe<&2, Char>, pb: Maybe<&2, Char>, +ca: Char, +cb: Char) -> Bool:  match pa:    case None{}:      False{}    case Some{xa}:      osa.swap.b(xa, pb, ca, cb)# the cheaper of a cell and a swap from two rows up and two cells leftdef osa.cell.swap(swap: Bool, +best: Nat, +far: Nat) -> Nat:  Bool.pick(Nat, swap, Nat.min(best, 1n+far), best)# one cell: the cheapest of a deletion, an insertion, a substitution and a# swap of two adjacent charsdef osa.cell(  swap: Bool,  +up: Nat,  +left: Nat,  +diag: Nat,  +cost: Nat,  +far: Nat) -> Nat:  osa.cell.swap(swap, Nat.min(Nat.min(1n+up, 1n+left), Nat.add(diag, cost)), far)# the rest of a row for the char `ca`, whose char before is `pa`: `ps` is the# row above from the cell above-left on, `qs` the row two up from two cells# left on, `pb` the char of `bs` before its headdef osa.row(  bs: List<&2, Char>,  +ca: Char,  +pa: Maybe<&2, Char>,  pb: Maybe<&2, Char>,  left: Nat,  +ps: List<&2, Nat>,  +qs: List<&2, Nat>) -> List<&2, Nat>:  match bs:    case Nil{}:      []    case Con{+cb, bt}:      +rr = osa.cell(osa.swap(pa, pb, ca, cb), osa.head(osa.tail(ps)), left, osa.head(ps),        Bool.pick(Nat, Char.is_eq(ca, cb), 0n, 1n), osa.head(qs))      rr <> osa.row(bt, ca, pa, Some{cb}, rr, osa.tail(ps), osa.tail(qs))# the rows for the chars of `as`, the last row `pp` and the one before `qq`,# down to the distance in the last row's last celldef osa.rows(  as: List<&2, Char>,  +bs: List<&2, Char>,  pa: Maybe<&2, Char>,  +ii: Nat,  +pp: List<&2, Nat>,  qq: List<&2, Nat>) -> Nat:  match as:    case Nil{}:      osa.last(pp, 0n)    case Con{+ca, at}:      osa.rows(at, bs, Some{ca}, 1n+ii, (1n+ii) <> osa.row(bs, ca, pa, None{}, 1n+ii, pp, 0n <> qq), pp)# the optimal string alignment distance from `aa` to `bb`: the fewest# insertions, deletions, substitutions and swaps of two adjacent chars, no# char edited twicedef osa(aa: String, bb: String) -> Nat:  +bs = String.to_list(bb)  osa.rows(String.to_list(aa), bs, None{}, 0n, osa.first(bs, 0n), osa.first(bs, 0n))# whether a candidate is close to the typed spellingdef sug.close.go(+dd: Nat, +room: Nat) -> Bool:  Bool.and(Nat.is_lt(0n, dd), Nat.is_le(Nat.mul(3n, dd), room))# whether `cc` is close to `typed`: some edits away, and at most a third of# the longer as manydef sug.close(+typed: String, +cc: String) -> Bool:  sug.close.go(osa(typed, cc), Nat.max(String.length(typed), String.length(cc)))# a close candidate against the closest one after it: the earlier on a tiedef sug.keep.rest(+typed: String, +cc: String, rest: Maybe<&2, String>) -> Maybe<&2, String>:  match rest:    case None{}:      Some{cc}    case Some{+ww}:      Bool.pick(Maybe<&2, String>, Nat.is_le(osa(typed, cc), osa(typed, ww)), Some{cc}, Some{ww})# a candidate, when it is close, against the closest one after itdef sug.keep(close: Bool, +typed: String, +cc: String, rest: Maybe<&2, String>) -> Maybe<&2, String>:  match close:    case False{}:      rest    case True{}:      sug.keep.rest(typed, cc, rest)# the closest close candidate, the first among equalsdef sug.best(cands: List<&2, String>, +typed: String) -> Maybe<&2, String>:  match cands:    case Nil{}:      None{}    case Con{+cc, tt}:      sug.keep(sug.close(typed, cc), typed, cc, sug.best(tt, typed))# a long spelling in front of the rest, when there is onedef sug.cands.put(long: Maybe<&2, String>, rest: List<&2, String>) -> List<&2, String>:  match long:    case None{}:      rest    case Some{ll}:      ll <> rest# the long spellings of a command's arguments, in orderdef sug.cands(args: List<&2, Arg>) -> List<&2, String>:  match args:    case Nil{}:      []    case Con{hh, tt}:      Arg{_n, _s, long, _k, _h, _r, _d, _c} = hh      sug.cands.put(long, sug.cands(tt))# the arguments of the page at the end of a shrinking pathdef sug.args.go(path: List<&2, String>, how: How) -> List<&2, Arg>:  match path:    case Nil{}:      How{_b, _a, _v, args, _s, _u} = how      args    case Con{+hh, tt}:      sug.args.go(tt, help.step(how, hh))# the arguments of the command at `path`, reached as `help` reaches itdef sug.args(+app: Cli, path: List<&2, String>) -> List<&2, Arg>:  Cli{+name, +about, version, args, subs} = app  sug.args.go(path, How{name, about, version, args, subs, name})# the spelling a long option word was typed with: its chars up to `=`def sug.typed(cut: String & Maybe<&2, String>) -> String:  (nn, _v) = cut  nn# a candidate spelled as a long option worddef sug.found(mm: Maybe<&2, String>) -> Maybe<&2, String>:  match mm:    case None{}:      None{}    case Some{cc}:      Some{"--" ++ cc}# the suggestion for a word, when it is a long option worddef sug.word.go(long: Bool, +app: Cli, +at: List<&2, String>, +word: String) -> Maybe<&2, String>:  match long:    case False{}:      None{}    case True{}:      sug.found(sug.best(sug.cands(sug.args(app, at)), sug.typed(cut_eq(String.drop(word, 2n)))))# the long spelling a word refused at `at` was likely meant to bedef sug.word(+app: Cli, +at: List<&2, String>, +word: String) -> Maybe<&2, String>:  sug.word.go(String.starts_with(word, "--"), app, at, word)# the long spelling an unknown long option was likely meant to be, as clap's# "a similar argument exists"; None for every other errordef suggestion(+app: Cli, ee: ParseErr) -> Maybe<&2, String>:  match ee:    case UnknownFlag{+at, +word}:      sug.word(app, at, word)    case Missing{_at, _name}:      None{}    case NoValue{_at, _name}:      None{}    case BadValue{_at, _name, _value}:      None{}    case NeedHelp{_path}:      None{}    case Unexpected{_at, _arg}:      None{}    case Repeated{_at, _name}:      None{}# clap's tip line for a suggestion, or nothingdef sug.tip(mm: Maybe<&2, String>) -> String:  match mm:    case None{}:      ""    case Some{ss}:      "\n\n  tip: a similar argument exists: '" ++ ss ++ "'"# error text; NeedHelp is empty (the caller prints `help`)def err_text(+app: Cli, ee: ParseErr) -> String:  match ee:    case UnknownFlag{+at, +word}:      err.wrap(app, at, "error: unexpected argument '" ++ word ++ "' found" ++ sug.tip(sug.word(app, at, word)))    case Missing{+at, name}:      err.wrap(app, at, "error: the following required argument was not provided:\n  " ++        name)    case NoValue{+at, name}:      err.wrap(app, at, "error: a value is required for '" ++ name ++ "' but none was supplied")    case BadValue{+at, name, value}:      err.wrap(app, at, "error: invalid value '" ++ value ++ "' for '" ++ name ++ "'")    case NeedHelp{_path}:      ""    case Unexpected{+at, arg}:      err.wrap(app, at, "error: unexpected argument '" ++ arg ++ "' found")    case Repeated{+at, name}:      err.wrap(app, at, "error: the argument '" ++ name ++ "' cannot be used multiple times")