diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-08-17 04:05:41 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-08-17 04:05:41 +0800 |
| commit | aa251efb87b561f62266628f06ad341253fbbdc5 (patch) | |
| tree | 7d2cc2f34f3e4a7282d8d6f0ec0f5ae4bfc87002 /docs | |
| parent | c23c590330af83afb6e146bcd9b0a274b3689d22 (diff) | |
refactor!: remove legacy parser feature and migrate to picker
The legacy `parser` feature and its module tree (`mingling::parser`,
`Argument`, `Picker`, `Pickable`, etc.) have been fully removed and
replaced by the `picker` feature powered by `arg-picker`.
BREAKING CHANGE: Remove `parser` feature and use `picker` instead.
Migration guide: Replace `features = ["parser"]` with
`features = ["picker"]` and update API usage per the provided table.
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/_zh_CN/pages/3-define-a-chain.md | 2 | ||||
| -rw-r--r-- | docs/_zh_CN/pages/6-argument-parse-picker.md | 246 | ||||
| -rw-r--r-- | docs/_zh_CN/pages/other/features.md | 12 | ||||
| -rw-r--r-- | docs/dev/pages/abouts/code-verify-system.md | 4 | ||||
| -rw-r--r-- | docs/example-pages/examples.json | 30 | ||||
| -rw-r--r-- | docs/pages/1-getting-started.md | 2 | ||||
| -rw-r--r-- | docs/pages/3-define-a-chain.md | 2 | ||||
| -rw-r--r-- | docs/pages/6-argument-parse-picker.md | 243 | ||||
| -rw-r--r-- | docs/pages/other/features.md | 14 |
9 files changed, 213 insertions, 342 deletions
diff --git a/docs/_zh_CN/pages/3-define-a-chain.md b/docs/_zh_CN/pages/3-define-a-chain.md index bf30b91..c8d8a24 100644 --- a/docs/_zh_CN/pages/3-define-a-chain.md +++ b/docs/_zh_CN/pages/3-define-a-chain.md @@ -92,7 +92,7 @@ fn handle_greet(args: EntryGreet) -> Next { } ``` -如果你启用了 `parser` 特性,还可以用 `Picker` 做更灵活的参数提取,不过那是后话了。 +如果你启用了 `picker` 特性,还可以用 `Picker` 做更灵活的参数提取,不过那是后话了。 ## 组合起来 diff --git a/docs/_zh_CN/pages/6-argument-parse-picker.md b/docs/_zh_CN/pages/6-argument-parse-picker.md index 33f1f0d..7944d0a 100644 --- a/docs/_zh_CN/pages/6-argument-parse-picker.md +++ b/docs/_zh_CN/pages/6-argument-parse-picker.md @@ -19,34 +19,38 @@ let name = args.first().cloned().unwrap_or_else(|| "World".to_string()); ```toml # Cargo.toml [dependencies.mingling] -features = ["parser"] +features = ["picker"] ``` 好了,让我们看看 `Picker` 的写法: ```rust -// Features: ["parser"] +// Features: ["picker"] @@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); #[chain] fn handle_greet_entry(prev: EntryGreet) -> Next { - let name = prev.pick_or((), "World").unpack(); + let name = prev + .pick_or(&arg![String], || "World".to_string()) + .unwrap(); ResultName::new(name).into() } ``` -`AsPicker` 为所有可以转换为 `Vec<String>` 的类型实现了 `pick`、`pick_or`、`pick_or_route` 函数:它们可以语义化地从字符串列表中 **拾取 (Pick)** 参数,并转换为结构化数据。 +`EntryPicker` 为所有入口类型实现了 `pick`、`pick_or`、`pick_or_default` 和 `pick_or_route` 函数:它们可以通过 `arg!` 宏声明要拾取的内容,语义化地从字符串列表中 **拾取 (Pick)** 参数,并转换为结构化数据。 对于上述示例中的代码: ```rust -// Features: ["parser"] +// Features: ["picker"] @@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); @@@#[chain] @@@fn handle_greet_entry(prev: EntryGreet) -> Next { -let name = prev.pick_or((), "World").unpack(); +let name = prev + .pick_or(&arg![String], || "World".to_string()) + .unwrap(); @@@ResultName::new(name).into() @@@} ``` @@ -54,75 +58,79 @@ let name = prev.pick_or((), "World").unpack(); 它的语义为: ```rust -// Features: ["parser"] +// Features: ["picker"] @@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); @@@#[chain] @@@fn handle_greet_entry(prev: EntryGreet) { @@@let name: String = - prev.pick_or((), "World").unpack(); -// ~~~~ ~~~~~~~ ~~ ~~~~~~~ ~~~~~~~~ -// | | | | |_ 解包为 String -// | | | |__________ 默认值为 "World" -// | | |______________ 取出第一个位置参数(不指定标志) -// | |______________________ 拾取或使用默认 -// |___________________________ 从前一个输入中 + prev.pick_or(&arg![String], || "World".to_string()).unwrap(); +// ~~~~ ~~~~~~~ ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~ +// | | | | |_ 解包为 String +// | | | |__________________________ 默认值为 "World" +// | | |________________________________________ 取出第一个位置参数(声明为 String) +// | |________________________________________________ 拾取或使用默认 +// |_____________________________________________________ 从前一个输入中 @@@} ``` ## 解析标志参数 -若你的程序需要解析标志参数(例如 `greet --name Alice`),可以使用如下方式 +若你的程序需要解析标志参数(例如 `greet --name Alice`),可以在 `arg!` 中声明一个具名标志: ```rust -// Features: ["parser"] +// Features: ["picker"] @@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); #[chain] fn handle_greet_entry(prev: EntryGreet) -> Next { - let name = prev.pick_or(["--name", "-n"], "World").unpack(); + let name = prev + .pick_or(&arg![name: String, 'n'], || "World".to_string()) + .unwrap(); ResultName::new(name).into() } ``` +`arg!` 宏会从字段名推导长标志名(`--name`),`'n'` 则添加短别名(`-n`)。 + 同理,它的语义为: ```rust -// Features: ["parser"] +// Features: ["picker"] @@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); @@@#[chain] @@@fn handle_greet_entry(prev: EntryGreet) { @@@let name: String = - prev.pick_or(["--name", "-n"], "World").unpack(); -// ~~~~ ~~~~~~~ ~~~~~~~~~~~~~~~~ ~~~~~~~ ~~~~~~~~ -// | | | | |_ 解包为 String -// | | | |__________ 默认值为 "World" -// | | |____________________________ 取出 "--name" 或 "-n" 后面的参数 -// | |____________________________________ 拾取或使用默认 -// |_________________________________________ 从前一个输入中 + prev.pick_or(&arg![name: String, 'n'], || "World".to_string()).unwrap(); +// ~~~~ ~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~ ~~~~~~ +// | | | | |_ 解包为 String +// | | | |________________________ 默认值为 "World" +// | | |___________________________________________________ 取出 "--name" 或 "-n" 后面的参数 +// | |___________________________________________________________ 拾取或使用默认 +// |________________________________________________________________ 从前一个输入中 @@@} ``` -## 关于 `.unpack()` +## 关于 `.unwrap()` 与 `route!` -你可能注意到了,`Picker` 在命令解析的最后,会执行一个 `.unpack()` 函数,它的作用是将前面解析出来的结果,转换为结构化信息。 +你可能注意到了,`Picker` 在命令解析的最后,会执行一个 `.unwrap()`(或 `route!`)函数,它的作用是将前面解析出来的结果,转换为结构化信息。 -对于只拾取了一次的数据来说,`.unpack()` 会返回单个数据,而对于多次拾取,`Picker` 则会返回元组: +对于只拾取了一次的数据来说,`.unwrap()` 会返回单个数据,而对于多次拾取,`Picker` 则会返回元组: ```rust -// Features: ["parser"] +// Features: ["picker"] @@@dispatcher!("test", EntryTest); @@@pack!(ResultInfo = (String, u8, u32)); #[chain] fn handle_test_entry(prev: EntryTest) -> Next { let (name, age, id) = prev - .pick::<String>(["--name", "-n"]) - .pick::<u8>(["--age", "-a"]) - .pick::<u32>(["--id", "-I"]) - .unpack(); + .pick_or_default(&arg![name: String, 'n']) + .pick_or_default(&arg![age: u8, 'a']) + .pick_or_default(&arg![id: u32, 'I']) + .unwrap(); ResultInfo::new((name, age, id)).into() } @@ -138,7 +146,7 @@ fn handle_test_entry(prev: EntryTest) -> Next { 先来看一个简单示例 ```rust -// Features: ["parser", "extras"] +// Features: ["picker", "extras"] @@@use mingling::macros::buffer; @@@use mingling::macros::route; @@@dispatcher!("greet", EntryGreet); @@ -147,12 +155,13 @@ fn handle_test_entry(prev: EntryTest) -> Next { #[chain] fn handle_greet_entry(prev: EntryGreet) -> Next { - let pick_result = prev - .pick_or_route(["--name", "-n"], ErrorNoName::default()) - .unpack(); - - // 使用 route! 宏展开 pick_result - let name = route!(pick_result); + // 使用 route! 宏展开 Result<Value, Route> + let name = route!( + prev.pick_or_route(&arg![name: String, 'n'], || { + ErrorNoName::default().to_chain() + }) + .to_result() + ); ResultName::new(name).into() } @@ -162,18 +171,18 @@ fn render_greet(result: ResultName) { } ``` -若使用 `pick_or_route`,写法会变得相对复杂:因为 `.unpack()` 不再直接返回参数,而是 `Result<Value, Route>`。 +若使用 `pick_or_route`,`.to_result()` 不再直接返回参数,而是 `Result<Value, Route>`。 不过 **Mingling** 的 `extras` 特性提供了简化展开的宏 `route!`,它不复杂,只是省略了一部分样板代码: ```rust -// Features: ["parser", "extras"] +// Features: ["picker", "extras"] @@@ pack!(ErrorFail = ()); @@@ use mingling::macros::route; +@@@ use mingling::picker::IntoPicker; @@@ fn func() -> mingling::ChainProcess<ThisProgram> { @@@ let args: Vec<String> = vec![]; -@@@ let pick_result = args.pick_or_route::<String, _>((), ErrorFail::new(())).unpack(); -let name = route!(pick_result); +let name = route!(args.pick_or_route(&arg![String], || ErrorFail::new(()).to_chain()).to_result()); @@@ mingling::macros::empty_result!() @@@ } ``` @@ -181,14 +190,14 @@ let name = route!(pick_result); 它展开为: ```rust -// Features: ["parser", "extras"] +// Features: ["picker", "extras"] @@@ pack!(ErrorFail = ()); +@@@ use mingling::picker::IntoPicker; @@@ fn func() -> mingling::ChainProcess<ThisProgram> { @@@ let args: Vec<String> = vec![]; -@@@ let pick_result = args.pick_or_route::<String, _>((), ErrorFail::new(())).unpack(); -let name = match pick_result { +let name = match args.pick_or_route(&arg![String], || ErrorFail::new(()).to_chain()).to_result() { Ok(r) => r, - Err(e) => return e.to_chain(), + Err(e) => return e, }; @@@ mingling::macros::empty_result!() @@@ } @@ -196,122 +205,59 @@ let name = match pick_result { ## 提取值的后处理 -在您使用 `pick` 提取了用户输入后,可以使用 `after` 立刻处理该参数 +在您使用 `pick` 提取了用户输入后,可以使用 `post` 立刻处理该参数 -````rust -// Features: ["parser"] +```rust +// Features: ["picker"] @@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); #[chain] fn handle_greet_entry(prev: EntryGreet) -> Next { let name = prev - .pick_or(["--name", "-n"], "World") + .pick_or(&arg![name: String, 'n'], || "World".to_string()) // 在提取出 --name 后,立刻格式化 - .after(|name: String| { + .post(|name: String| { name.replace(['-', '_', '.'], " ") .to_lowercase() .trim() .to_string() }) - .unpack(); - - ResultName::new(name).into() -} -``` - -同样,你可以使用 `after_or_route` 来处理输入参数的格式错误 - -```rust -// Features: ["parser", "extras"] -@@@use mingling::macros::buffer; -@@@use mingling::macros::route; -@@@dispatcher!("greet", 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); + .unwrap(); ResultName::new(name).into() } - -#[renderer(buffer)] -fn render_name_too_long(prev: ErrorNameTooLong) { - let len = *prev; - r_println!("Error: name too long (length: {} > 32)", len); -} - -#[renderer(buffer)] -fn render_name(prev: ResultName) { - r_println!("Hello, {}!", *prev); -} ``` ## 布尔值解析 -`Picker` 当然也可以解析布尔类型,但是布尔类型分为显式和隐式模式: - -| 模式 | 格式 | -| ---- | ----------------------------------- | -| 隐式 | `--confirmed` | -| 显式 | `--confirm true` 或 `--confirm yes` | - -- 使用 `.pick::<bool>(flag)` 时,采用隐式解析:只要标志存在即为 `true` -- 使用 `.pick::<Yes>(flag)` 或 `.pick::<True>(flag)` 时,采用显式解析 - -一般来说使用隐式解析即可,但在处理重要的确认行为时,显式逻辑更符合语义。 +`Picker` 将布尔值解析为**标志**:标志存在即为 `true`。 ```rust -// Features: ["parser"] -@@@use mingling::parser::Yes; +// Features: ["picker"] +@@@use mingling::picker::value::Flag; @@@dispatcher!("test", EntryTest); @@@pack!(ResultDone = ()); #[chain] fn handle_entry(prev: EntryTest) -> Next { -@@@ let prev1 = prev.clone(); - let _confirmed: bool = prev.pick::<Yes>(()).unpack().is_yes(); -@@@ let prev = prev1; - let _confirm: bool = prev.pick::<bool>(["--confirm", "-C"]).unpack(); + // `--confirm` / `-C` 存在 → true + let _confirm: bool = *prev.pick(&arg![confirm: Flag, 'C']).unwrap(); ResultDone::default().to_render() } ``` -## 特殊用法:`usize` 解析 - -**Mingling** 为 `usize` 提供了一个特殊的用法:解析类似 `25G`、`32mib` 等字样 +> [!NOTE] +> 对于重要的确认行为,如果精确的布尔语义很关键,请将标志与显式的值检查配合使用。 -```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); -} -``` - ## 自定义可解析类型 -你可以使用 `Pickable` trait 使你的类型支持被 `Picker` 解析,这也是 `Picker` 拓展性的来源 +你可以使用 `SinglePickable` trait 使你的类型支持被 `Picker` 解析,这也是 `Picker` 拓展性的来源 ```rust -// Features: ["parser"] +// Features: ["picker"] @@@use mingling::macros::buffer; -@@@use mingling::parser::{Pickable, Argument}; +@@@use mingling::picker::{PickerArgResult, SinglePickable}; @@@use mingling::Flag; #[derive(Default, Clone)] pub struct Address { @@ -319,14 +265,18 @@ pub struct Address { port: u16, } -impl Pickable for Address { - type Output = Self; - fn pick(args: &mut Argument, flag: Flag) -> Option<Self::Output> { - let raw = args.pick_argument(flag)?; +impl SinglePickable for Address { + fn pick_single(str: Option<&str>) -> PickerArgResult<Self> { + let Some(raw) = str else { + return PickerArgResult::NotFound; + }; 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 }) + 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); @@ -334,7 +284,7 @@ impl Pickable for Address { #[chain] fn handle_connect_entry(prev: EntryConnect) -> Next { - let address: Address = prev.pick("--addr").unpack(); + let address: Address = prev.pick_or_default(&arg![Address]).unwrap(); ResultConnected::new(address).into() } @@ -351,14 +301,14 @@ fn render_connected(addr: ResultConnected) { Connected: IP: 127.0.0.1 PORT: 8080 ``` -## 自动为枚举实现 Pickable +## 为枚举实现 Pickable -要为枚举类型实现 `Pickable`,只需该枚举实现了 `EnumTag`,然后为其实现 `PickableEnum` 即可 +要让枚举支持 `Picker` 解析,可以手写 `SinglePickable`,用 match 匹配输入: ```rust -// Features: ["parser"] +// Features: ["picker"] @@@use mingling::macros::buffer; -@@@use mingling::parser::PickableEnum; +@@@use mingling::picker::{PickerArgResult, SinglePickable}; @@@use mingling::EnumTag; #[derive(Debug, Default, EnumTag)] pub enum Fruits { @@ -368,13 +318,26 @@ pub enum Fruits { Orange, } -impl PickableEnum for Fruits {} +impl SinglePickable for Fruits { + fn pick_single(str: Option<&str>) -> PickerArgResult<Self> { + 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); @@@pack!(ResultFruit = Fruits); #[chain] fn handle_eat_entry(prev: EntryEat) -> Next { - let fruit: Fruits = prev.pick("--fruit").unpack(); + let fruit: Fruits = prev.pick_or_default(&arg![Fruits]).unwrap(); ResultFruit::new(fruit).into() } @@ -389,4 +352,3 @@ fn render_fruit(prev: ResultFruit) { <p align="center" style="font-size: 0.85em; color: gray;"> Written by @Weicao-CatilGrass </p> -```` diff --git a/docs/_zh_CN/pages/other/features.md b/docs/_zh_CN/pages/other/features.md index 4913945..30231ce 100644 --- a/docs/_zh_CN/pages/other/features.md +++ b/docs/_zh_CN/pages/other/features.md @@ -330,24 +330,12 @@ analyze_and_build_type_mapping().unwrap(); 详见 [示例](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-pathfinder) -## 特性 `parser` - -**介绍:** - -启用参数解析器模块,提供参数解析功能。 - -开启后可以使用 `Picker` 进行简易的参数提取,支持 `pick()` 和 `pick_or()` 等方法。 - -详见 [示例](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-argument-parse) - ## 特性 `picker` **介绍:** 引入依赖 `arg-picker`,为 Mingling 提供更高级的参数解析能力。 -它可以与 `parser`、`clap` 特性共存,但建议不要和 `parser` 特性同时启用,因为两者的 API 极为相似。 - `picker` 是独立于 Mingling 的参数解析器,不依赖 `mingling_core` 的内置参数提取 API。 详见 [示例](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-argument-picker) diff --git a/docs/dev/pages/abouts/code-verify-system.md b/docs/dev/pages/abouts/code-verify-system.md index 61b66e8..929b337 100644 --- a/docs/dev/pages/abouts/code-verify-system.md +++ b/docs/dev/pages/abouts/code-verify-system.md @@ -209,7 +209,7 @@ Use `@@@` for: ````markdown ```rust -// Features: ["parser"] +// Features: ["picker"] // Dependencies: // serde = "1" @@ -237,6 +237,6 @@ mingling::macros::gen_program!(); ```toml [dependencies] -mingling = { path = "../../mingling", features = ["parser"] } +mingling = { path = "../../mingling", features = ["picker"] } serde = { version = "1", features = ["derive"] } ``` diff --git a/docs/example-pages/examples.json b/docs/example-pages/examples.json index 31336f1..8d6eaa6 100644 --- a/docs/example-pages/examples.json +++ b/docs/example-pages/examples.json @@ -16,21 +16,6 @@ ] }, { - "id": "example-argument-parse", - "name": "Argument Parse", - "icon": "📋", - "category": "parsing", - "desc": "Shows how to use Mingling's `parser` feature with a `Picker` to extract and validate typed arguments from the command line.\n", - "tags": [ - "pick", - "Pickable" - ], - "files": [ - "src/main.rs", - "Cargo.toml" - ] - }, - { "id": "example-argument-picker", "name": "Argument Picker", "icon": "📋", @@ -144,21 +129,6 @@ ] }, { - "id": "example-custom-pickable", - "name": "Custom Pickable", - "icon": "🎯", - "category": "parsing", - "desc": "Shows how to implement the `Pickable` trait on custom types for seamless extraction from CLI arguments via Picker.\n", - "tags": [ - "Pickable", - "custom" - ], - "files": [ - "src/main.rs", - "Cargo.toml" - ] - }, - { "id": "example-dispatch-tree", "name": "Dispatch Tree", "icon": "🌳", diff --git a/docs/pages/1-getting-started.md b/docs/pages/1-getting-started.md index 0131ec1..94f507e 100644 --- a/docs/pages/1-getting-started.md +++ b/docs/pages/1-getting-started.md @@ -27,7 +27,7 @@ Some features **directly affect the entire lifecycle behavior**, so you need to [dependencies.mingling] version = "0.5.0" features = [ - "parser", + "picker", "comp", ] ``` diff --git a/docs/pages/3-define-a-chain.md b/docs/pages/3-define-a-chain.md index dca299e..72ade94 100644 --- a/docs/pages/3-define-a-chain.md +++ b/docs/pages/3-define-a-chain.md @@ -92,7 +92,7 @@ fn handle_greet(args: EntryGreet) -> Next { } ``` -If you enable the `parser` feature, you can also use `Picker` for more flexible param extraction — but that's a topic for later. +If you enable the `picker` feature, you can also use `Picker` for more flexible param extraction — but that's a topic for later. ## Putting It Together diff --git a/docs/pages/6-argument-parse-picker.md b/docs/pages/6-argument-parse-picker.md index 9da56d5..da0fff1 100644 --- a/docs/pages/6-argument-parse-picker.md +++ b/docs/pages/6-argument-parse-picker.md @@ -19,34 +19,38 @@ To enable `Picker`, update your `Cargo.toml`: ```toml # Cargo.toml [dependencies.mingling] -features = ["parser"] +features = ["picker"] ``` Now let's see how `Picker` is written: ```rust -// Features: ["parser"] +// Features: ["picker"] @@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); #[chain] fn handle_greet_entry(prev: EntryGreet) -> Next { - let name = prev.pick_or((), "World").unpack(); + let name = prev + .pick_or(&arg![String], || "World".to_string()) + .unwrap(); ResultName::new(name).into() } ``` -`AsPicker` implements `pick`, `pick_or`, and `pick_or_route` for all types convertible to `Vec<String>`. These functions semantically **pick** params from the string list and convert them into structured data. +`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: ["parser"] +// Features: ["picker"] @@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); @@@#[chain] @@@fn handle_greet_entry(prev: EntryGreet) -> Next { -let name = prev.pick_or((), "World").unpack(); +let name = prev + .pick_or(&arg![String], || "World".to_string()) + .unwrap(); @@@ResultName::new(name).into() @@@} ``` @@ -54,75 +58,79 @@ let name = prev.pick_or((), "World").unpack(); Its semantics are: ```rust -// Features: ["parser"] +// Features: ["picker"] @@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); @@@#[chain] @@@fn handle_greet_entry(prev: EntryGreet) { @@@let name: String = - prev.pick_or((), "World").unpack(); -// ~~~~ ~~~~~~~ ~~ ~~~~~~~ ~~~~~~~~ -// | | | | |_ unpack as String -// | | | |__________ default value "World" -// | | |______________ pick the first positional arg (no flag) -// | |______________________ pick or use default -// |___________________________ from the previous input + 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`), do this: +If your program needs to parse flag arguments (e.g. `greet --name Alice`), declare a named flag in `arg!`: ```rust -// Features: ["parser"] +// Features: ["picker"] @@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); #[chain] fn handle_greet_entry(prev: EntryGreet) -> Next { - let name = prev.pick_or(["--name", "-n"], "World").unpack(); + let name = prev + .pick_or(&arg![name: String, 'n'], || "World".to_string()) + .unwrap(); ResultName::new(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: ["parser"] +// Features: ["picker"] @@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); @@@#[chain] @@@fn handle_greet_entry(prev: EntryGreet) { @@@let name: String = - prev.pick_or(["--name", "-n"], "World").unpack(); -// ~~~~ ~~~~~~~ ~~~~~~~~~~~~~~~~ ~~~~~~~ ~~~~~~~~ -// | | | | |_ unpack as String -// | | | |__________ default value "World" -// | | |____________________________ pick the value after "--name" or "-n" -// | |____________________________________ pick or use default -// |_________________________________________ from the previous input + 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 `.unpack()` +## About `.unwrap()` and `route!` -You may have noticed that `Picker` calls `.unpack()` at the end of parsing. It converts the collected results into structured info. +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, `.unpack()` returns the value directly; for multiple picks, it returns a tuple: +For a single pick, `.unwrap()` returns the value directly; for multiple picks, it returns a tuple: ```rust -// Features: ["parser"] +// Features: ["picker"] @@@dispatcher!("test", EntryTest); @@@pack!(ResultInfo = (String, u8, u32)); #[chain] fn handle_test_entry(prev: EntryTest) -> Next { let (name, age, id) = prev - .pick::<String>(["--name", "-n"]) - .pick::<u8>(["--age", "-a"]) - .pick::<u32>(["--id", "-I"]) - .unpack(); + .pick_or_default(&arg![name: String, 'n']) + .pick_or_default(&arg![age: u8, 'a']) + .pick_or_default(&arg![id: u32, 'I']) + .unwrap(); ResultInfo::new((name, age, id)).into() } @@ -138,7 +146,7 @@ As the saying goes: "never trust your users." To handle missing required params, Here's a simple example: ```rust -// Features: ["parser", "extras"] +// Features: ["picker", "extras"] @@@use mingling::macros::buffer; @@@use mingling::macros::route; @@@dispatcher!("greet", EntryGreet); @@ -147,12 +155,13 @@ Here's a simple example: #[chain] fn handle_greet_entry(prev: EntryGreet) -> Next { - let pick_result = prev - .pick_or_route(["--name", "-n"], ErrorNoName::default()) - .unpack(); - - // Use route! macro to expand pick_result - let name = route!(pick_result); + // Use route! macro to expand the Result<Value, Route> + let name = route!( + prev.pick_or_route(&arg![name: String, 'n'], || { + ErrorNoName::default().to_chain() + }) + .to_result() + ); ResultName::new(name).into() } @@ -162,18 +171,18 @@ fn render_greet(result: ResultName) { } ``` -With `pick_or_route`, the code becomes more involved: `.unpack()` no longer returns the value directly, but `Result<Value, Route>`. +With `pick_or_route`, `.to_result()` no longer returns the value directly, but `Result<Value, Route>`. However, **Mingling**'s `extras` feature provides the `route!` macro for simplified expansion. It's not complex — it just reduces boilerplate: ```rust -// Features: ["parser", "extras"] +// Features: ["picker", "extras"] @@@ pack!(ErrorFail = ()); @@@ use mingling::macros::route; +@@@ use mingling::picker::IntoPicker; @@@ fn func() -> mingling::ChainProcess<ThisProgram> { @@@ let args: Vec<String> = vec![]; -@@@ let pick_result = args.pick_or_route::<String, _>((), ErrorFail::new(())).unpack(); -let name = route!(pick_result); +let name = route!(args.pick_or_route(&arg![String], || ErrorFail::new(()).to_chain()).to_result()); @@@ mingling::macros::empty_result!() @@@ } ``` @@ -181,14 +190,14 @@ let name = route!(pick_result); It expands to: ```rust -// Features: ["parser", "extras"] +// Features: ["picker", "extras"] @@@ pack!(ErrorFail = ()); +@@@ use mingling::picker::IntoPicker; @@@ fn func() -> mingling::ChainProcess<ThisProgram> { @@@ let args: Vec<String> = vec![]; -@@@ let pick_result = args.pick_or_route::<String, _>((), ErrorFail::new(())).unpack(); -let name = match pick_result { +let name = match args.pick_or_route(&arg![String], || ErrorFail::new(()).to_chain()).to_result() { Ok(r) => r, - Err(e) => return e.to_chain(), + Err(e) => return e, }; @@@ mingling::macros::empty_result!() @@@ } @@ -196,122 +205,59 @@ let name = match pick_result { ## Post-processing Extracted Values -After picking user input with `pick`, you can use `after` to process it immediately: +After picking user input with `pick`, you can use `post` to process it immediately: ```rust -// Features: ["parser"] +// Features: ["picker"] @@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); #[chain] fn handle_greet_entry(prev: EntryGreet) -> Next { let name = prev - .pick_or(["--name", "-n"], "World") + .pick_or(&arg![name: String, 'n'], || "World".to_string()) // Format immediately after picking --name - .after(|name: String| { + .post(|name: String| { name.replace(['-', '_', '.'], " ") .to_lowercase() .trim() .to_string() }) - .unpack(); + .unwrap(); ResultName::new(name).into() } ``` -Similarly, you can use `after_or_route` to handle input format errors: - -```rust -// Features: ["parser", "extras"] -@@@use mingling::macros::buffer; -@@@use mingling::macros::route; -@@@dispatcher!("greet", 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(buffer)] -fn render_name_too_long(prev: ErrorNameTooLong) { - let len = *prev; - r_println!("Error: name too long (length: {} > 32)", len); -} - -#[renderer(buffer)] -fn render_name(prev: ResultName) { - r_println!("Hello, {}!", *prev); -} -``` - ## Boolean Parsing -`Picker` can also parse booleans, in two modes: - -| Mode | Format | -| -------- | ----------------------------------- | -| Implicit | `--confirmed` | -| Explicit | `--confirm true` or `--confirm yes` | - -- `.pick::<bool>(flag)` uses implicit mode: the flag being present means `true` -- `.pick::<Yes>(flag)` or `.pick::<True>(flag)` uses explicit mode - -Implicit mode is generally sufficient, but for important confirmations, explicit logic is more idiomatic. +`Picker` parses booleans as **flags**: the flag being present means `true`. ```rust -// Features: ["parser"] -@@@use mingling::parser::Yes; +// Features: ["picker"] +@@@use mingling::picker::value::Flag; @@@dispatcher!("test", EntryTest); @@@pack!(ResultDone = ()); #[chain] fn handle_entry(prev: EntryTest) -> Next { -@@@ let prev1 = prev.clone(); - let _confirmed: bool = prev.pick::<Yes>(()).unpack().is_yes(); -@@@ let prev = prev1; - let _confirm: bool = prev.pick::<bool>(["--confirm", "-C"]).unpack(); + // `--confirm` / `-C` present → true + let _confirm: bool = *prev.pick(&arg![confirm: Flag, 'C']).unwrap(); ResultDone::default().to_render() } ``` -## Special Usage: `usize` Parsing - -**Mingling** provides a special `usize` feature: parsing strings like `25G`, `32mib`, etc. +> [!NOTE] +> For important confirmations, pair the flag with an explicit value check if the exact boolean semantics matter. -```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 Pickable Types -You can make your types pickable by `Picker` using the `Pickable` trait — this is where `Picker`'s extensibility comes from. +You can make your types pickable by `Picker` using the `SinglePickable` trait — this is where `Picker`'s extensibility comes from. ```rust -// Features: ["parser"] +// Features: ["picker"] @@@use mingling::macros::buffer; -@@@use mingling::parser::{Pickable, Argument}; +@@@use mingling::picker::{PickerArgResult, SinglePickable}; @@@use mingling::Flag; #[derive(Default, Clone)] pub struct Address { @@ -319,14 +265,18 @@ pub struct Address { port: u16, } -impl Pickable for Address { - type Output = Self; - fn pick(args: &mut Argument, flag: Flag) -> Option<Self::Output> { - let raw = args.pick_argument(flag)?; +impl SinglePickable for Address { + fn pick_single(str: Option<&str>) -> PickerArgResult<Self> { + let Some(raw) = str else { + return PickerArgResult::NotFound; + }; 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 }) + 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); @@ -334,7 +284,7 @@ impl Pickable for Address { #[chain] fn handle_connect_entry(prev: EntryConnect) -> Next { - let address: Address = prev.pick("--addr").unpack(); + let address: Address = prev.pick_or_default(&arg![Address]).unwrap(); ResultConnected::new(address).into() } @@ -351,14 +301,14 @@ Output: Connected: IP: 127.0.0.1 PORT: 8080 ``` -## Auto-implementing Pickable for Enums +## Implementing Pickable for Enums -To make an enum `Pickable`, just implement `EnumTag` on it, then implement `PickableEnum`: +To make an enum pickable, implement `SinglePickable` manually with a match on the input: ```rust -// Features: ["parser"] +// Features: ["picker"] @@@use mingling::macros::buffer; -@@@use mingling::parser::PickableEnum; +@@@use mingling::picker::{PickerArgResult, SinglePickable}; @@@use mingling::EnumTag; #[derive(Debug, Default, EnumTag)] pub enum Fruits { @@ -368,13 +318,26 @@ pub enum Fruits { Orange, } -impl PickableEnum for Fruits {} +impl SinglePickable for Fruits { + fn pick_single(str: Option<&str>) -> PickerArgResult<Self> { + 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); @@@pack!(ResultFruit = Fruits); #[chain] fn handle_eat_entry(prev: EntryEat) -> Next { - let fruit: Fruits = prev.pick("--fruit").unpack(); + let fruit: Fruits = prev.pick_or_default(&arg![Fruits]).unwrap(); ResultFruit::new(fruit).into() } diff --git a/docs/pages/other/features.md b/docs/pages/other/features.md index 4552d30..3779390 100644 --- a/docs/pages/other/features.md +++ b/docs/pages/other/features.md @@ -330,23 +330,11 @@ analyze_and_build_type_mapping().unwrap(); See [example](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-pathfinder) -## Feature `parser` - -**Description:** - -Enables the argument parser module, providing argument parsing functionality. - -When enabled, you can use `Picker` for simple argument extraction, supporting methods like `pick()` and `pick_or()`. - -See [example](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-argument-parse) - ## Feature `picker` **Description:** -Introduces the `arg-picker` dependency, providing more advanced argument parsing capabilities for Mingling. - -It can coexist with the `parser` and `clap` features, but it is recommended not to enable it alongside the `parser` feature, as their APIs are very similar. +Introduces the `arg-picker` dependency, providing advanced argument parsing capabilities for Mingling. `picker` is an argument parser independent of Mingling and does not rely on the built-in argument extraction API of `mingling_core`. |
