rust-args-parser

Crates.iorust-args-parser
lib.rsrust-args-parser
version0.1.0
created_at2025-09-23 19:41:54.141523+00
updated_at2025-09-23 19:41:54.141523+00
descriptionTiny, fast, callback-based CLI argument parser for Rust
homepagehttps://github.com/milchinskiy/rust-args-parser
repositoryhttps://github.com/milchinskiy/rust-args-parser
max_upload_size
id1852021
size148,637
Alexander Milchinskiy (milchinskiy)

documentation

https://docs.rs/rust-args-parser

README

rust_args_parser

Tiny, fast, callback-based CLI argument parser for Rust. No macros. No derive. No global state.

⚙️ Zero unsafe, 📦 std-only. 🧵 Minimal allocations on hot paths. 🎨 Optional colorized help that respects NO_COLOR.

This crate is a small, pragmatic alternative to heavy frameworks when you want:

  • Simple, callback-driven option handling (fn(Option<&str>, &mut U) -> Result<()>)
  • Subcommands with aliases (nested CmdSpec)
  • Short clusters (-abc, -j10, -j 10) and long options --name[=value]
  • Optional/required values with numeric look-ahead (so -1 / -.5 / 1e3 aren't mistaken for options)
  • Exclusive / required groups (at_most_one(group), at_least_one(group))
  • Environment / default application (.env("NAME"), .default("value"))
  • Positionals schema with ranges (PosSpec::new("FILE").range(1, 10))
  • Built-ins: -h/--help, -V/--version, -A/--author when enabled in Env

It's inspired by the author's C library c-args-parser, re-imagined in safe Rust.


Table of contents


Quick start

Minimal skeleton:

use rust_args_parser as ap;

#[derive(Default)]
struct App { verbose: bool, n: i32, limit: Option<String> }

fn main() -> ap::Result<()> {
    let mut app = App::default();
    let env = ap::Env::new("demo")
        .version("0.1.0")
        .author("Your Name <you@example.com>")
        .auto_help(true)
        .auto_color();

    let root = ap::CmdSpec::new(None, None)
        .desc("Demo tool")
        .opts([
            // -v, --verbose (flag)
            ap::OptSpec::new("verbose", |_, u: &mut App| { u.verbose = true; Ok(()) })
                .short('v')
                .help("Enable verbose output")
                .flag(),
            // -n N / --n=N (required value, hinted numeric)
            ap::OptSpec::new("n", |v, u: &mut App| {
                u.n = v.unwrap().parse().map_err(|_| ap::Error::User("bad -n"))?;
                Ok(())
            })
                .short('n').metavar("N").help("Required number").numeric().required(),
            // -l[=N] / --limit[=N] (optional value)
            ap::OptSpec::new("limit", |v, u: &mut App| { u.limit = v.map(Into::into); Ok(()) })
                .short('l').metavar("N").help("Optional limit").optional(),
        ])
        .pos([ ap::PosSpec::new("FILE").range(1, 10).desc("Input files") ]);

    // Collect CLI args (skip program name) as &strs
    let argv: Vec<String> = std::env::args().skip(1).collect();
    let args: Vec<&str> = argv.iter().map(String::as_str).collect();

    // Parse and run; prints auto help/version/author to stdout when triggered
    match ap::dispatch(&env, &root, &args, &mut app) {
        Ok(()) => Ok(())
        , Err(ap::Error::Exit(code)) => std::process::exit(code)
        , Err(err) => { eprintln!("{err}"); std::process::exit(2) }
    }
}

Run it:

$ demo -h
$ demo --n=3 -vv file1 file2
$ demo --limit 10 file

See examples/basic.rs in this repo for a full, runnable version.


Examples

Run any example with:

cargo run --example <name> -- [args...]
Example Teaches Try
01_minimal one flag + one positional cargo run --example 01_minimal -- -v file.txt
02_flags_values clusters, required & optional values cargo run --example 02_flags_values -- -vv -n10 --limit=5 ./path
03_optional_numbers optional numeric look-ahead cargo run --example 03_optional_numbers -- -t -0.25
04_groups XOR / REQ_ONE groups cargo run --example 04_groups -- --mode-a
05_subcommands subcommands & aliases cargo run --example 05_subcommands -- remote add https://example
06_env_defaults_and_help env/defaults & built-ins THREADS=8 cargo run --example 06_env_defaults_and_help -- -o out.txt

The sources live under examples/.


Subcommands

Compose subcommands by nesting CmdSpec:

let remote_add = ap::CmdSpec::new(Some("add"), Some(|pos, _u: &mut App| {
    println!("remote add: {}", pos.get(0).unwrap_or(&""));
    Ok(())
}))
.pos([ap::PosSpec::new("URL").one().desc("Remote URL")]);

let remote = ap::CmdSpec::new(Some("remote"), None)
    .aliases(["r"]) // optional
    .subs([remote_add]);

let root = ap::CmdSpec::new(None, None)
    .subs([remote])
    .pos([]);

dispatch descends through bare tokens until it resolves the final command, then parses options/positionals at that depth.


Groups (mutually-exclusive / required one)

Use group ids to express constraints across options:

let root = ap::CmdSpec::new(None, None).opts([
    ap::OptSpec::new("color", |_,_| Ok(())).help("Force color").at_most_one(1),
    ap::OptSpec::new("no-color", |_,_| Ok(())).help("Disable color").at_most_one(1),
    ap::OptSpec::new("mode-a", |_,_| Ok(())).help("Mode A").at_least_one(2),
    ap::OptSpec::new("mode-b", |_,_| Ok(())).help("Mode B").at_least_one(2),
]);

at_most_one(g) enforces XOR (≤ 1 present in group g), at_least_one(g) enforces REQ_ONE (≥ 1 present in group g). Defaults and env-applied options count toward these rules.


Environment & defaults

Attach environment variables and string defaults to options. Both are applied after parsing and before group/positional checks:

ap::OptSpec::new("threads", |v, _| Ok(()))
    .metavar("N").numeric()
    .env("THREADS")      // uses value from env if present
    .default("4");       // otherwise falls back to this

Positionals

Describe positionals per command with names, descriptions and cardinality:

ap::PosSpec::new("FILE").one();            // exactly one
ap::PosSpec::new("ITEM").range(2, 5);      // 2..=5
ap::PosSpec::new("PATH").desc("Input path");

If no positional schema is declared for a command, any leftover bare token becomes an error.


Behavior details

  • Option forms
    • Long: --name, --name=value, --name value
    • Short clusters: -abc, -j10, -j 10 (no -j=10)
  • Optional values pick up the next token if it looks like a value. Numeric hints allow -1, -.5, 1e9 to be consumed as values.
  • -- stops option parsing; remaining tokens are positionals.
  • Built-ins (when configured in Env): -h|--help, -V|--version, -A|--author print to stdout and return Err(Error::Exit(0)) so you can process::exit(0) cleanly.

Color & wrapping

Help text is rendered with optional ANSI color. Use Env::auto_color() to disable when NO_COLOR is set, or Env::color(false) to force plain output. Set wrap_cols to wrap long descriptions.


Errors

dispatch returns Result<()>. Common handling:

match ap::dispatch(&env, &root, &args, &mut app) {
    Ok(()) => {}
    Err(ap::Error::Exit(code)) => std::process::exit(code),
    Err(e) => { eprintln!("{e}"); std::process::exit(2) }
}

All errors implement Display and std::error::Error.


Performance & safety

  • Zero unsafe (#![forbid(unsafe_code)]): the parser is implemented entirely in safe Rust.
  • No global state; no proc-macros; no derives.
  • Minimal allocations: small count vector for options, optional small buffers for help rendering.
  • Fast paths for ASCII short clusters and long option parsing.

For heavy usage, see benches/ (Criterion®) and measure on your workload. The library aims to avoid surprises and keep hot paths branch-lean.


License

Dual-licensed under MIT or Apache-2.0 at your option.

SPDX-License-Identifier: MIT OR Apache-2.0

If you contribute, you agree to license your contributions under the same terms.

Commit count: 1

cargo fmt