Parsing Arguments with Picker

Use Picker to perform basic argument parsing

In previous tutorials, we manually extracted parameters from `EntryGreet.0` (`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 when there are many params. Mingling provides `Picker` — a chained API for extracting and transforming params. To enable `Picker`, update your `Cargo.toml`: ```toml # Cargo.toml [dependencies.mingling] features = ["picker"] ``` Now let's see how `Picker` is written: ```rust // Features: ["picker"] @@@dispatcher!("greet", EntryGreet); @@@#[derive(Grouped, Wrap)] @@@pub struct ResultName(String); #[chain] fn handle_greet_entry(prev: EntryGreet) -> Next { let name = prev .pick_or(&arg![String], || "World".to_string()) .unwrap(); ResultName(name).into() } ``` `EntryPicker` implements `pick`, `pick_or`, `pick_or_default`, and `pick_or_route` for all entry types. These functions semantically **pick** params from the string list and convert them into structured data, using the `arg!` macro to declare what to pick. For the code above: ```rust // Features: ["picker"] @@@dispatcher!("greet", EntryGreet); @@@#[derive(Grouped, Wrap)] @@@pub struct ResultName(String); @@@#[chain] @@@fn handle_greet_entry(prev: EntryGreet) -> Next { let name = prev .pick_or(&arg![String], || "World".to_string()) .unwrap(); @@@ResultName(name).into() @@@} ``` Its semantics are: ```rust // Features: ["picker"] @@@dispatcher!("greet", EntryGreet); @@@#[derive(Grouped, Wrap)] @@@pub struct ResultName(String); @@@#[chain] @@@fn handle_greet_entry(prev: EntryGreet) { @@@let name: String = prev.pick_or(&arg![String], || "World".to_string()).unwrap(); // ~~~~ ~~~~~~~ ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~ // | | | | |_ unwrap to String // | | | |__________________________ default value "World" // | | |________________________________________ pick the first positional arg (declared as `String`) // | |________________________________________________ pick or use default // |_____________________________________________________ from the previous input @@@} ``` ## Parsing Flag Arguments If your program needs to parse flag arguments (e.g. `greet --name Alice`), declare a named flag in `arg!`: ```rust // Features: ["picker"] @@@dispatcher!("greet", EntryGreet); @@@#[derive(Grouped, Wrap)] @@@pub struct ResultName(String); #[chain] fn handle_greet_entry(prev: EntryGreet) -> Next { let name = prev .pick_or(&arg![name: String, 'n'], || "World".to_string()) .unwrap(); ResultName(name).into() } ``` The `arg!` macro derives the long flag name (`--name`) from the field name, and `'n'` adds the short alias (`-n`). Its semantics: ```rust // Features: ["picker"] @@@dispatcher!("greet", EntryGreet); @@@#[derive(Grouped, Wrap)] @@@pub struct ResultName(String); @@@#[chain] @@@fn handle_greet_entry(prev: EntryGreet) { @@@let name: String = prev.pick_or(&arg![name: String, 'n'], || "World".to_string()).unwrap(); // ~~~~ ~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~ // | | | | |_ unwrap to String // | | | |________________________ default value "World" // | | |___________________________________________________ pick the value after "--name" or "-n" // | |___________________________________________________________ pick or use default // |________________________________________________________________ from the previous input @@@} ``` ## About `.unwrap()` and `route!` You may have noticed that `Picker` calls `.unwrap()` (or `route!`) at the end of parsing. It converts the collected results into structured info. For a single pick, `.unwrap()` returns the value directly; for multiple picks, it returns a tuple: ```rust // Features: ["picker"] @@@dispatcher!("test", EntryTest); @@@#[derive(Grouped, Wrap)] @@@pub struct ResultInfo((String, u8, u32)); #[chain] fn handle_test_entry(prev: EntryTest) -> Next { let (name, age, id) = prev .pick_or_default(&arg![name: String, 'n']) .pick_or_default(&arg![age: u8, 'a']) .pick_or_default(&arg![id: u32, 'I']) .unwrap(); ResultInfo((name, age, id)).into() } ``` > [!IMPORTANT] > `Picker` is sensitive to parse order, especially for positional args — it parses sequentially. If you need to parse positional args, make sure all **flag arguments** are picked and consumed first. ## Handling Edge Cases with `pick_or_route` As the saying goes: "never trust your users." To handle missing required params, type mismatches, etc., `pick_or_route` routes the chain to a dedicated error handler. Here's a simple example: ```rust // Features: ["picker", "extras"] @@@use mingling::macros::buffer; @@@use mingling::macros::route; @@@dispatcher!("greet", EntryGreet); @@@#[derive(Grouped, Wrap)] @@@pub struct ResultName(String); @@@#[derive(Grouped, Wrap, Default)] @@@pub struct ErrorNoName(()); #[chain] fn handle_greet_entry(prev: EntryGreet) -> Next { // Use route! macro to expand the Result let name = route!( prev.pick_or_route(&arg![name: String, 'n'], || { ErrorNoName::default().to_chain() }) .to_result() ); ResultName(name).into() } #[renderer(buffer)] fn render_greet(result: ResultName) { r_println!("Hello, {}!", *result); } ``` With `pick_or_route`, `.to_result()` no longer returns the value directly, but `Result`. However, **Mingling**'s `extras` feature provides the `route!` macro for simplified expansion. It's not complex — it just reduces boilerplate: ```rust // Features: ["picker", "extras"] @@@ #[derive(Grouped, Wrap)] @@@ pub struct ErrorFail(()); @@@ use mingling::macros::route; @@@ use mingling::picker::IntoPicker; @@@ fn func() -> mingling::ChainProcess { @@@ let args: Vec = vec![]; let name = route!(args.pick_or_route(&arg![String], || ErrorFail(()).to_chain()).to_result()); @@@ mingling::macros::empty_result!() @@@ } ``` It expands to: ```rust // Features: ["picker", "extras"] @@@ #[derive(Grouped, Wrap)] @@@ pub struct ErrorFail(()); @@@ use mingling::picker::IntoPicker; @@@ fn func() -> mingling::ChainProcess { @@@ let args: Vec = vec![]; let name = match args.pick_or_route(&arg![String], || ErrorFail(()).to_chain()).to_result() { Ok(r) => r, Err(e) => return e, }; @@@ mingling::macros::empty_result!() @@@ } ``` ## Post-processing Extracted Values After picking user input with `pick`, you can use `post` to process it immediately: ```rust // Features: ["picker"] @@@dispatcher!("greet", EntryGreet); @@@#[derive(Grouped, Wrap)] @@@pub struct ResultName(String); #[chain] fn handle_greet_entry(prev: EntryGreet) -> Next { let name = prev .pick_or(&arg![name: String, 'n'], || "World".to_string()) // Format immediately after picking --name .post(|name: String| { name.replace(['-', '_', '.'], " ") .to_lowercase() .trim() .to_string() }) .unwrap(); ResultName(name).into() } ``` ## Boolean Parsing `Picker` parses booleans as **flags**: the flag being present means `true`. ```rust // Features: ["picker"] @@@use mingling::picker::value::Flag; @@@dispatcher!("test", EntryTest); @@@#[derive(Grouped, Wrap, Default)] @@@pub struct ResultDone(()); #[chain] fn handle_entry(prev: EntryTest) -> Next { // `--confirm` / `-C` present → true let _confirm: bool = *prev.pick(&arg![confirm: Flag, 'C']).unwrap(); ResultDone::default().to_render() } ``` > [!NOTE] > For important confirmations, pair the flag with an explicit value check if the exact boolean semantics matter. ## Custom Pickable Types You can make your types pickable by `Picker` using the `SinglePickable` trait — this is where `Picker`'s extensibility comes from. ```rust // Features: ["picker"] @@@use mingling::macros::buffer; @@@use mingling::picker::{PickerArgResult, SinglePickable}; @@@use mingling::Flag; #[derive(Default, Clone)] pub struct Address { ip: String, port: u16, } impl SinglePickable for Address { fn pick_single(str: Option<&str>) -> PickerArgResult { let Some(raw) = str else { return PickerArgResult::NotFound; }; let parts: Vec<&str> = raw.split(':').collect(); let ip = parts.first().copied().unwrap_or_default().to_string(); let port: u16 = match parts.get(1).and_then(|p| p.parse().ok()) { Some(p) => p, None => return PickerArgResult::NotFound, }; PickerArgResult::Parsed(Address { ip, port }) } } @@@dispatcher!("connect", EntryConnect); @@@#[derive(Grouped, Wrap)] @@@pub struct ResultConnected(Address); #[chain] fn handle_connect_entry(prev: EntryConnect) -> Next { let address: Address = prev.pick_or_default(&arg![Address]).unwrap(); ResultConnected(address).into() } #[renderer(buffer)] fn render_connected(addr: ResultConnected) { 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 ``` ## Implementing Pickable for Enums To make an enum pickable, implement `SinglePickable` manually with a match on the input: ```rust // Features: ["picker"] @@@use mingling::macros::buffer; @@@use mingling::picker::{PickerArgResult, SinglePickable}; @@@use mingling::EnumTag; #[derive(Debug, Default, EnumTag)] pub enum Fruits { #[default] Apple, Banana, Orange, } impl SinglePickable for Fruits { fn pick_single(str: Option<&str>) -> PickerArgResult { let Some(str) = str else { return PickerArgResult::NotFound; }; let fruit = match str.to_lowercase().as_str() { "apple" => Self::Apple, "banana" => Self::Banana, "orange" => Self::Orange, _ => return PickerArgResult::NotFound, }; PickerArgResult::Parsed(fruit) } } @@@dispatcher!("eat", EntryEat); @@@#[derive(Grouped, Wrap)] @@@pub struct ResultFruit(Fruits); #[chain] fn handle_eat_entry(prev: EntryEat) -> Next { let fruit: Fruits = prev.pick_or_default(&arg![Fruits]).unwrap(); ResultFruit(fruit).into() } #[renderer(buffer)] fn render_fruit(prev: ResultFruit) { r_println!("Picked fruit: {:?}", *prev); } ``` That covers all the usages of `Picker`.

Written by @Weicao-CatilGrass