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 --- README.md | 18 +- docs/_sidebar.md | 3 + docs/_zh_CN/_sidebar.md | 3 + docs/_zh_CN/pages/1-creating-your-first-program.md | 4 +- docs/_zh_CN/pages/2-implementing-fallbacks.md | 4 +- docs/_zh_CN/pages/3-parsing-complex-arguments.md | 365 +++++++++++++++++++++ docs/_zh_CN/pages/4-implementing-help-display.md | 4 + docs/_zh_CN/pages/5-implementing-completion.md | 4 + docs/pages/1-creating-your-first-program.md | 2 + docs/pages/2-implementing-fallbacks.md | 2 + docs/pages/3-parsing-complex-arguments.md | 365 +++++++++++++++++++++ docs/pages/4-implementing-help-display.md | 4 + docs/pages/5-implementing-completion.md | 4 + 13 files changed, 771 insertions(+), 11 deletions(-) create mode 100644 docs/_zh_CN/pages/3-parsing-complex-arguments.md create mode 100644 docs/_zh_CN/pages/4-implementing-help-display.md create mode 100644 docs/_zh_CN/pages/5-implementing-completion.md create mode 100644 docs/pages/3-parsing-complex-arguments.md create mode 100644 docs/pages/4-implementing-help-display.md create mode 100644 docs/pages/5-implementing-completion.md diff --git a/README.md b/README.md index 69f6e67..d85bef3 100644 --- a/README.md +++ b/README.md @@ -29,15 +29,15 @@ ## 📚 Contents -- [🚀 Intro](#intro) -- [⚡ Quick Start](#quick-start) -- [🧠 Core Concepts](#core-concepts) -- [🏗️ Project Structure](#project-structure) -- [💡 Example Projects](#example-projects) -- [👣 Next Steps](#next-steps) -- [🗺️ Roadmap](#roadmap) -- [🚫 Unplanned Features](#unplanned-features) -- [📄 License](#license) +- [🚀 Intro](#🚀-intro) +- [⚡ Quick Start](#⚡-quick-start) +- [🧠 Core Concepts](#🧠-core-concepts) +- [🏗️ Project Structure](#🏗️-project-structure) +- [💡 Example Projects](#💡-example-projects) +- [👣 Next Steps](#👣-next-steps) +- [🗺️ Roadmap](#🗺️-roadmap) +- [🚫 Unplanned Features](#🚫-unplanned-features) +- [📄 License](#📄-license) ## 🚀 Intro diff --git a/docs/_sidebar.md b/docs/_sidebar.md index cc43930..009dc34 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -1,3 +1,6 @@ - [Welcome!](README) * [Creating your first Program](pages/1-creating-your-first-program) * [Implementing Fallbacks](pages/2-implementing-fallbacks) +* [Parsing Complex Args](pages/3-parsing-complex-arguments) +* [Impl Help Display](pages/4-implementing-help-display) +* [Impl Shell Completion](pages/5-implementing-completion) diff --git a/docs/_zh_CN/_sidebar.md b/docs/_zh_CN/_sidebar.md index b347acf..f8905de 100644 --- a/docs/_zh_CN/_sidebar.md +++ b/docs/_zh_CN/_sidebar.md @@ -1,3 +1,6 @@ - [Welcome!](README) * [创建您的第一个程序](pages/1-creating-your-first-program) * [实现回退机制](pages/2-implementing-fallbacks) +* [解析复杂参数](pages/3-parsing-complex-arguments) +* [实现帮助文档显示](pages/4-implementing-help-display) +* [实现命令行补全](pages/5-implementing-completion) diff --git a/docs/_zh_CN/pages/1-creating-your-first-program.md b/docs/_zh_CN/pages/1-creating-your-first-program.md index 96932ba..d1ff0ae 100644 --- a/docs/_zh_CN/pages/1-creating-your-first-program.md +++ b/docs/_zh_CN/pages/1-creating-your-first-program.md @@ -56,7 +56,7 @@ mingling::macros::gen_program!(); > [!TIP] > -> `gen_Sprogram!()` 宏展开时,会收集在它之前展开的其他组件、类型的信息,这意味着您需要将 `gen_program!()` 放在整个 crate 中最后被展开的位置 +> `gen_program!()` 宏展开时,会收集在它之前展开的其他组件、类型的信息,这意味着您需要将 `gen_program!()` 放在整个 crate 中最后被展开的位置 > > 我推荐放在 `main.rs` 或者 `lib.rs` 的结尾。 @@ -251,6 +251,8 @@ Hello, World! Hello, Alice! ``` + 至此,您已成功创建基本的 **Mingling** 命令行程序,下一章节将会讲述如何为您的命令行程序实现回退机制来处理命令不存在、渲染器不存在的情况。 +

Written by @Weicao-CatilGrass

diff --git a/docs/_zh_CN/pages/2-implementing-fallbacks.md b/docs/_zh_CN/pages/2-implementing-fallbacks.md index 5ed7242..a7c04d0 100644 --- a/docs/_zh_CN/pages/2-implementing-fallbacks.md +++ b/docs/_zh_CN/pages/2-implementing-fallbacks.md @@ -133,7 +133,9 @@ thread 'main' (90772) panicked at src/bin/your-bin.rs:30:5: Renderer "ResultGreetSomeone" not found! note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ``` - + + 以上便是 **Mingling** 的回退机制,在接下来的章节中,您将学习如何使用 `Picker` 解析复杂的用户输入。 +

Written by @Weicao-CatilGrass

diff --git a/docs/_zh_CN/pages/3-parsing-complex-arguments.md b/docs/_zh_CN/pages/3-parsing-complex-arguments.md new file mode 100644 index 0000000..17e8f93 --- /dev/null +++ b/docs/_zh_CN/pages/3-parsing-complex-arguments.md @@ -0,0 +1,365 @@ +

解析复杂参数

+

+ 使用 Mingling Picker 解析复杂的用户输入 +

+ +## 引言 + + 在前文的示例中,我们成功创建了带有 `"greet"` 子命令的命令行程序,可以输出用户输入的第一个参数。 + + 您也注意到了,示例当中使用的方式近乎直接操作字符串,不够语义化,这不利于长期维护。 + +```rust +let name = args.first().cloned().unwrap_or_else(|| "World".to_string()); +``` + + 而本章节将会引入新的 **Mingling** 特性:`Picker`,它提供轻量且和 **Mingling** 类型路由高度契合的命令解析方案。 + + 要启用 `Picker`,您需要修改 `Cargo.toml` ✏️ + +```toml +[dependencies] +mingling = { + version = "...", + features = ["parser"] +} +``` + + 好了,多的不说,让我们上手编辑代码,重写前文的解析代码 ✏️ + +```rust +#[chain] +fn handle_greet_entry(prev: GreetEntry) -> NextProcess { + // 前文中使用的方式: + // let args = prev.inner; + // let name = args.first().cloned().unwrap_or_else(|| "World".to_string()); + + // 引入 Picker 后使用的方式 + let name = prev.pick_or((), "World").unpack(); + + ResultGreetSomeone::new(name) +} +``` + + `Picker` 为所有 `Into>` 实现了 `pick` `pick_or` `pick_or_route` 函数:它们可以语义化地从字符串列表中 **拾取 (Pick)** 参数,并转换为结构化数据。 + + 对于上述示例中的代码: + +```rust +prev.pick_or((), "World").unpack(); +``` + + 它的语义为: + +```rust + prev.pick_or((), "World").unpack(); +// ~~~~ ~~~~~~~ ~~ ~~~~~~~ ~~~~~~~~ +// | | | | |_ 解包为 String +// | | | |__________ 默认值为 "World" +// | | |______________ 取出第一个位置参数(不指定标志) +// | |______________________ 拾取或使用默认 +// |___________________________ 从前一个输入中 +``` + +## 解析标志参数 + + 若您的程序设计需要解析标志参数 (例如:`greet --name Alice`),可以使用如下方式: + +```rust +prev.pick_or(["--name", "-n"], "World").unpack(); +``` + + 同理,它的语义为: + +```rust + prev.pick_or(["--name", "-n"], "World").unpack(); +// ~~~~ ~~~~~~~ ~~~~~~~~~~~~~~~~ ~~~~~~~ ~~~~~~~~ +// | | | | |_ 解包为 String +// | | | |__________ 默认值为 "World" +// | | |____________________________ 取出 "--name" 或 "-n" 后面的参数 +// | |____________________________________ 拾取或使用默认 +// |_________________________________________ 从前一个输入中 +``` + +## 关于 `.unpack()` 💡 + + 您可能注意到了,`Picker` 在命令解析的最后,会执行一个 `.unpack()` 函数,它的作用是将前面解析出来的结果,转换为结构化信息。 + + 对于只拾取了一次的数据来说,`.unpack()` 会返回单个数据,而对于多次拾取,`Picker` 则会返回元组: + +```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(); + +// 可解析参数 --name Alice --age 21 --id 0711251 +``` + +> [!IMPORTANT] +> `Picker` 对解析顺序极其敏感,特别是位置参数:因为它是顺序解析的 +> +> 若您需要解析位置参数,请确保解析前已拾取并消费所有 **标志参数** + +## 使用 `pick_or_route` 处理边界情况 + + 哈哈,就像那句老话:“永远不要相信你的用户”,为了应对错误情况:必要参数缺失、输入类型不匹配、同时启用了互斥选项,这些都是令人头疼的边界情况。 + + `pick_or_route` 便用于上述问题发生时,能将执行链路由到专门的错误处理类型上,以提供精细的错误处理逻辑。 + + 让我们先编写一个简单的示例来展示基本的用法: + +```rust +dispatcher!("greet", GreetCommand => GreetEntry); + +pack!(ResultGreetSomeone = String); +pack!(ErrorGreetNoNameProvided = ()); + +#[chain] +fn handle_greet_entry(prev: GreetEntry) -> NextProcess { + // 使用 `pick_or_route` 提取 `--name` 参数 + // 如果不存在或解析失败,则路由到 ErrorGreetNoNameProvided + let pick_result = prev + .pick_or_route( + ["--name", "-n"], + ErrorGreetNoNameProvided::default().to_render(), + ) + // 在使用了任何可路由到方法后,`unpack` 将会返回 `Result` + .unpack(); + + // 使用 route! 宏展开 `pick_result`, + // 若内部为 Err,该链在此处返回,并路由到指定类型 + let name = route!(pick_result); + ResultGreetSomeone::new(name).to_chain() +} + +// 承接 `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); +} +``` + + 若使用 `pick_or_route`,写法会变得相对复杂:因为 `.unpack()` 不再直接返回参数,而是 `Result` + + 不过 **Mingling** 提供了简化展开的宏 `route!`,它不复杂,只是省略了一部分样板代码: + +```rust +let name = route!(pick_result); + +// 展开为 +let name = match pick_result { + Ok(r) => r, + Err(e) => return e, +}; +``` + +## 提取值的后处理 + + 在您使用 `pick` 提取了用户输入后,可以使用 `after` 或 `after_or_route` 立刻处理该参数 ✏️ + +```rust +#[chain] +fn handle_greet_entry(prev: GreetEntry) -> NextProcess { + let name = prev + .pick_or(["--name", "-n"], "World") + // 在提取出 `--name` 后,立刻对其进行格式化 + .after(|name: String| { + name.replace(['-', '_', '.'], " ") + .to_lowercase() + .trim() + .to_string(); + name + }) + .unpack(); + + ResultGreetSomeone::new(name) // 此处传入的 name 已被格式化处理 +} +``` + + 同样,您可以使用 `after_or_route` 来处理输入参数的格式错误 ✏️ + +```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") + // 和 `after` 不同,此处传入的是 &String + .after_or_route(|name: &String| { + name.replace(['-', '_', '.'], " ") + .to_lowercase() + .trim() + .to_string(); + + // 判断名字长度,若过长则路由到错误类型 + 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); +} +``` + +## 布尔值解析 + + `Picker` 当然也可以解析 **布尔类型**,但是布尔类型分为显式和隐式模式, + + |模式|格式| + |-|-| + |显式|`--confirm true` 或 `--confirm yes`| + |隐式|`--confirmed`| + + - 若您使用 `.pick` 解析 `bool` 类型的时候,它将使用隐式解析:只要标志存在则为 `true` + - 若您使用 `.pick` 解析 `mingling::parser::Yes` 或 `mingling::parser::True` 时,它将使用显式解析,此处必须填写为 `true` / `yes` 时,才能识别为 `true` + + 一般来说:使用隐式解析足以,但是在处理位置参数或重要确认行为时,使用显式逻辑可能更符合语义。 + +```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(); + + // 其他逻辑 +} +``` + +## 特殊用法:`usize` 解析 + + **Mingling** 为 `usize` 提供了一个特殊的用法:解析类似 `25G`、`32mb` 等字样 ✏️ + +```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); +} +``` + +## 自定义可解析类型 + + 您可以使用 `Pickable` trait 使您的类型支持被 `Picker` 解析,这也是 `Picker` 拓展性的来源 ✏️ + +```rust +// 必须实现 Default:当解析失败时,内部会直接记录默认值 +#[derive(Default)] +pub struct Address { + ip: String, + port: u16, +} + +impl Pickable for Address { + type Output = Self; + fn pick(args: &mut Argument, flag: Flag) -> Option { + // 直接从 Argument 中使用 Flag 提取原始字符 + 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 }) + } +} +``` + + 我们为 `Address` 实现 `Pickable`:接下来我们便可以使用 `ip:port` 的方式来输入参数了 ✏️ + +```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); +} +``` + + 执行效果如下: + +```bash +~> your-bin connect --addr 127.0.0.1:8080 +Connected: IP: 127.0.0.1 PORT: 8080 +``` + +## 自动为枚举实现 Pickable + + 要为枚举类型实现 `Pickable` trait,无需手动实现:`Picker` 会为所有实现了 `PickableEnum` 的类型实现 `Pickable`,只需要该枚举类型实现了 `EnumTag` ✏️ + +```rust +// Debug : 用于渲染 +// Default: 用于 Picker 解析 +// EnumTag: 用于实现 PickableEnum +#[derive(Debug, Default, EnumTag)] +pub enum Fruits { + #[default] + Apple, + Banana, + Orange, +} + +// 为 Fruits 实现 PickableEnum +impl PickableEnum for Fruits {} +``` + + 接下来您便可以直接使用 `Picker` 解析该类型 ✏️ + +```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); +} +``` + + 以上便是 `Picker` 的所有用法,在下一章节,我会介绍如何在 **Mingling** 内为命令实现帮助文档。 + +

+ Written by @Weicao-CatilGrass +

diff --git a/docs/_zh_CN/pages/4-implementing-help-display.md b/docs/_zh_CN/pages/4-implementing-help-display.md new file mode 100644 index 0000000..57179d7 --- /dev/null +++ b/docs/_zh_CN/pages/4-implementing-help-display.md @@ -0,0 +1,4 @@ +

实现帮助文档显示

+

+ 用 help 宏为命令实现帮助文档 +

diff --git a/docs/_zh_CN/pages/5-implementing-completion.md b/docs/_zh_CN/pages/5-implementing-completion.md new file mode 100644 index 0000000..f0729f7 --- /dev/null +++ b/docs/_zh_CN/pages/5-implementing-completion.md @@ -0,0 +1,4 @@ +

实现命令行补全

+

+ 使用 Mingling Completion 实现全动态补全系统 +

diff --git a/docs/pages/1-creating-your-first-program.md b/docs/pages/1-creating-your-first-program.md index 75c5081..1dacb82 100644 --- a/docs/pages/1-creating-your-first-program.md +++ b/docs/pages/1-creating-your-first-program.md @@ -250,6 +250,8 @@ Hello, World! ~> your-bin greet Alice Hello, Alice! ``` + + At this point, you have successfully created a basic **Mingling** command-line program. The next chapter will explain how to implement a fallback mechanism for your command-line program to handle cases where a command or renderer does not exist.

Written by @Weicao-CatilGrass diff --git a/docs/pages/2-implementing-fallbacks.md b/docs/pages/2-implementing-fallbacks.md index a820fa1..685d238 100644 --- a/docs/pages/2-implementing-fallbacks.md +++ b/docs/pages/2-implementing-fallbacks.md @@ -134,6 +134,8 @@ Renderer "ResultGreetSomeone" not found! note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ``` + The above is the fallback mechanism of **Mingling**. In the next chapter, you will learn how to use `Picker` to parse complex user inputs. +

Written by @Weicao-CatilGrass

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 +

diff --git a/docs/pages/4-implementing-help-display.md b/docs/pages/4-implementing-help-display.md new file mode 100644 index 0000000..625863e --- /dev/null +++ b/docs/pages/4-implementing-help-display.md @@ -0,0 +1,4 @@ +

Impl Help Display

+

+ Implement help documentation for commands using the help macro +

diff --git a/docs/pages/5-implementing-completion.md b/docs/pages/5-implementing-completion.md new file mode 100644 index 0000000..7622775 --- /dev/null +++ b/docs/pages/5-implementing-completion.md @@ -0,0 +1,4 @@ +

Impl Shell Completion

+

+ Implementing a fully dynamic completion system using Mingling Completion +

-- cgit