# 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](https://github.com/milchinskiy/c-args-parser)**, re-imagined in safe Rust.
---
## Table of contents
- [Quick start](#quick-start)
- [Examples](#examples)
- [Subcommands](#subcommands)
- [Groups (mutually-exclusive / required one)](#groups-mutually-exclusive--required-one)
- [Environment & defaults](#environment--defaults)
- [Positionals](#positionals)
- [Behavior details](#behavior-details)
- [Color & wrapping](#color--wrapping)
- [Errors](#errors)
- [Performance & safety](#performance--safety)
- [License](#license)
---
## Quick start
Minimal skeleton:
```rust
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:
```bash
cargo run --example <name> -- [args...]
```
| 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`:
```rust
.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:
```rust
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:
```rust
---
## 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:
```rust
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.