#moonbitlang/core/argparse

    Declarative argument parsing for MoonBit.

    This package is inspired by clap and keeps a small, predictable feature set.

    long defaults to the argument name. Pass long="" to disable long alias. Argument constructors are named FlagArg / OptionArg / PositionArg to avoid shadowing built-in types like Option.

    #Quick Start

    For tests, snapshotting the failure message is the recommended way to cover both the parse error and the full contextual help text.

    ///|
    test "basic option + positional success snapshot" {
    let matches = @argparse.parse(
    Command("demo", options=[OptionArg("name")], positionals=[
    PositionArg("target"),
    ]),
    argv=["--name", "alice", "file.txt"],
    env=Map([]),
    )
    @debug.debug_inspect(
    matches.values,
    content=(
    #|{ "name": ["alice"], "target": ["file.txt"] }
    ),
    )
    }

    ///|
    test "basic option + positional failure snapshot" {
    let cmd = @argparse.Command("demo", options=[OptionArg("name")], positionals=[
    PositionArg("target"),
    ])
    try cmd.parse(argv=["--bad"], env=Map([])) catch {
    err =>
    inspect(
    err,
    content=(
    #|error: unexpected argument '--bad' found
    #|
    #|Usage: demo [options] [target]
    #|
    #|Arguments:
    #| target
    #|
    #|Options:
    #| -h, --help Show help information.
    #| --name <name>
    #|
    ),
    )
    } noraise {
    _ => panic()
    }
    }

    #Using it for async CLI

    You can call cmd.parse() directly inside async fn main. On parse failure, argparse automatically prints the error message with full contextual help text.

    ///|
    async fn main {
    let cmd = @argparse.Command("demo", options=[@argparse.OptionArg("name")], positionals=[
    @argparse.PositionArg("target"),
    ])
    let _ = cmd.parse()
    }

    #Flags And Negation

    flags stay as Map[String, Bool], so negated flags preserve explicit false states.

    ///|
    test "negatable flag success snapshot" {
    let cmd = @argparse.Command("demo", flags=[FlagArg("cache", negatable=true)])
    inspect(
    cmd.render_help(),
    content=(
    #|Usage: demo [options]
    #|
    #|Options:
    #| -h, --help Show help information.
    #| --[no-]cache
    #|
    ),
    )

    let parsed = try! cmd.parse(argv=["--no-cache"], env=Map([]))
    @debug.debug_inspect(
    parsed.flags,
    content=(
    #|{ "cache": false }
    ),
    )
    }

    #Subcommands And Globals

    ///|
    test "global count flag success snapshot" {
    let cmd = @argparse.Command(
    "demo",
    flags=[FlagArg("verbose", short='v', action=Count, global=true)],
    subcommands=[Command("run")],
    )

    let parsed = try! cmd.parse(argv=["-v", "run", "-v"], env=Map([]))
    @debug.debug_inspect(
    parsed.flag_counts,
    content=(
    #|{ "verbose": 2 }
    ),
    )
    parsed.subcommand is Some(("run", child))
    @debug.debug_inspect(
    child.flag_counts,
    content=(
    #|{ "verbose": 2 }
    ),
    )
    }

    ///|
    test "subcommand context failure snapshot" {
    let cmd = @argparse.Command(
    "demo",
    flags=[FlagArg("verbose", short='v', action=Count, global=true)],
    subcommands=[Command("run")],
    )
    try cmd.parse(argv=["run", "--oops"], env=Map([])) catch {
    err =>
    inspect(
    err,
    content=(
    #|error: unexpected argument '--oops' found
    #|
    #|Usage: demo run [options]
    #|
    #|Options:
    #| -h, --help Show help information.
    #| -v, --verbose
    #|
    ),
    )
    } noraise {
    _ => panic()
    }
    }

    #Value Sources (argv > env > default_values)

    Value precedence is argv > env > default_values.

    ///|
    test "value source precedence snapshots" {
    let cmd = @argparse.Command("demo", options=[
    OptionArg("level", env="LEVEL", default_values=["1"]),
    ])

    inspect(
    cmd.render_help(),
    content=(
    #|Usage: demo [options]
    #|
    #|Options:
    #| -h, --help Show help information.
    #| --level <level> [env: LEVEL] [default: 1]
    #|
    ),
    )

    let from_default = try! cmd.parse(argv=[], env=Map([]))
    @debug.debug_inspect(
    from_default.values,
    content=(
    #|{ "level": ["1"] }
    ),
    )
    @debug.debug_inspect(
    from_default.sources,
    content=(
    #|{ "level": Default }
    ),
    )

    let from_env = try! cmd.parse(argv=[], env={ "LEVEL": "2" })
    @debug.debug_inspect(
    from_env.values,
    content=(
    #|{ "level": ["2"] }
    ),
    )
    @debug.debug_inspect(
    from_env.sources,
    content=(
    #|{ "level": Env }
    ),
    )

    let from_argv = try! cmd.parse(argv=["--level", "3"], env={ "LEVEL": "2" })
    @debug.debug_inspect(
    from_argv.values,
    content=(
    #|{ "level": ["3"] }
    ),
    )
    @debug.debug_inspect(
    from_argv.sources,
    content=(
    #|{ "level": Argv }
    ),
    )
    }

    #Input Forms

    ///|
    test "option input forms snapshot" {
    let cmd = @argparse.Command("demo", options=[OptionArg("count", short='c')])

    inspect(
    cmd.render_help(),
    content=(
    #|Usage: demo [options]
    #|
    #|Options:
    #| -h, --help Show help information.
    #| -c, --count <count>
    #|
    ),
    )

    let long_split = try! cmd.parse(argv=["--count", "2"], env=Map([]))
    @debug.debug_inspect(
    long_split.values,
    content=(
    #|{ "count": ["2"] }
    ),
    )

    let long_inline = try! cmd.parse(argv=["--count=3"], env=Map([]))
    @debug.debug_inspect(
    long_inline.values,
    content=(
    #|{ "count": ["3"] }
    ),
    )

    let short_split = try! cmd.parse(argv=["-c", "4"], env=Map([]))
    @debug.debug_inspect(
    short_split.values,
    content=(
    #|{ "count": ["4"] }
    ),
    )

    let short_attached = try! cmd.parse(argv=["-c5"], env=Map([]))
    @debug.debug_inspect(
    short_attached.values,
    content=(
    #|{ "count": ["5"] }
    ),
    )
    }

    ///|
    test "double-dash separator snapshot" {
    let cmd = @argparse.Command("demo", positionals=[
    PositionArg("tail", num_args=ValueRange(lower=0), allow_hyphen_values=true),
    ])
    let parsed = try! cmd.parse(argv=["--", "--x", "-y"], env=Map([]))
    @debug.debug_inspect(
    parsed.values,
    content=(
    #|{ "tail": ["--x", "-y"] }
    ),
    )
    }

    #Constraints And Policies

    parse raises a single display-ready error string that includes the error and full contextual help.

    ///|
    test "requires relationship success and failure snapshots" {
    let cmd = @argparse.Command("demo", options=[
    OptionArg("mode", requires=["config"]),
    OptionArg("config"),
    ])

    let ok = try! cmd.parse(
    argv=["--mode", "fast", "--config", "cfg.toml"],
    env=Map([]),
    )
    @debug.debug_inspect(
    ok.values,
    content=(
    #|{ "mode": ["fast"], "config": ["cfg.toml"] }
    ),
    )

    try cmd.parse(argv=["--mode", "fast"], env=Map([])) catch {
    err =>
    inspect(
    err,
    content=(
    #|error: the following required argument was not provided: 'config' (required by 'mode')
    #|
    #|Usage: demo [options]
    #|
    #|Options:
    #| -h, --help Show help information.
    #| --mode <mode>
    #| --config <config>
    #|
    ),
    )
    } noraise {
    _ => panic()
    }
    }

    ///|
    test "arg group required and exclusive failure snapshot" {
    let cmd = @argparse.Command(
    "demo",
    groups=[
    ArgGroup("mode", required=true, multiple=false, args=["fast", "slow"]),
    ],
    flags=[FlagArg("fast"), FlagArg("slow")],
    )

    try cmd.parse(argv=[], env=Map([])) catch {
    err =>
    inspect(
    err,
    content=(
    #|error: the following required arguments were not provided:
    #| <--fast|--slow>
    #|
    #|Usage: demo [options]
    #|
    #|Options:
    #| -h, --help Show help information.
    #| --fast
    #| --slow
    #|
    #|Groups:
    #| mode [required] [exclusive] --fast, --slow
    #|
    ),
    )
    } noraise {
    _ => panic()
    }
    }

    ///|
    test "subcommand required policy failure snapshot" {
    let cmd = @argparse.Command("demo", subcommand_required=true, subcommands=[
    Command("echo"),
    ])

    try cmd.parse(argv=[], env=Map([])) catch {
    err =>
    inspect(
    err,
    content=(
    #|error: the following required argument was not provided: 'subcommand'
    #|
    #|Usage: demo <command>
    #|
    #|Commands:
    #| echo
    #| help Print help for the subcommand(s).
    #|
    #|Options:
    #| -h, --help Show help information.
    #|
    ),
    )
    } noraise {
    _ => panic()
    }
    }

    ///|
    test "conflicts_with success and failure snapshots" {
    let cmd = @argparse.Command("demo", flags=[
    FlagArg("verbose", conflicts_with=["quiet"]),
    FlagArg("quiet"),
    ])

    let ok = try! cmd.parse(argv=["--verbose"], env=Map([]))
    @debug.debug_inspect(
    ok.flags,
    content=(
    #|{ "verbose": true }
    ),
    )

    try cmd.parse(argv=["--verbose", "--quiet"], env=Map([])) catch {
    err =>
    inspect(
    err,
    content=(
    #|error: conflicting arguments: verbose and quiet
    #|
    #|Usage: demo [options]
    #|
    #|Options:
    #| -h, --help Show help information.
    #| --verbose
    #| --quiet
    #|
    ),
    )
    } noraise {
    _ => panic()
    }
    }

    #PositionArg Value Ranges

    Positionals are parsed in declaration order (no explicit index).

    ///|
    test "bounded non-last positional success snapshot" {
    let cmd = @argparse.Command("demo", positionals=[
    PositionArg("first", num_args=ValueRange(lower=1, upper=2)),
    PositionArg("second", num_args=@argparse.ValueRange::single()),
    ])

    let parsed = try! cmd.parse(argv=["a", "b", "c"], env=Map([]))
    @debug.debug_inspect(
    parsed.values,
    content=(
    #|{ "first": ["a", "b"], "second": ["c"] }
    ),
    )
    }

    ///|
    test "bounded non-last positional failure snapshot" {
    let cmd = @argparse.Command("demo", positionals=[
    PositionArg("first", num_args=ValueRange(lower=1, upper=2)),
    PositionArg("second", num_args=@argparse.ValueRange::single()),
    ])
    try cmd.parse(argv=["a", "b", "c", "d"], env=Map([])) catch {
    err =>
    inspect(
    err,
    content=(
    #|error: unexpected value 'd' for '<second>' found; no more were expected
    #|
    #|Usage: demo <first...> <second>
    #|
    #|Arguments:
    #| first...
    #| second
    #|
    #|Options:
    #| -h, --help Show help information.
    #|
    ),
    )
    } noraise {
    _ => panic()
    }
    }

    ///|
    let cmd : @argparse.Command = Command(
    "wrap",
    options=[OptionArg("config"), OptionArg("mode")],
    positionals=[
    PositionArg(
    "child_argv",
    num_args=ValueRange(lower=0),
    allow_hyphen_values=true,
    ),
    ],
    )

    ///|
    test "positional passthrough keeps child argv after double-dash snapshot" {
    let parsed = try! cmd.parse(
    argv=[
    "--config", "cfg.toml", "--", "child", "--mode", "fast", "--", "--flag",
    ],
    env=Map([]),
    )
    @debug.debug_inspect(
    parsed.values,
    content=(
    #|{
    #| "config": ["cfg.toml"],
    #| "child_argv": ["child", "--mode", "fast", "--", "--flag"],
    #|}
    ),
    )
    }

    ///|
    test "without separator outer parser still consumes its own option names snapshot" {
    let parsed = try! cmd.parse(
    argv=["--config", "cfg.toml", "child", "--mode", "fast"],
    env=Map([]),
    )
    @debug.debug_inspect(
    parsed.values,
    content=(
    #|{ "config": ["cfg.toml"], "mode": ["fast"], "child_argv": ["child"] }
    ),
    )
    }

    ArgGroup

    Declarative argument group constructor.

    ArgGroup::ArgGroup

    #alias(new, deprecated="Use `ArgGroup()` instead")
    fn ArgGroup::ArgGroup(name : StringView, required? : Bool, multiple? : Bool, args? : ArrayView[String], requires? : ArrayView[String], conflicts_with? : ArrayView[String]) -> ArgGroup

    Create an argument group.

    Notes:
    • required=true means at least one member of the group must be present.
    • multiple=false means group members are mutually exclusive.
    • requires and conflicts_with may reference either group names or argument names.

    Command

    pub struct Command {
    // private fields
    } derive(
    Debug
    )

    Declarative command specification.

    Command::Command

    #alias(new, deprecated="Use `Command()` instead")
    fn Command::Command(name : StringView, flags? : ArrayView[FlagArg], options? : ArrayView[OptionArg], positionals? : ArrayView[PositionArg], subcommands? : ArrayView[Command], about? : StringView, version? : StringView, disable_help_flag? : Bool, disable_version_flag? : Bool, disable_help_subcommand? : Bool, arg_required_else_help? : Bool, subcommand_required? : Bool, hidden? : Bool, groups? : ArrayView[ArgGroup], default_subcommand? : StringView) -> Command

    Create a declarative command specification.

    Notes:
    • flags, options, and positionals declare arguments by kind.
    • groups declares argument-group membership and policies.
    • disable_help_flag / disable_version_flag disable built-in --help / --version.
    • disable_help_subcommand disables built-in help <subcommand> routing.
    • arg_required_else_help=true prints help when no argv tokens are provided.
    • subcommand_required=true requires selecting a subcommand.
    • default_subcommand dispatches missing subcommands to a visible child.
    • hidden=true omits this command from parent command listings.

    Command::parse

    #as_free_fn
    fn Command::parse(self : Command, argv? : ArrayView[String], env? : Map[String, String]) -> Matches raise

    Parse argv/environment according to this command spec.

    Behavior:
    • Help/version requests print output immediately and terminate with exit code 0.
    • Parse failures raise display-ready error text with full contextual help.
    • Command-definition validation failures raise display-ready validation text (without appended help).

    Explicit argv contains only arguments, without the program name. By default, the runtime command line is used with its executable prefix removed (including the script path on JavaScript/Node). Environment values are read only from the supplied env map, which defaults to empty; pass @env.get_env_vars() to use the process environment. Value precedence is argv > env > default_values.

    Command::render_help

    fn Command::render_help(self : Command) -> String

    Render help text without parsing.

    FlagAction

    pub(all) enum FlagAction {
    SetTrue
    SetFalse
    Count
    Help
    Version
    } derive(Eq,
    Debug
    )

    Behavior for flag args.

    • SetTrue / SetFalse set a boolean value.
    • Count increments Matches.flag_counts.
    • Help / Version display output and exit successfully when triggered.

    FlagAction::equal

    fn FlagAction::equal(FlagAction, FlagAction) -> Bool

    FlagAction::to_string

    fn FlagAction::to_string(self : FlagAction) -> String

    FlagArg

    pub struct FlagArg {
    // private fields
    } derive(
    Debug
    )

    Declarative flag constructor wrapper.

    FlagArg::FlagArg

    #alias(new, deprecated="Use `FlagArg()` instead")
    fn FlagArg::FlagArg(name : StringView, short? : Char, long? : StringView, about? : StringView, action? : FlagAction, env? : StringView, requires? : ArrayView[String], conflicts_with? : ArrayView[String], required? : Bool, global? : Bool, negatable? : Bool, hidden? : Bool) -> FlagArg

    Create a flag argument.

    Notes:
    • long defaults to name.
    • Use long="" to disable the long form.
    • At least one of short, long, or env must be available.
    • global=true makes the flag available in subcommands.
    • negatable=true accepts --no-<long> for long flags.
    • If env is set on a SetTrue / SetFalse flag, accepted boolean values are: 1, 0, true, false, yes, no, on, off.
    • If env is set on a Count flag, the value must be a non-negative integer.

    Matches

    pub struct Matches {
    flags : Map[String, Bool]
    values : Map[String, Array[String]]
    flag_counts : Map[String, Int]
    sources : Map[String, ValueSource]
    subcommand : (String, Matches)?
    // private fields
    } derive(
    Debug
    )

    Parse results for declarative commands.

    • flags stores final boolean states for flag args.
    • values stores collected option/positional values.
    • flag_counts stores occurrence counts for FlagAction::Count.
    • sources records the final source (Argv / Env / Default) per arg.
    • subcommand stores nested matches for the selected subcommand.

    OptionAction

    pub(all) enum OptionAction {
    Set
    Append
    } derive(Eq,
    Debug
    )

    Behavior for option args.

    • Set accepts the value once; a repeated occurrence is an error.
    • Append keeps all provided values in order.

    OptionAction::equal

    OptionAction::to_string

    fn OptionAction::to_string(self : OptionAction) -> String

    OptionArg

    pub struct OptionArg {
    // private fields
    } derive(
    Debug
    )

    Declarative option constructor wrapper. Named OptionArg to avoid shadowing the built-in Option type.

    OptionArg::OptionArg

    #alias(new, deprecated="Use `OptionArg()` instead")
    fn OptionArg::OptionArg(name : StringView, short? : Char, long? : StringView, about? : StringView, action? : OptionAction, env? : StringView, default_values? : ArrayView[String], allow_hyphen_values? : Bool, requires? : ArrayView[String], conflicts_with? : ArrayView[String], required? : Bool, global? : Bool, hidden? : Bool) -> OptionArg

    Create an option argument.

    Notes:
    • long defaults to name.
    • Use long="" to disable the long form.
    • At least one of short, long, or env must be available.
    • Use action=Append to keep repeated occurrences.
    • global=true makes the option available in subcommands.
    • allow_hyphen_values=true allows values like -1 or --raw to be consumed as this option's value when parsing argv.

    PositionArg

    pub struct PositionArg {
    // private fields
    } derive(
    Debug
    )

    Declarative positional constructor wrapper.

    PositionArg::PositionArg

    #alias(new, deprecated="Use `PositionArg()` instead")
    fn PositionArg::PositionArg(name : StringView, about? : StringView, env? : StringView, default_values? : ArrayView[String], num_args? : ValueRange, allow_hyphen_values? : Bool, requires? : ArrayView[String], conflicts_with? : ArrayView[String], global? : Bool, hidden? : Bool) -> PositionArg

    Create a positional argument.

    Notes:
    • PositionArg order follows declaration order.
    • num_args controls accepted value count.
    • If num_args is omitted, the default is an optional single value (0..=1).
    • Use num_args=ValueRange::single() for a required single positional.
    • allow_hyphen_values=true allows leading-- tokens to be consumed as positional values (unless they match a declared option).
    • Tokens after -- are always treated as positional values.

    ValueRange

    pub struct ValueRange {
    // private fields
    } derive(Eq, Show,
    Debug
    )

    Number-of-values constraint for an argument.

    ValueRange::ValueRange

    #alias(new, deprecated="Use `ValueRange()` instead")
    fn ValueRange::ValueRange(lower? : Int, upper? : Int) -> ValueRange

    Create a value-count range.

    Notes:
    • lower defaults to 0.
    • upper omitted means no upper bound.
    • ValueRange(lower=0) means 0...
    • ValueRange(lower=1, upper=3) means 1..=3.

    ValueRange::equal

    fn ValueRange::equal(ValueRange, ValueRange) -> Bool

    ValueRange::single

    fn ValueRange::single() -> ValueRange

    Exact single-value range (1..1).

    ValueRange::to_string

    fn ValueRange::to_string(self : ValueRange) -> String

    ValueSource

    pub enum ValueSource {
    Argv
    Env
    Default
    } derive(Eq, Show,
    Debug
    )

    Where a value/flag came from.

    ValueSource::equal

    fn ValueSource::equal(ValueSource, ValueSource) -> Bool

    ValueSource::to_string

    fn ValueSource::to_string(self : ValueSource) -> String