Crates.io | rust-args-parser |
lib.rs | rust-args-parser |
version | 0.1.0 |
created_at | 2025-09-23 19:41:54.141523+00 |
updated_at | 2025-09-23 19:41:54.141523+00 |
description | Tiny, fast, callback-based CLI argument parser for Rust |
homepage | https://github.com/milchinskiy/rust-args-parser |
repository | https://github.com/milchinskiy/rust-args-parser |
max_upload_size | |
id | 1852021 |
size | 148,637 |
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 respectsNO_COLOR
.
This crate is a small, pragmatic alternative to heavy frameworks when you want:
fn(Option<&str>, &mut U) -> Result<()>
)CmdSpec
)-abc
, -j10
, -j 10
) and long options --name[=value]
-1
/ -.5
/ 1e3
aren't mistaken for options)at_most_one(group)
, at_least_one(group)
).env("NAME")
, .default("value")
)PosSpec::new("FILE").range(1, 10)
)-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.
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.
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/
.
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.
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.
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
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.
--name
, --name=value
, --name value
-abc
, -j10
, -j 10
(no -j=10
)-1
, -.5
, 1e9
to be consumed as values.--
stops option parsing; remaining tokens are positionals.Env
): -h|--help
, -V|--version
, -A|--author
print to stdout
and return Err(Error::Exit(0))
so you can process::exit(0)
cleanly.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.
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
.
unsafe
(#![forbid(unsafe_code)]
): the parser is implemented entirely in safe Rust.For heavy usage, see
benches/
(Criterion®) and measure on your workload. The library aims to avoid surprises and keep hot paths branch-lean.
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.