From 7240416b66197ffcefe129b74628b55204da07f6 Mon Sep 17 00:00:00 2001
From: 魏曹先生 <1992414357@qq.com>
Date: Mon, 4 May 2026 01:15:39 +0800
Subject: Add documentation for Picker, help display, and shell completion
---
docs/pages/3-parsing-complex-arguments.md | 365 ++++++++++++++++++++++++++++++
1 file changed, 365 insertions(+)
create mode 100644 docs/pages/3-parsing-complex-arguments.md
(limited to 'docs/pages/3-parsing-complex-arguments.md')
diff --git a/docs/pages/3-parsing-complex-arguments.md b/docs/pages/3-parsing-complex-arguments.md
new file mode 100644
index 0000000..285e17d
--- /dev/null
+++ b/docs/pages/3-parsing-complex-arguments.md
@@ -0,0 +1,365 @@
+
Parsing Complex Args
+
+ Use Mingling Picker to parse complex user input
+
+
+## Intro
+
+ In the prev. example, we built a CLI app with a `"greet"` subcommand that outputs the user's first arg.
+
+ You may have noticed the approach used was almost direct string manipulation—not very semantic, and hard to maintain long-term.
+
+```rust
+let name = args.first().cloned().unwrap_or_else(|| "World".to_string());
+```
+
+ This chapter introduces a new **Mingling** feature: `Picker`. It provides a lightweight parsing solution that meshes well with **Mingling**'s typed routing.
+
+ To enable `Picker`, edit `Cargo.toml` ✏️
+
+```toml
+[dependencies]
+mingling = {
+ version = "...",
+ features = ["parser"]
+}
+```
+
+ Enough talk, let's get coding and rewrite the parsing logic from the prev. section ✏️
+
+```rust
+#[chain]
+fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
+ // Prev. approach:
+ // let args = prev.inner;
+ // let name = args.first().cloned().unwrap_or_else(|| "World".to_string());
+
+ // New approach with Picker
+ let name = prev.pick_or((), "World").unpack();
+
+ ResultGreetSomeone::new(name)
+}
+```
+
+ `Picker` implements `pick`, `pick_or`, and `pick_or_route` for anything `Into>`. These functions let you semantically **pick** args from a string list and convert them into structured data.
+
+ In the code above:
+
+```rust
+prev.pick_or((), "World").unpack();
+```
+
+ Its meaning:
+
+```rust
+ prev.pick_or((), "World").unpack();
+// ~~~~ ~~~~~~~ ~~ ~~~~~~~ ~~~~~~~~
+// | | | | |_ unpack to String
+// | | | |__________ default value is "World"
+// | | |______________ pick the first positional arg (no flag)
+// | |______________________ pick or use default
+// |___________________________ from the prev. input
+```
+
+## Parsing Flag Args
+
+ If your app needs to parse flag args (e.g., `greet --name Alice`), do:
+
+```rust
+prev.pick_or(["--name", "-n"], "World").unpack();
+```
+
+ Its meaning:
+
+```rust
+ prev.pick_or(["--name", "-n"], "World").unpack();
+// ~~~~ ~~~~~~~ ~~~~~~~~~~~~~~~~ ~~~~~~~ ~~~~~~~~
+// | | | | |_ unpack to String
+// | | | |__________ default value is "World"
+// | | |____________________________ pick the value after "--name" or "-n"
+// | |____________________________________ pick or use default
+// |_________________________________________ from the prev. input
+```
+
+## About `.unpack()` 💡
+
+ You may have noticed `Picker` calls `.unpack()` at the end of parsing. It converts the parsed result into structured info.
+
+ For a single pick, `.unpack()` returns a single value. For multiple picks, `Picker` returns a tuple:
+
+```rust
+let name_single: String = prev.clone().pick_or((), "World").unpack();
+let (name, age, id) = prev
+ .pick::(["--name", "-n"])
+ .pick::(["--age", "-a"])
+ .pick::(["--id", "-I"])
+ .unpack();
+
+// Parses: --name Alice --age 21 --id 0711251
+```
+
+> [!IMPORTANT]
+> `Picker` is very order-sensitive, esp. with positional args: it parses sequentially.
+>
+> If you need to parse positional args, make sure to pick & consume all **flag args** first.
+
+## Using `pick_or_route` for Edge Cases
+
+ Ha, as the old saying goes: "Never trust your users." Missing required args, type mismatches, enabling mutually exclusive options—these are all headache-inducing edge cases.
+
+ `pick_or_route` handles these by routing the chain to a dedicated error-handling type, giving you fine-grained error control.
+
+ Let's write a simple example showing basic usage:
+
+```rust
+dispatcher!("greet", GreetCommand => GreetEntry);
+
+pack!(ResultGreetSomeone = String);
+pack!(ErrorGreetNoNameProvided = ());
+
+#[chain]
+fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
+ // Use `pick_or_route` to extract the `--name` arg
+ // If missing or parse fails, route to ErrorGreetNoNameProvided
+ let pick_result = prev
+ .pick_or_route(
+ ["--name", "-n"],
+ ErrorGreetNoNameProvided::default().to_render(),
+ )
+ // After using any routable method, `unpack` returns `Result`
+ .unpack();
+
+ // Use the `route!` macro to expand `pick_result`,
+ // If it's `Err`, the chain returns here, routing to the specified type
+ let name = route!(pick_result);
+ ResultGreetSomeone::new(name).to_chain()
+}
+
+// Handles rendering for `ErrorGreetNoNameProvided`
+#[renderer]
+fn render_err_greet_no_name_provided(_prev: ErrorGreetNoNameProvided) {
+ r_println!("Error: No name provided.")
+}
+
+#[renderer]
+fn render_greet_someone(prev: ResultGreetSomeone) {
+ r_println!("Hello, {}!", *prev);
+}
+```
+
+ Using `pick_or_route` makes the code a bit more complex: `.unpack()` no longer returns the value directly, but `Result`.
+
+ However, **Mingling** provides the `route!` macro to simplify expansion. It's not complex—just cuts some boilerplate:
+
+```rust
+let name = route!(pick_result);
+
+// Expands to
+let name = match pick_result {
+ Ok(r) => r,
+ Err(e) => return e,
+};
+```
+
+## Post-Processing Extracted Values
+
+ After using `pick` to extract user input, you can use `after` or `after_or_route` to process the arg immediately ✏️
+
+```rust
+#[chain]
+fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
+ let name = prev
+ .pick_or(["--name", "-n"], "World")
+ // After extracting `--name`, format it immediately
+ .after(|name: String| {
+ name.replace(['-', '_', '.'], " ")
+ .to_lowercase()
+ .trim()
+ .to_string();
+ name
+ })
+ .unpack();
+
+ ResultGreetSomeone::new(name) // name is now formatted
+}
+```
+
+ Similarly, use `after_or_route` to handle format errors in input args ✏️
+
+```rust
+dispatcher!("greet", GreetCommand => GreetEntry);
+
+pack!(ResultGreetSomeone = String);
+pack!(ErrorGreetNameTooLong = usize);
+
+#[chain]
+fn handle_greet_entry(prev: GreetEntry) -> NextProcess {
+ let pick_result = prev
+ .pick_or(["--name", "-n"], "World")
+ // Unlike `after`, this borrows &String
+ .after_or_route(|name: &String| {
+ name.replace(['-', '_', '.'], " ")
+ .to_lowercase()
+ .trim()
+ .to_string();
+
+ // Check name length, route to error type if too long
+ let len = name.len();
+ if len < 32 {
+ Ok(name.clone())
+ } else {
+ Err(ErrorGreetNameTooLong::new(len).to_render())
+ }
+ })
+ .unpack();
+ let name = route!(pick_result);
+
+ ResultGreetSomeone::new(name).to_chain()
+}
+
+#[renderer]
+fn render_error_greet_name_too_long(prev: ErrorGreetNameTooLong) {
+ let len = *prev;
+ r_println!("Error: name too long (length: {} > 32)", len);
+}
+
+#[renderer]
+fn render_greet_someone(prev: ResultGreetSomeone) {
+ r_println!("Hello, {}!", *prev);
+}
+```
+
+## Parsing Booleans
+
+ `Picker` can parse **bool** types too, but with both explicit and implicit modes:
+
+ |Mode|Format|
+ |-|-|
+ |Explicit|`--confirm true` or `--confirm yes`|
+ |Implicit|`--confirmed`|
+
+ - Using `.pick` on `bool` uses implicit parsing: flag present → `true`
+ - Using `.pick` on `mingling::parser::Yes` or `mingling::parser::True` uses explicit parsing; the value must be `true` / `yes` to be recognized as `true`
+
+ Generally, implicit parsing is enough, but for positional args or important confirmations, explicit logic might be more semantic.
+
+```rust
+#[chain]
+fn handle_some_entry(prev: SomeEntry) -> NextProcess {
+ let confirmed: bool = prev.pick::(()).unpack().is_yes();
+ let confirm: bool = prev.pick::(["--confirm", "-C"]).unpack();
+
+ // other logic
+}
+```
+
+## Special Use: `usize` Parsing
+
+ **Mingling** has a special use for `usize`: parsing strings like `25G`, `32mb`, etc. ✏️
+
+```rust
+#[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 Parsable Types
+
+ Use the `Pickable` trait to make your types parsable by `Picker`. This is where `Picker`'s extensibility comes from ✏️
+
+```rust
+// Must implement Default: parse failures record the default directly
+#[derive(Default)]
+pub struct Address {
+ ip: String,
+ port: u16,
+}
+
+impl Pickable for Address {
+ type Output = Self;
+ fn pick(args: &mut Argument, flag: Flag) -> Option {
+ // Extract raw string from Argument using Flag
+ let raw = args.pick_argument(flag)?;
+
+ // Parse raw string into structured data
+ 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 })
+ }
+}
+```
+
+ With `Pickable` implemented for `Address`, we can now use `ip:port` format for input ✏️
+
+```rust
+dispatcher!("connect", ConnectCommand => ConnectEntry);
+
+pack!(ResultConnected = Address);
+
+#[chain]
+fn handle_connect_entry(prev: ConnectEntry) -> NextProcess {
+ 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);
+}
+```
+
+ Running it:
+
+```bash
+~> your-bin connect --addr 127.0.0.1:8080
+Connected: IP: 127.0.0.1 PORT: 8080
+```
+
+## Auto-Implementing Pickable for Enums
+
+ No need to manually implement `Pickable` for enums: `Picker` auto-implements it for any type that implements `PickableEnum`, as long as it also implements `EnumTag` ✏️
+
+```rust
+// Debug : for rendering
+// Default: for Picker parsing
+// EnumTag: for implementing PickableEnum
+#[derive(Debug, Default, EnumTag)]
+pub enum Fruits {
+ #[default]
+ Apple,
+ Banana,
+ Orange,
+}
+
+// Implement PickableEnum for Fruits
+impl PickableEnum for Fruits {}
+```
+
+ Now you can directly use `Picker` to parse this type ✏️
+
+```rust
+pack!(ResultFruit = Fruits);
+
+#[chain]
+fn handle_eat_fruit_entry(prev: EatFruitEntry) -> NextProcess {
+ let fruit: Fruits = prev.pick("--fruit").unpack();
+ ResultFruit::new(fruit)
+}
+
+#[renderer]
+fn render_ate_fruit(prev: ResultFruit) {
+ r_println!("Picked fruit: {:?}", *prev);
+}
+```
+
+ That's all for `Picker`'s usage. In the next chapter, I'll introduce how to implement help docs for commands in **Mingling**.
+
+
+ Written by @Weicao-CatilGrass
+
--
cgit