Parsing Arguments with Picker
Use Picker to perform basic argument parsing
In previous tutorials, we extracted args manually from `EntryGreet.inner` (`Vec`).
```rust
@@@ fn main() {
@@@ let args : Vec = vec![];
let name = args.first().cloned().unwrap_or_else(|| "World".to_string());
@@@ }
```
But this approach doesn't scale well for many params. Mingling provides `Picker` — a chaining API to extract and convert args.
To enable `Picker`, update your `Cargo.toml`:
```toml
# Cargo.toml
[dependencies.mingling]
features = ["parser"]
```
Now let's look at `Picker` in action:
```rust
// Features: ["parser"]
@@@dispatcher!("greet", CMDGreet => EntryGreet);
@@@pack!(ResultName = String);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev.pick_or((), "World").unpack();
ResultName::new(name)
}
```
`AsPicker` implements `pick`, `pick_or`, and `pick_or_route` for any type that can convert to `Vec`: they semantically **pick** args from a string list and convert them to structured data.
Breaking down the example above:
```rust
// Features: ["parser"]
@@@dispatcher!("greet", CMDGreet => EntryGreet);
@@@pack!(ResultName = String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev.pick_or((), "World").unpack();
@@@ResultName::new(name)
@@@}
```
Its semantics are:
```rust
// Features: ["parser"]
@@@dispatcher!("greet", CMDGreet => EntryGreet);
@@@pack!(ResultName = String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) {
@@@let name: String =
prev.pick_or((), "World").unpack();
// ~~~~ ~~~~~~~ ~~ ~~~~~~~ ~~~~~~~~
// | | | | |_ unpack to String
// | | | |__________ default value "World"
// | | |______________ pick the first positional arg (no flag)
// | |______________________ pick or use default
// |___________________________ from previous input
@@@}
```
## Parsing Flag Args
If your program needs to parse flag args (e.g., `greet --name Alice`), do this:
```rust
// Features: ["parser"]
@@@dispatcher!("greet", CMDGreet => EntryGreet);
@@@pack!(ResultName = String);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev.pick_or(["--name", "-n"], "World").unpack();
ResultName::new(name)
}
```
Its semantics:
```rust
// Features: ["parser"]
@@@dispatcher!("greet", CMDGreet => EntryGreet);
@@@pack!(ResultName = String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) {
@@@let name: String =
prev.pick_or(["--name", "-n"], "World").unpack();
// ~~~~ ~~~~~~~ ~~~~~~~~~~~~~~~~ ~~~~~~~ ~~~~~~~~
// | | | | |_ unpack to String
// | | | |__________ default value "World"
// | | |____________________________ pick arg after "--name" or "-n"
// | |____________________________________ pick or use default
// |_________________________________________ from previous input
@@@}
```
## About `.unpack()`
You may have noticed that `Picker` calls `.unpack()` at the end of parsing. It converts the accumulated parse results into structured info.
For a single pick, `.unpack()` returns a single value; for multiple picks, it returns a tuple:
```rust
// Features: ["parser"]
@@@dispatcher!("test", CMDTest => EntryTest);
@@@pack!(ResultInfo = (String, u8, u32));
#[chain]
fn handle_test_entry(prev: EntryTest) -> Next {
let (name, age, id) = prev
.pick::(["--name", "-n"])
.pick::(["--age", "-a"])
.pick::(["--id", "-I"])
.unpack();
ResultInfo::new((name, age, id))
}
```
> [!IMPORTANT]
> `Picker` is very sensitive to parse order, esp. for positional args (they're parsed sequentially). If you need to parse positional args, make sure all **flag args** have been picked and consumed first.
## Handling Edge Cases with `pick_or_route`
As the old saying goes: "Never trust your users." To handle missing required args, type mismatches, etc., `pick_or_route` routes the execution chain to a dedicated error-handling type.
A simple example:
```rust
// Features: ["parser", "extra_macros"]
@@@use mingling::macros::route;
@@@dispatcher!("greet", CMDGreet => EntryGreet);
@@@pack!(ResultName = String);
@@@pack!(ErrorNoName = ());
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
let pick_result = prev
.pick_or_route(["--name", "-n"], ErrorNoName::default())
.unpack();
// Use route! macro to unpack pick_result
let name = route!(pick_result);
ResultName::new(name).into()
}
#[renderer]
fn render_no_name(_prev: ErrorNoName) {
r_println!("Error: No name provided.");
}
#[renderer]
fn render_name(prev: ResultName) {
r_println!("Hello, {}!", *prev);
}
```
With `pick_or_route`, the code gets a bit more complex: `.unpack()` no longer returns the value directly, but `Result`.
However, Mingling's `extra_macros` feature provides the `route!` macro to simplify unwrapping — it just omits some boilerplate:
```rust
// Features: ["parser", "extra_macros"]
@@@ pack!(ErrorFail = ());
@@@ use mingling::macros::route;
@@@ fn func() -> mingling::ChainProcess {
@@@ let args: Vec = vec![];
@@@ let pick_result = args.pick_or_route::((), ErrorFail::new(())).unpack();
let name = route!(pick_result);
@@@ mingling::macros::empty_result!()
@@@ }
```
It expands to:
```rust
// Features: ["parser", "extra_macros"]
@@@ pack!(ErrorFail = ());
@@@ fn func() -> mingling::ChainProcess {
@@@ let args: Vec = vec![];
@@@ let pick_result = args.pick_or_route::((), ErrorFail::new(())).unpack();
let name = match pick_result {
Ok(r) => r,
Err(e) => return e.to_chain(),
};
@@@ mingling::macros::empty_result!()
@@@ }
```
## Post-processing Extracted Values
After picking user input, you can use `after` to process the value immediately:
```rust
// Features: ["parser"]
@@@dispatcher!("greet", CMDGreet => EntryGreet);
@@@pack!(ResultName = String);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev
.pick_or(["--name", "-n"], "World")
// Format immediately after extracting --name
.after(|name: String| {
name.replace(['-', '_', '.'], " ")
.to_lowercase()
.trim()
.to_string()
})
.unpack();
ResultName::new(name)
}
```
Similarly, use `after_or_route` to handle format errors in input args:
```rust
// Features: ["parser", "extra_macros"]
@@@use mingling::macros::route;
@@@dispatcher!("greet", CMDGreet => EntryGreet);
@@@pack!(ResultName = String);
@@@pack!(ErrorNameTooLong = usize);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
let pick_result = prev
.pick_or(["--name", "-n"], "World")
.after_or_route(|name: &String| {
if name.len() < 32 {
Ok(name.clone())
} else {
Err(ErrorNameTooLong::new(name.len()))
}
})
.unpack();
let name = route!(pick_result);
ResultName::new(name).into()
}
#[renderer]
fn render_name_too_long(prev: ErrorNameTooLong) {
let len = *prev;
r_println!("Error: name too long (length: {} > 32)", len);
}
#[renderer]
fn render_name(prev: ResultName) {
r_println!("Hello, {}!", *prev);
}
```
## Boolean Parsing
`Picker` can parse bools too, with two modes: implicit and explicit.
| Mode | Format |
| -------- | ----------------------------------- |
| Implicit | `--confirmed` |
| Explicit | `--confirm true` or `--confirm yes` |
- Using `.pick::(flag)` → implicit: flag present means `true`
- Using `.pick::(flag)` or `.pick::(flag)` → explicit
Implicit is fine for most cases, but for important confirmations, explicit logic is more semantic.
```rust
// Features: ["parser"]
@@@use mingling::parser::Yes;
@@@dispatcher!("test", CMDTest => EntryTest);
@@@pack!(ResultDone = ());
#[chain]
fn handle_entry(prev: EntryTest) -> Next {
@@@ let prev1 = prev.clone();
let _confirmed: bool = prev.pick::(()).unpack().is_yes();
@@@ let prev = prev1;
let _confirm: bool = prev.pick::(["--confirm", "-C"]).unpack();
ResultDone::default().to_render()
}
```
## Special Usage: `usize` Parsing
Mingling provides a special `usize` feature: parsing strings like `25G`, `32mib`, etc.
```rust
// Features: ["parser"]
#[test]
fn parse_size() {
let vec = vec!["--size".to_string(), "25mib".to_string()];
let size: usize = vec.pick(["--size", "-S"]).unpack();
assert_eq!(size, 25 * 1024 * 1024);
}
```
## Custom Parseable Types
Implement the `Pickable` trait to make your type parseable by `Picker` — this is where Picker's extensibility comes from.
```rust
// Features: ["parser"]
@@@use mingling::parser::{Pickable, Argument};
@@@use mingling::Flag;
#[derive(Default)]
pub struct Address {
ip: String,
port: u16,
}
impl Pickable for Address {
type Output = Self;
fn pick(args: &mut Argument, flag: Flag) -> Option {
let raw = args.pick_argument(flag)?;
let parts: Vec<&str> = raw.split(':').collect();
let ip = parts.first()?.to_string();
let port: u16 = parts.get(1)?.parse().ok()?;
Some(Address { ip, port })
}
}
@@@dispatcher!("connect", CMDConnect => EntryConnect);
@@@pack!(ResultConnected = Address);
#[chain]
fn handle_connect_entry(prev: EntryConnect) -> Next {
let address: Address = prev.pick("--addr").unpack();
ResultConnected::new(address)
}
#[renderer]
fn render_connected(prev: ResultConnected) {
let addr = prev.inner;
r_println!("Connected: IP: {} PORT: {}", addr.ip, addr.port);
}
```
Output:
```text
~# my-cli connect --addr 127.0.0.1:8080
Connected: IP: 127.0.0.1 PORT: 8080
```
## Auto-implementing Pickable for Enums
To implement `Pickable` for an enum, just make it implement `EnumTag`, then implement `PickableEnum`:
```rust
// Features: ["parser"]
@@@use mingling::parser::PickableEnum;
@@@use mingling::EnumTag;
#[derive(Debug, Default, EnumTag)]
pub enum Fruits {
#[default]
Apple,
Banana,
Orange,
}
impl PickableEnum for Fruits {}
@@@dispatcher!("eat", CMDEat => EntryEat);
@@@pack!(ResultFruit = Fruits);
#[chain]
fn handle_eat_entry(prev: EntryEat) -> Next {
let fruit: Fruits = prev.pick("--fruit").unpack();
ResultFruit::new(fruit)
}
#[renderer]
fn render_fruit(prev: ResultFruit) {
r_println!("Picked fruit: {:?}", *prev);
}
```
That covers all the features of `Picker`.
Written by @Weicao-CatilGrass