diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-04-28 22:39:59 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-04-28 22:39:59 +0800 |
| commit | 85ee549f68449bc70a7f1271a93ad26a8207ee40 (patch) | |
| tree | bfb0b678d0f96c06b196417fd612a9cad2baf7fe /docs/pages | |
| parent | 5bf4209bd138faf76e3bd316fdfa128a08f2bb2e (diff) | |
Rebuild and rewrite the documentation site infrastructure
Diffstat (limited to 'docs/pages')
| -rw-r--r-- | docs/pages/.gitignore | 1 | ||||
| -rw-r--r-- | docs/pages/1-get-started.md | 278 | ||||
| -rw-r--r-- | docs/pages/1-get-started_zh_CN.md | 256 | ||||
| -rw-r--r-- | docs/pages/2-basic.md | 16 | ||||
| -rw-r--r-- | docs/pages/2-basic/1-program.md | 114 | ||||
| -rw-r--r-- | docs/pages/2-basic/2-setup.md | 150 | ||||
| -rw-r--r-- | docs/pages/2-basic/3-dispatcher.md | 95 | ||||
| -rw-r--r-- | docs/pages/2-basic/4-chain.md | 75 | ||||
| -rw-r--r-- | docs/pages/2-basic/5-renderer.md | 73 | ||||
| -rw-r--r-- | docs/pages/3-features.md | 15 | ||||
| -rw-r--r-- | docs/pages/3-features/1-parser.md | 376 | ||||
| -rw-r--r-- | docs/pages/3-features/2-general-renderer.md | 75 | ||||
| -rw-r--r-- | docs/pages/3-features/3-comp.md | 118 | ||||
| -rw-r--r-- | docs/pages/3-features/4-async.md | 6 | ||||
| -rw-r--r-- | docs/pages/3-features/5-clap-parser.md | 6 |
15 files changed, 491 insertions, 1163 deletions
diff --git a/docs/pages/.gitignore b/docs/pages/.gitignore new file mode 100644 index 0000000..dd33554 --- /dev/null +++ b/docs/pages/.gitignore @@ -0,0 +1 @@ +.obsidian diff --git a/docs/pages/1-get-started.md b/docs/pages/1-get-started.md index d3358f4..1627ac4 100644 --- a/docs/pages/1-get-started.md +++ b/docs/pages/1-get-started.md @@ -1,66 +1,256 @@ -# Get Started -This article explains how to quickly create your first **Mingling** command-line program. +<h1 align="center">Get Started</h1> +<p align="center"> + Welcome to Mìng Lìng +</p> + +## Intro + + This chapter will guide you through **Mingling** step by step. + + Before we start, let me explain what **Mingling** can do: + + Without extra features, it is a sub-command dispatch system based on `proc-macro`: it matches user input, finds & creates the corresponding data, then pushes that data into a dispatcher that continually transforms its type. When the data can no longer be transformed, the program renders the final result to the terminal. + + In other words, you need to understand a new dev paradigm: **a fully type-based dispatch system**. This may feel **frustrating** at first, but once you get the hang of it, you'll be able to write CLI apps that are super easy to modify and extend. + + + +## Creating a Basic Program + + Next I'll walk you through creating a basic program—I assume you already have an empty Rust project ready! + +#### 1. Add Dependencies + + Add the following deps to `Cargo.toml` ✏️ -## Quick Start -1. Add `mingling` to your Rust project. -```bash -cargo add mingling -``` -Or add the following to your `Cargo.toml`: ```toml [dependencies] -mingling = "0.1.6" +mingling = "0.1.7" + +# If you want the latest, try the version hosted on Github +mingling = { git = "https://github.com/catilgrass/mingling", branch = "main" } +``` + +> [!NOTE] +> +> This version matches the **Mingling** version used when writing this doc. Check [crates.io](https://crates.io/crates/mingling) for the latest release! 😄 +> +> **Mingling** docs are actively updated to keep pace with the latest version. + + + +#### 2. Create the Program + + Now, create the program in `src/main.rs` ✏️ + +```rust +fn main() { + // Create ThisProgram and run it + ThisProgram::new().exec(); +} + +// The gen_program! macro collects *all preceding* components & types +// then generates the `ThisProgram` struct +mingling::macros::gen_program!(); +``` + +> [!TIP] +> +> When `gen_program!()` expands, it gathers info from other components & types that were expanded before it. This means you must place `gen_program!()` at the very last expansion point in the crate. +> +> I recommend putting it at the end of `main.rs` or `lib.rs`. + + + +#### 3. Create a Command + + Of course, the program currently does nothing—it won't output anything at runtime. So let's create our first command `greet` and say hi to someone ✏️ + +```rust +fn main() { + // ... +} + +// Create a dispatcher, binding GreetCommand to the "greet" sub-command +// When the user specifies this command, send GreetEntry to the dispatcher +dispatcher!("greet", GreetCommand => GreetEntry); + +// ... +gen_program!(); ``` -2. Write the basic code in your `main.rs` or other program entry point. + Don't be scared by the sudden macro and two new types! Let me explain what this macro does: + +##### About the `dispatcher!` macro 💡 + +1. It creates a `GreetCommand` struct and implements the `Dispatcher` trait + + *This tells the framework: there's a new dispatcher that will handle a sub-command's behavior.* + +2. It implements the `Dispatcher` trait's `node(&self) -> Node` function, setting the node to `"greet"` + + *This tells the framework: this dispatcher handles the `"greet"` sub-command.* + +3. It implements the `Dispatcher` trait's `begin` function, converting the user's full input into the first type `GreetEntry` + + *This tells the framework: when this dispatcher is matched, it sends a `GreetEntry` type to the dispatcher for further processing.* + + In short: **"When user types `greet`, I create a `GreetEntry` and throw it into the dispatcher for conversion."** + + + +#### 4. Register the Command + + After creating the `Dispatcher`, we have two types: `GreetCommand` and `GreetEntry`. First, register `GreetCommand` with `ThisProgram` ✏️ + ```rust -use mingling::macros::{dispatcher, gen_program, r_println, renderer}; - fn main() { - // Create ThisProgram let mut program = ThisProgram::new(); - - // Import the dispatcher `HelloCommand` - program.with_dispatcher(HelloCommand); - - // Run the program + + // Register the dispatcher + program.with_dispatcher(GreetCommand); program.exec(); } - -// Define the dispatcher `HelloCommand`, which routes the "hello" subcommand to `HelloEntry` -dispatcher!("hello", HelloCommand => HelloEntry); - -// Define the renderer, which receives `HelloEntry` and renders the content +``` + + Now `ThisProgram` recognizes the `"greet"` sub-command, but the framework still doesn't know what `"greet"` should do. That's where we implement the actual logic: + + + +#### 5. Implement Rendering Behavior + + We want `"greet"` to output `"Hello, World"`: since we're outputting to the screen, we can use another **Mingling** component, `Renderer`, which handles rendering data to the terminal ✏️ + +```rust +// ... +dispatcher!("greet", GreetCommand => GreetEntry); + +// Declare a renderer `render_greet`, specifying the previous type as `GreetEntry` #[renderer] -fn render_hello(_prev: HelloEntry) { - r_println!("Hello, World!") +fn render_greet(_prev: GreetEntry) { + r_println!("Hello, World!"); } - -// Create ThisProgram at the end of the code -gen_program!(); + +// ... +gen_program!(); // The renderer will be registered with the program ``` -3. Install your command-line program and run it. -```bash -cargo install --path ./ -your_bin hello + For functions marked with `#[renderer]`, **Mingling** strictly enforces only one function signature: + +```rust +#[renderer] +fn renderer_name (_prev: PreviousType) { } ``` -Result: -```bash -Hello, World! + + The macro reads the type of the first param and tells `gen_program!` that this function renders that type. + +##### About `r_println!()` 💡 + + You might notice that the print macro used inside `#[renderer]` is `r_println!` instead of `println!`. This is because the framework's rendering logic doesn't happen inside that function: after `#[renderer]` expands, it injects a `r: &mut RenderResult` into the function; `r_println!` appends the message to the `RenderResult`, and after the dispatcher closes, the final rendered data is handed to `Program::exec` for output. + + + +#### 6. Add Execution Logic + + I bet you're already itching to implement something like `greet Alice` to output `"Hello, Alice!"`—and this section is about to do just that! + + **Mingling**'s core execution flow is `Dispatcher -> Chain -> Renderer`, and the key part is `Chain`: it converts the input data type into another type, then lets the dispatcher find the next `Chain` or `Renderer` based on the result type ✏️ + +```rust +dispatcher!("greet", GreetCommand => GreetEntry); + +// Wrap the intermediate type `ResultGreetSomeone` +pack!(ResultGreetSomeone = String); + +#[chain] +fn handle_greet_entry(prev: GreetEntry) -> NextProcess { + let args = prev.inner; + let name = args + .first() + .cloned() + .unwrap_or_else(|| "World".to_string()); + + // Wrap into intermediate type + ResultGreetSomeone::new(name) +} + +#[renderer] +fn render_greet_someone(prev: ResultGreetSomeone) { + // Deref prev to get the raw type + r_println!("Hello, {}!", *prev); +} +``` + + Just like `#[renderer]`, we created a `#[chain]` that processes type `GreetEntry` and outputs `ResultGreetSomeone`. + + This inserts a `Chain` between the original `Dispatcher` and `Renderer`: it extracts the user's input params (or falls back to "World"), then passes them to the renderer to print to the terminal. + +##### About `NextProcess` 💡 + + `NextProcess` is a placeholder generated by `gen_program!()`. After `#[chain]` expands, it's replaced by a type-erased type `ChainProcess<ThisProgram>` that the dispatcher can recognize, helping reduce boilerplate code. + +> [!NOTE] +> +> `NextProcess` is a temporary solution; the next update will wait until Rust's `Impl In Type Aliases` feature is stable. +> +> **But don't worry**: the next `NextProcess` update won't introduce **breaking changes!** + +##### About `pack!` 💡 + + `pack!` is an **extremely** frequently used macro in **Mingling** development: it wraps any type into another type and auto-derives the traits the framework needs. + + Its syntax is as simple as you see: + +```rust +pack!(PackedType = RawType); ``` -## About Async Runtime + Note: `pack!` doesn't support types with lifetimes, because types are always moved (not borrowed) between dispatchers. + + + +#### 7. Compile & Run + + Alright, we've completed a basic CLI app. Here's the full code—you can paste it and run it directly: + +```rust +use mingling::macros::{chain, dispatcher, gen_program, pack, r_println, renderer}; + +fn main() { + let mut program = ThisProgram::new(); + program.with_dispatcher(GreetCommand); + program.exec(); +} + +dispatcher!("greet", GreetCommand => GreetEntry); -**Mingling** supports **async runtime**, you can enable the `async` feature to activate it. +pack!(ResultGreetSomeone = String); -After enabling it, **Mingling** will have the following changes: +#[chain] +fn handle_greet_entry(prev: GreetEntry) -> NextProcess { + let args = prev.inner; + let name = args.first().cloned().unwrap_or_else(|| "World".to_string()); -- The `Chain` trait and `chain!` macro will require you to use **async functions** -- `Program::exec` will become an async function -- The `gen_program!` macro will generate async functions + ResultGreetSomeone::new(name) +} + +#[renderer] +fn render_greet_someone(prev: ResultGreetSomeone) { + r_println!("Hello, {}!", *prev); +} -**Mingling** does not depend on any specific asynchronous runtime internally, which means you can freely choose a suitable asynchronous runtime for your program (such as `async-std`, `tokio`) +gen_program!(); +``` + + Output: + +```bash +~> your-bin greet +Hello, World! +~> your-bin greet Alice +Hello, Alice! +``` -## 💡 Next Steps -> **Mingling**'s basic components [Go](./pages/2-basic) +<p align="center" style="font-size: 0.85em; color: gray;"> + Written by @Weicao-CatilGrass +</p> diff --git a/docs/pages/1-get-started_zh_CN.md b/docs/pages/1-get-started_zh_CN.md new file mode 100644 index 0000000..b72ad2e --- /dev/null +++ b/docs/pages/1-get-started_zh_CN.md @@ -0,0 +1,256 @@ +<h1 align="center">Get Started</h1> +<p align="center"> + 欢迎使用 Mìng Lìng +</p> + +## 前言 + + 本章节将介绍如何渐进式地了解 **Mingling** + + 在开始之前,我先来讲讲 **Mingling** 能做什么: + + 在未开启其他特性时,它本身是一个基于 `proc-macro` 的子命令调度系统:它匹配用户输入的文本,以此查找并创建具体的数据,并将该数据放入调度器中不断转换类型,当该数据被转换到无法转换时,程序会将最终的数据渲染到终端上。 + + 也就是说,您需要理解一套新的开发范式:**完全基于类型的调度系统**。这可能会让您前期的学习**充满挫败感**,但当您逐渐理解这套范式后,您将可以写出极其方便修改和拓展的命令行程序。 + + + +## 创建基本程序 + + 接下来我将会讲述如何创建一个基本的程序,相信您已经准备好了一个空的 Rust 项目! + +#### 1. 添加依赖 + + 在 `Cargo.toml` 中添加如下依赖 ✏️ + +```toml +[dependencies] +mingling = "0.1.7" + +# 如果您要尝鲜,可以试试 Github 上托管的版本 +mingling = { git = "https://github.com/catilgrass/mingling", branch = "main" } +``` + +> [!NOTE] +> +> 该版本基于文档编写时的 **Mingling** 版本,您可以前往 [crates.io](https://crates.io/crates/mingling) 查看最新的版本!😄 +> +> **Mingling** 会积极更新文档,以确保文档内容能紧跟最新版本 + + + +#### 2. 创建程序 + + 接下来,在 `src/main.rs` 中创建程序✏️ + +```rust +fn main() { + // 创建 ThisProgram,并执行 + ThisProgram::new().exec(); +} + +// gen_program! 宏将会收集 *它之前* 的所有组件、类型 +// 然后生成程序 `ThisProgram` +mingling::macros::gen_program!(); +``` + +> [!TIP] +> +> `gen_Sprogram!()` 宏展开时,会收集在它之前展开的其他组件、类型的信息,这意味着您需要将 `gen_program!()` 放在整个 crate 中最后被展开的位置 +> +> 我推荐放在 `main.rs` 或者 `lib.rs` 的结尾。 + + + +#### 3. 创建命令 + + 当然,现在的程序什么都没有,在运行时不会输出任何消息。所以,让我们创建第一条命令 `greet`,给谁打个招呼吧 ✏️ + +```rust +fn main() { + // ... +} + +// 创建分发器,并将 GreetCommand 绑定在 "greet" 子命令 +// 在用户指定该命令时,向调度器发送 GreetEntry +dispatcher!("greet", GreetCommand => GreetEntry); + +// ... +gen_program!(); +``` + + 不要被突然多出来的一个宏和两个类型所吓到!我来逐一解释这个宏干了什么: + +##### 关于 `dispatcher!` 宏 💡 + +1. 宏创建了一个` GreetCommand` 结构体,并实现了 `Dispatcher` trait + + *这一步告诉框架:现在有了个新的分发器,它将会承接一个子命令的行为。* + +2. 宏实现了 `Dispatcher` trait 内部的 `node(&self) -> Node` 函数,并告诉节点为 `"greet"` + + *这一步告诉框架:该分发器将承接子命令 `"greet"` 的行为* + +3. 宏实现了 `Dispatcher` trait 内部的 `begin` 函数,将用户输入的完整参数转换为了第一个类型 `GreetEntry` + + *这一步告诉框架:该分发器在被匹配到后,将会向调度器发送类型 `GreetEntry`,供后续执行* + + 简而言之:**“用户输入 `greet`,我就创建 `GreetEntry`,丢给调度器转换”** + + + +#### 4. 注册命令 + + 在 `Dispatcher` 创建后,我们得到了两个类型 `GreetCommand` 和 `GreetEntry`,首先将 `GreetCommand` 注册到 `ThisProgram` ✏️ + +```rust +fn main() { + let mut program = ThisProgram::new(); + + // 注册分发器 + program.with_dispatcher(GreetCommand); + program.exec(); +} +``` + + 这样,`ThisProgram` 就认得 `"greet"` 子命令了,但是框架还不知道 `"greet"` 的行为是怎样的。此时我们便需要实现具体的逻辑: + + + +#### 5. 实现渲染行为 + + 我们期望 `"greet"` 的时候输出 `"Hello, World"`:既然要输出到屏幕,那么我们可以使用 **Mingling** 的另一个组件 `Renderer`,它负责将数据渲染到终端 ✏️ + +```rust +// ... +dispatcher!("greet", GreetCommand => GreetEntry); + +// 声明渲染器 `render_greet`,并表示前一个类型是 `GreetEntry` +#[renderer] +fn render_greet(_prev: GreetEntry) { + r_println!("Hello, World!"); +} + +// ... +gen_program!(); // 渲染器会被注册到程序 +``` + + 对于 `#[renderer]` 属性宏标记的函数,**Mingling** 严格规定只允许使用一种函数签名: + +```rust +#[renderer] +fn renderer_name (_prev: PreviousType) { } +``` + + 宏会读取到第一个参数的类型,并告诉 `gen_program!` 该函数用来渲染该类型。 + +##### 关于 `r_println!()` 💡 + + 您可能会注意到,在 `#[renderer]` 中使用的打印宏是 `r_println!` 而非 `println!`,这是因为框架的渲染逻辑并不在该函数内:在 `#[renderer]` 展开后,会向函数注入一个 `r: &mut RenderResult`;而 `r_println!` 将信息追加到 `RenderResult` 内,并在调度器关闭后,将最终的渲染数据交给 `Program::exec` 函数输出。 + + + +#### 6. 增加执行逻辑 + + 我猜您已经很想实现 `greet Alice` 这样的语法来输出 `"Hello, Alice!"` 了,本段正准备干这件事! + + **Mingling** 的核心执行流程是 `Dispatcher -> Chain -> Renderer`,而最关键的就是 `Chain`:它负责将输入的数据类型转换为其他类型,然后让调度器根据结果的类型找到下一个 `Chain` 或者 `Renderer ✏️ + +```rust +dispatcher!("greet", GreetCommand => GreetEntry); + +// 包装中间类型 `ResultGreetSomeone` +pack!(ResultGreetSomeone = String); + +#[chain] +fn handle_greet_entry(prev: GreetEntry) -> NextProcess { + let args = prev.inner; + let name = args + .first() + .cloned() + .unwrap_or_else(|| "World".to_string()); + + // 包装为中间类型 + ResultGreetSomeone::new(name) +} + +#[renderer] +fn render_greet_someone(prev: ResultGreetSomeone) { + // 解引用 prev 拿到原始类型 + r_println!("Hello, {}!", *prev); +} +``` + + 像 `#[renderer]` 一样,我们创建了一个 `#[chain]`,它处理类型 `GreetEntr`,输出 `ResultGreetSomeone` + + 这样我们就在原本的 `Dispatcher` 和 `Renderer` 中间插入了一个 `Chain`:它可以将用户输入的参数提取出来(或回退到默认值 "World"),再交由渲染器打印到终端。 + +##### 关于 `NextProcess` 💡 + + `NextProcess` 是由 `gen_program!()` 生成的占位符,在 `#[chain]` 展开后,它将被替换为调度器能识别的类型擦除类型 `ChainProcess<ThisProgram>`,用于减少代码量 + +> [!NOTE] +> +> `NextProcess` 方案为临时替代,下一次更新需要等待 Rust 的 `Impl In Type Aliases` 特性稳定后。 +> +> **不过,您不用担心**:下一次 `NextProcess` 的更新不会引入 **破坏性变更!** + +##### 关于 `pack!` 💡 + + `pack!` 是 **Mingling** 开发过程中使用频率 **极高** 的宏:它负责将任意类型包装成另一个类型,并自动为其派生框架所需的特征。 + + 它的语法如您所见,极为简单: + +```rust +pack!(PackedType = RawType); +``` + + 不过请注意:`pack!` 宏不支持带有生命周期的类型包装,因为类型在调度器之间的流转方式永远都是 `move` 而非 `borrow`。 + + + +#### 7. 编译并运行 + + 好的,至此我们完成了一个基本的命令行程序,以下是完整代码,您可以直接粘贴运行: + +```rust +use mingling::macros::{chain, dispatcher, gen_program, pack, r_println, renderer}; + +fn main() { + let mut program = ThisProgram::new(); + program.with_dispatcher(GreetCommand); + program.exec(); +} + +dispatcher!("greet", GreetCommand => GreetEntry); + +pack!(ResultGreetSomeone = String); + +#[chain] +fn handle_greet_entry(prev: GreetEntry) -> NextProcess { + let args = prev.inner; + let name = args.first().cloned().unwrap_or_else(|| "World".to_string()); + + ResultGreetSomeone::new(name) +} + +#[renderer] +fn render_greet_someone(prev: ResultGreetSomeone) { + r_println!("Hello, {}!", *prev); +} + +gen_program!(); +``` + + 运行结果: + +```bash +~> your-bin greet +Hello, World! +~> your-bin greet Alice +Hello, Alice! +``` + +<p align="center" style="font-size: 0.85em; color: gray;"> + Written by @Weicao-CatilGrass +</p> diff --git a/docs/pages/2-basic.md b/docs/pages/2-basic.md deleted file mode 100644 index 78d2fda..0000000 --- a/docs/pages/2-basic.md +++ /dev/null @@ -1,16 +0,0 @@ -<h1 align="center">Mingling's Basic Components</h1> -<p align="center"> - Table of Contents -</p> - ---- - -Mingling abstracts the lifecycle of a CLI program into the following types: - -| Component | Description | -| :--- | :--- | -| [Program](./pages/2-basic/1-program) | Records resources for the current context | -| [Setup](./pages/2-basic/2-setup) | Bundles commonly used functionalities | -| [Dispatcher](./pages/2-basic/3-dispatcher) | Dispatches user input to specific types | -| [Chain](./pages/2-basic/4-chain) | Handles type conversion | -| [Renderer](./pages/2-basic/5-renderer) | Handles type rendering | diff --git a/docs/pages/2-basic/1-program.md b/docs/pages/2-basic/1-program.md deleted file mode 100644 index ae22896..0000000 --- a/docs/pages/2-basic/1-program.md +++ /dev/null @@ -1,114 +0,0 @@ -<h1 align="center">Program</h1> -<p align="center"> - Mingling's Basic Components -</p> - ---- - -## Intro - -`Program` is the data structure that holds the state for **Mingling** CLI programs. It manages the user's context and enables type-based dispatch. - -`Program` needs to implement the `ProgramCollect` trait, - -> but, you don't have to do this manually — - -The `mingling_macros` crate provides the `gen_program!()` macro, which can auto collect resources defined by the `dispatcher!`, `chain!`, and the `completion!` macro of the `comp` feature. - -```rust -// Define Dispatcher -dispatcher!("hello", HelloCommand => HelloEntry); - -// Define Renderer -#[renderer] -fn render_hello(_prev: HelloEntry) { - r_println!("Hello, World!") -} - -// Collect all resources here and generate ThisProgram -gen_program!(); - -// You can also explicitly declare a Program -// with a different name like this: -// gen_program!(MyProgram); -``` - -## Adding Setup - -You can use the `with_setup` function to add preset [Setup](pages/2-basic/2-setup) to your program, which provide reusable functionality. - -For example, you can use the following code to add parsing for global flags like `--confirm` / `--help` / `--quiet` to your program: - -```rust -use mingling::{ - macros::gen_program, - setup::BasicProgramSetup -}; - -fn main() { - let mut program = ThisProgram::new(); - // Add `BasicProgramSetup` - program.with_setup(BasicProgramSetup); - program.exec(); -} - -// Generate `ThisProgram` -gen_program!(); -``` - -## Adding Dispatcher - -You can use `with_dispatcher` or `with_dispatchers` to add [Dispatchers](pages/2-basic/3-dispatcher) to your program to make it work: - -```rust -// Define two Dispatchers using `dispatcher!` -dispatcher!("member.add", - AddMemberCommand => AddMemberEntry); -dispatcher!("member.rm", - RemoveMemberCommand => RemoveMemberEntry); - -fn main() { - let mut program = ThisProgram::new(); - - // Register Dispatchers - program.with_dispatcher(AddMemberCommand); - program.with_dispatcher(RemoveMemberCommand); - - // Or use `with_dispatchers` - program.with_dispatchers(( - AddMemberCommand, - RemoveMemberCommand - )); - - program.exec(); -} -``` - -## Parsing Global Args - -You can extract global arguments before the program runs to control the global state of the `Program`: - -```rust -fn main() { - let mut program = ThisProgram::new(); - - let mut output = current_dir().unwrap(); - - // Pick the "--quiet" or "-q" flag - program.global_flag(["--quiet", "-q"], |p| { - // Disable render output - p.stdout_setting.render_output = false; - }); - - // Pick the "--output" or "-O" flag, write to output - program.global_argument( - ["--output", "-O"], - |_, v| output = PathBuf::from(v) - ); - - program.exec(); -} -``` - -## 💡 Next Page -> **Basic Component** - Setup [Go](./pages/2-basic/2-setup) diff --git a/docs/pages/2-basic/2-setup.md b/docs/pages/2-basic/2-setup.md deleted file mode 100644 index 102e213..0000000 --- a/docs/pages/2-basic/2-setup.md +++ /dev/null @@ -1,150 +0,0 @@ -<h1 align="center">Setup</h1> -<p align="center"> - Mingling's Basic Components -</p> - ---- - -## Intro - -`Setup` is used to organize and package the initialization process of a `Program`, making the project easier to manage. - -## Usage - -It is defined as follows: - -```rust -struct MySetup; -impl ProgramSetup<ThisProgram> - for MySetup -{ - fn setup( - &mut self, - program: &mut Program<ThisProgram> - ) { - // Your setup logic - } -} -``` - -For example: - -```rust -use std::{env::current_dir, path::PathBuf}; - -use mingling::{ - Program, - macros::{dispatcher, gen_program, renderer}, - setup::ProgramSetup, -}; - -// Global state -static OUTPUT_PATH: std::sync::OnceLock<PathBuf> - = std::sync::OnceLock::new(); - -fn main() { - let mut program = ThisProgram::new(); - program.with_setup(MySetup); - program.exec(); -} - -// Define two Dispatchers using `dispatcher!` -dispatcher!("member.add", - AddMemberCommand => AddMemberEntry); -dispatcher!("member.rm", - RemoveMemberCommand => RemoveMemberEntry); - -struct MySetup; -impl ProgramSetup<ThisProgram, ThisProgram> for MySetup { - fn setup( - &mut self, program: &mut Program<ThisProgram, ThisProgram> - ) { - // Register Dispatchers - program.with_dispatcher(AddMemberCommand); - program.with_dispatcher(RemoveMemberCommand); - - // Initialize global output once - OUTPUT_PATH.get_or_init(|| current_dir().unwrap()); - - // Pick the "--quiet" or "-q" flag - program.global_flag(["--quiet", "-q"], |p| { - // Disable render output - p.stdout_setting.render_output = false; - }); - - // Pick the "--output" or "-O" flag, write to output - program.global_argument(["--output", "-O"], |_, v| { - let _ = OUTPUT_PATH.set(PathBuf::from(v)); - }); - } -} - -gen_program!(); -``` - -## Simplified Syntax - -If you find the above declaration method too **verbose**, you can use the `program_setup!` macro to simplify it. The format is: - -```rust -#[program_setup] -fn my_setup( - program: &mut Program<ThisProgram> -) { - // Your setup logic -} -``` - -For example: - -```rust -use std::{env::current_dir, path::PathBuf}; - -use mingling::{ - Program, - macros::{ - dispatcher, - gen_program, - program_setup, - renderer - }, -}; - -static OUTPUT_PATH: std::sync::OnceLock<PathBuf> - = std::sync::OnceLock::new(); - -#[tokio::main] -async fn main() { - let mut program = ThisProgram::new(); - program.with_setup(MySetup); - program.exec().await; -} - -dispatcher!("member.add", - AddMemberCommand => AddMemberEntry); -dispatcher!("member.rm", - RemoveMemberCommand => RemoveMemberEntry); - -#[program_setup] -fn my_setup( - program: &mut Program<ThisProgram> -) { - program.with_dispatcher(AddMemberCommand); - program.with_dispatcher(RemoveMemberCommand); - - OUTPUT_PATH.get_or_init(|| current_dir().unwrap()); - - program.global_flag(["--quiet", "-q"], |p| { - p.stdout_setting.render_output = false; - }); - - program.global_argument(["--output", "-O"], |_, v| { - let _ = OUTPUT_PATH.set(PathBuf::from(v)); - }); -} - -gen_program!(); -``` - -## 💡 Next Page -> **Basic Component** - Dispatcher [Go](./pages/2-basic/3-dispatcher) diff --git a/docs/pages/2-basic/3-dispatcher.md b/docs/pages/2-basic/3-dispatcher.md deleted file mode 100644 index 66e5c90..0000000 --- a/docs/pages/2-basic/3-dispatcher.md +++ /dev/null @@ -1,95 +0,0 @@ -<h1 align="center">Dispatcher</h1> -<p align="center"> - Mingling's Basic Components -</p> - ---- - -## Intro - -`Dispatcher` is a core concept in **Mingling**, used to dispatch user-input arguments to corresponding types, which are then handled by [Chain](pages/2-basic/4-chain) or [Renderer](pages/2-basic/5-renderer). - -To define a `Dispatcher`, it is recommended to use the `dispatcher!` macro provided by `mingling_macros`: - -```rust -// User input: your_bin hello -// Will access HelloCommand and -// dispatch arguments to HelloEntry -dispatcher!("hello", - HelloCommand => HelloEntry); - -// User input: your_bin sub foo -// Will access FooCommand and -// dispatch arguments to FooEntry -dispatcher!("sub.foo", - FooCommand => FooEntry); - -// Same as above -dispatcher!("sub.bar", - BarCommand => BarEntry); -``` - -If you explicitly specify a name in the `gen_program!` macro, for example: - -```rust -gen_program!(MyProgram); -``` - -Then when using the `dispatcher!` macro, you must also explicitly specify the [Program](pages/2-basic/1-program): - -```rust -dispatcher!(MyProgram, "hello", - HelloCommand => HelloEntry); -``` - -**Tips:** Finally, add the `Dispatcher` you created to the [Program](pages/2-basic/1-program): - -```rust -fn main() { - let mut program = ThisProgram::new(); - program.with_dispatcher(HelloCommand); - program.with_dispatcher(SubFooCommand); - program.with_dispatcher(SubBarCommand); - program.exec(); -} -``` - -## Manual Impl - -You can also manually implement the basic `Dispatcher` for more fine-grained control. However, compared to the procmacro, it is more cumbersome and cannot intelligently introduce certain traits based on the state of feature flags. - -```rust -// Define AddMemberEntry -// Use the `Groupped` derive to -// mark AddMemberEntry as a member of ThisProgram -#[derive(Debug, Groupped)] -pub struct AddMemberEntry { - // Define arguments to store user input - pub(crate) args: Vec<String>, -} - -// Implement the Dispatcher trait -impl Dispatcher<ThisProgram> for AddMemberCommand { - // Return the node name of this Dispatcher - fn node(&self) -> Node { - node!("member.add") - } - - // When executing this Dispatcher, output AddMemberEntry - fn begin(&self, args: Vec<String>) - -> ChainProcess<ThisProgram> - { - AnyOutput::new(AddMemberEntry { args }).route_chain() - } - - // Used to implement the clone trait for this Dispatcher - fn clone_dispatcher(&self) - -> Box<dyn Dispatcher<ThisProgram>> - { - Box::new(AddMemberCommand) - } -} -``` - -## 💡 Next Page -> **Basic Component** - Chain [Go](./pages/2-basic/4-chain) diff --git a/docs/pages/2-basic/4-chain.md b/docs/pages/2-basic/4-chain.md deleted file mode 100644 index d47642a..0000000 --- a/docs/pages/2-basic/4-chain.md +++ /dev/null @@ -1,75 +0,0 @@ -<h1 align="center">Chain</h1> -<p align="center"> - Mingling's Basic Components -</p> - ---- - -## Intro - -Like `Dispatcher`, `Chain` is also a core concept in building the entire **Mingling** framework. It is used to receive a dispatch of one type and convert it into another type. - -```rust -dispatcher!("hello", - HelloCommand => HelloEntry); - -// Define intermediate type ParsedHello, internally a String -pack!(ParsedHello = String); - -// Define chain parse_hello (expands to ParseHello) -// Declare conversion from HelloEntry -#[chain] -fn parse_hello(prev: HelloEntry) -> NextProcess { - // Take the inner reference of HelloEntry - let args = &*prev; - - // Extract the first argument, use default value "World" - // if it doesn't exist - let first = args.first().cloned().unwrap_or_else(|| "World".to_string()); - - // Pack the extracted argument into ParsedHello and - // dispatch to the next chain - ParsedHello::new(first).to_chain() -} -``` - -> [!Tip] -> `NextProcess` is a marker type in **Mingling**, from `mingling::marker`. -> -> It serves no functional purpose other than to simplify the declaration of chain functions. After the `chain!` macro expands, `NextProcess` will be replaced with `mingling::ChainProcess<ThisProgram>`. - -## Manual Impl - -You can also manually implement the basic `Chain` for finer control. - -However, please note that within the `chain!` macro, a `register_type!` macro is executed. This macro does not expand to any content; it only informs the `gen_program` context that this type exists. - -```rust -dispatcher!("hello", - HelloCommand => HelloEntry); - -pack!(ParsedHello = String); - -struct ParseHello; -impl Chain<ThisProgram> for ParseHello { - type Previous = HelloEntry; - fn proc(prev: Self::Previous) - -> ChainProcess<ThisProgram> - { - let args = &*prev; - let first = args - .first() - .cloned() - .unwrap_or_else(|| - "World".to_string() - ); - ParsedHello::new(first).to_chain() - } -} - -// Register chain to the context -register_chain!(HelloEntry, ParseHello); -``` - -## 💡 Next Page -> **Basic Component** - Renderer [Go](./pages/2-basic/5-renderer) diff --git a/docs/pages/2-basic/5-renderer.md b/docs/pages/2-basic/5-renderer.md deleted file mode 100644 index 2085d40..0000000 --- a/docs/pages/2-basic/5-renderer.md +++ /dev/null @@ -1,73 +0,0 @@ -<h1 align="center">Renderer</h1> -<p align="center"> - Mingling's Basic Components -</p> - ---- - -## Intro - -`Renderer` is similar to [Chain](pages/2-basic/4-chain) in that they both handle processing for a specific type. The difference is: [Chain](pages/2-basic/4-chain) transforms the type, while `Renderer` terminates the program and prints the information of that type to the terminal. - -A type can be processed by both [Chain](pages/2-basic/4-chain) and `Renderer`. If the type is `route_chain`ed, the system will search for a [Chain](pages/2-basic/4-chain) capable of handling that type. If none is found, it will automatically be routed to the `Renderer` to print the result of that type. - -The following example demonstrates how to handle rendering logic: - -```rust -dispatcher!("hello", - HelloCommand => HelloEntry); - -pack!(ParsedHello = String); - -// It's the Chain defined in the Dispatcher chapter -#[chain] -fn parse_hello(prev: HelloEntry) -> NextProcess { - let args = &*prev; - let first = args - .first() - .cloned() - .unwrap_or_else(|| - "World".to_string() - ); - - // Distribute the type to the Renderer - ParsedHello::new(first).to_render() -} - -// Define the renderer to -// handle rendering of ParsedHello -#[renderer] -fn render_hello(prev: ParsedHello) { - // Use r_println or r_print to - // render the content of ParsedHello - r_println!("Hello, {}!", *prev) -} -``` - -> [!Tip] -> `r_print!` can only be used inside a `Renderer`. This is because after the `renderer!` macro expands, it injects `r: &mut RenderResult` into the context. -> -> And `r_print!` directly writes content to the value `r`. -> This means: if there is no `&mut RenderResult` named `r` in the context, `r_print!` cannot be used. - -## Manual Impl - -Similarly, you can also manually implement `Renderer`, - -but note that inside the `renderer!` macro, a `register_type!` macro is executed. This macro itself does not expand into any content; it is only used to inform the `gen_program` context that the type exists: - -```rust -struct RenderHello; -impl Renderer for RenderHello { - type Previous = ParsedHello; - fn render( - prev: Self::Previous, - r: &mut RenderResult - ) { - r_println!("Hello, {}!", *prev) - } -} - -// Register renderer to the context -register_renderer!(ParsedHello, RenderHello); -``` diff --git a/docs/pages/3-features.md b/docs/pages/3-features.md deleted file mode 100644 index a2eac63..0000000 --- a/docs/pages/3-features.md +++ /dev/null @@ -1,15 +0,0 @@ -<h1 align="center">Mingling's Features</h1> -<p align="center"> - Table of Contents -</p> - ---- - -Mingling provides the following features for use: - -| Feature | Description | -| :--- | :--- | -| [Parser](./pages/3-features/1-parser) | Converts user input into structured data | -| [General Renderer](./pages/3-features/2-general-renderer) | Enables the `Renderer` to support serialization into different output formats | -| [Completion](./pages/3-features/3-comp) | Enables the program to support dynamic command-line completion | -| [Async](./pages/3-features/4-async) | Provides async execution capabilities | diff --git a/docs/pages/3-features/1-parser.md b/docs/pages/3-features/1-parser.md deleted file mode 100644 index 0b5ded5..0000000 --- a/docs/pages/3-features/1-parser.md +++ /dev/null @@ -1,376 +0,0 @@ -<h1 align="center">Parser</h1> -<p align="center"> - Mingling's Features -</p> - ---- - -## Enable Feature - -`parser` is a feature provided by **Mingling**. You can enable it in the following way: - -```toml -[dependencies] -mingling = { - version = "...", - features = ["parser"] -} -``` - -## Usage - -`parser` provides the ability to transform user input into structured data. Its core concept is **pick**. - -The following demonstrates the parsing approach without using a `Picker`: - -```rust -#[chain] -fn parse_hello(prev: HelloEntry) -> NextProcess { - let args = &*prev; - let first = args.first().cloned().unwrap_or_else(|| "World".to_string()); - ParsedHello::new(first).to_render() -} -``` - -This is how it looks when using `Picker`: - -```rust -#[chain] -fn parse_hello(prev: HelloEntry) -> NextProcess { - // Create Picker - let picker = Picker::<ThisProgram>::new(prev.inner); - - // Extract the first argument from the Picker, - // fallback to "World" if it doesn't exist - let first = picker - .pick_or((), "World") - .unpack_directly(); - - ParsedHello::new(first).to_render() -} -``` - -You might notice that using `Picker` can sometimes make statements more verbose, but this is only when parsing a small number of arguments. What if we complicate the scenario? - -Suppose we want to design the following commands: - -```bash -# Eat 1 apple weighing at least 20 -fruit eat Apple --min-weight 20 - -# Eat 10 apples weighing at least 20 -fruit eat Apple --min-weight 20 --count 10 - -# Eat 1 apple weighing between 10 and 20 -fruit eat Apple --min-weight 10 --max-weight 20 - -# Eat 1 apple weighing between 20 and 10 (incorrect logic) -fruit eat Apple --min-weight 20 --max-weight 10 - -# When no specific fruit is specified, eat banana -fruit eat --count 5 -``` - -For this complex scenario, the `Picker` comes into play! - -We first design the type `ParsedEatFruit` - -```rust -#[derive(Debug, Default, Groupped)] -struct ParsedEatFruit { - count: i16, - weight_range: (i16, i16), - fruit_type: Fruit, -} - -#[derive(Debug, Default, EnumTag)] -enum Fruit { - #[default] - Banana, - Apple, - Orange, -} -``` - -Then create the basic binary program `fruit` - -```rust -use mingling::{ - EnumTag, Groupped, - macros::{chain, dispatcher, gen_program, r_println, renderer}, - parser::PickableEnum, -}; - -fn main() { - let mut program = ThisProgram::new(); - program.with_dispatcher(FruitEatCommand); - program.exec(); -} - -dispatcher!("eat", - FruitEatCommand => FruitEatEntry); - -#[derive(Debug, Default, Groupped)] -struct ParsedEatFruit { - count: i16, - weight_range: (i16, i16), - fruit_type: Fruit, -} - -#[derive(Debug, Default, EnumTag)] -enum Fruit { - #[default] - Banana, - Apple, - Orange, -} - -// Implement PickableEnum for Fruit to make it pickable -impl PickableEnum for Fruit {} - -#[chain] -fn parse_fruit_eat(prev: FruitEatEntry) -> NextProcess { - // ... -} - -#[renderer] -fn render_fruit_eat(prev: ParsedEatFruit) { - let weight_str = match prev.weight_range { - (min, max) if min == 0 && max > 0 => { - format!("up to {}.", max) - } - (min, max) if min > 0 && max == 0 => { - format!("at least {}.", min) - } - (min, max) if min > 0 && max > 0 && min != max => { - format!("between {} and {}.", min, max) - } - (min, max) if min > 0 && max > 0 && min == max => { - format!("exactly {}.", min) - } - _ => "unknown.".to_string(), - }; - - let fruit_type = if prev.count > 1 { - format!("{}s", prev.fruit_type.enum_info().0) - } else { - prev.fruit_type.enum_info().0.to_string() - }; - - r_println!( - "I ate {} {}, each weighing {}", - prev.count, - fruit_type, - weight_str - ); -} - -gen_program!(); -``` - -Now focus on writing the logic for `parse_fruit_eat`: - -> Review the business logic: -> -> 1 - The default fruit is Banana -> -> 2 - The default quantity is 1 -> -> 3 - The default weight is (0, 0) -> -> 4 - When `max-weight` is less than `min-weight`, the business logic is in error - -Before writing the code, define the error type `MinGreaterThanMax` and the related `Renderer` - -```rust -pack!(MinGreaterThanMax = ()); - -#[renderer] -fn render_min_greater_than_max(_prev: MinGreaterThanMax) { - r_println!("Error: min weight cannot be greater than max weight."); -} -``` - -Now start writing the logic: - -```rust -#[chain] -fn parse_fruit_eat(prev: FruitEatEntry) -> NextProcess { - let picker = Picker::new(prev.inner); - let mut min_weight: i16 = 0; - let parsed = picker - .pick_or(["--count", "-n"], 1) - .pick::<i16>("--min-weight") // default: 0 - .after(|min| { - // Copy `min` to external variable - min_weight = min; - min - }) - .pick_or::<i16>("--max-weight", min_weight) // default: min_weight - .after_or_route(|max| { - // Check if `max` is valid - if max < &min_weight { - Err(MinGreaterThanMax::default()) - } else { - Ok(max.clone()) - } - }) - .pick(()) - // Since there's a possibility of being routed, - // don't use `unpack_directly` - .unpack(); - - match parsed { - Ok((count, min_weight, max_weight, fruit_type)) => { - let parsed = ParsedEatFruit { - count, - weight_range: (min_weight, max_weight), - fruit_type, - }; - - AnyOutput::new(parsed).route_renderer() - } - Err(route) => route.to_render(), - } -} -``` - -Complete code: - -```rust -use mingling::{ - AnyOutput, EnumTag, Groupped, - macros::{chain, dispatcher, gen_program, pack, r_println, renderer}, - parser::{PickableEnum, Picker}, -}; - -fn main() { - let mut program = ThisProgram::new(); - program.with_dispatcher(FruitEatCommand); - program.exec(); -} - -dispatcher!("eat", - FruitEatCommand => FruitEatEntry); - -#[derive(Debug, Default, Groupped)] -struct ParsedEatFruit { - count: i16, - weight_range: (i16, i16), - fruit_type: Fruit, -} - -#[derive(Debug, Default, EnumTag)] -enum Fruit { - #[default] - Banana, - Apple, - Orange, -} - -impl PickableEnum for Fruit {} - -pack!(MinGreaterThanMax = ()); - -#[chain] -fn parse_fruit_eat(prev: FruitEatEntry) -> NextProcess { - let picker = Picker::new(prev.inner); - let mut min_weight: i16 = 0; - let (count, min_weight, max_weight, fruit_type) = route! { - picker - // Pick count - .pick_or::<i16>(["--count", "-n"], 1 as i16) - - // Pick min/max weight - .pick::<i16>("--min-weight") - .after(|min| { - min_weight = min; - min - }) - - .pick_or::<i16>("--max-weight", min_weight) - .after_or_route(|max| { - if max < &min_weight { - Err(MinGreaterThanMax::default().to_render()) - } else { - Ok(max.clone()) - } - }) - - // Pick Type - .pick(()) - .unpack() - }; - - ParsedEatFruit { - count, - weight_range: (min_weight, max_weight), - fruit_type, - } - .to_render() -} - -#[renderer] -fn render_min_greater_than_max(_prev: MinGreaterThanMax) { - r_println!("Error: min weight cannot be greater than max weight."); -} - -#[renderer] -fn render_fruit_eat(prev: ParsedEatFruit) { - let weight_str = match prev.weight_range { - (min, max) if min == 0 && max > 0 => { - format!("up to {}.", max) - } - (min, max) if min > 0 && max == 0 => { - format!("at least {}.", min) - } - (min, max) if min > 0 && max > 0 && min != max => { - format!("between {} and {}.", min, max) - } - (min, max) if min > 0 && max > 0 && min == max => { - format!("exactly {}.", min) - } - _ => "unknown.".to_string(), - }; - - let fruit_type = if prev.count > 1 { - format!("{}s", prev.fruit_type.enum_info().0) - } else { - prev.fruit_type.enum_info().0.to_string() - }; - - r_println!( - "I ate {} {}, each weighing {}", - prev.count, - fruit_type, - weight_str - ); -} - -gen_program!(); -``` - -Now compile the program and run it: - -```bash -cargo install --path ./ -``` - -Running results: - -```bash -~> fruit eat Apple --min-weight 20 -I ate 1 Apple, each weighing exactly 20. - -~> fruit eat Apple --min-weight 20 --count 10 -I ate 10 Apples, each weighing exactly 20. - -~> fruit eat Apple --min-weight 10 --max-weight 20 -I ate 1 Apple, each weighing between 10 and 20. - -~> fruit eat Apple --min-weight 20 --max-weight 10 -Error: min weight cannot be greater than max weight. - -~> fruit eat --count 5 -I ate 5 Bananas, each weighing unknown. -``` diff --git a/docs/pages/3-features/2-general-renderer.md b/docs/pages/3-features/2-general-renderer.md deleted file mode 100644 index c3b81a2..0000000 --- a/docs/pages/3-features/2-general-renderer.md +++ /dev/null @@ -1,75 +0,0 @@ -<h1 align="center">General Renderer</h1> -<p align="center"> - Mingling's Features -</p> - ---- - -## Enable Feature - -`general_renderer` is a feature provided by **Mingling**. You can enable it in the following way: - -```toml -[dependencies] -mingling = { - version = "...", - features = ["general_renderer"] -} -``` - -## Setup - -`general_renderer` requires you to implement the `serde::Serialize` trait for **all** structs, so your project needs to include `serde` - -```toml -[dependencies] -serde = { - version = "1", - features = ["derive"] -} -``` - -For types wrapped with the `pack!` macro, `serde::Serialize` will be automatically implemented - -```rust -pack!(YourInfo = ()); // Auto derive `serde::Serialize` -``` - -For types using the derive macro `Groupped`, you need to manually implement `serde::Serialize` - -```rust -#[derive(Default, Groupped, Serialize)] -struct YourInfo { - name: String, - age: i32, -} -``` - -> [!Tip] -> If there are types that do not implement `serde::Serialize`, compilation will fail. - -## Import GeneralRendererSetup - -`general_renderer` provides a Setup type called `GeneralRendererSetup`. - -After importing it into your program, -user inputs like `--json`, `--yaml`, `--toml`, `--ron`, `--json-pretty`, and `--ron-pretty` will be automatically recognized. - -During the **rendering phase**, instead of the **default renderer**, the serialized content will be displayed to the terminal. - -```rust -fn main() { - let mut program = ThisProgram::new(); - - // Add General Renderer - program.with_setup(GeneralRendererSetup); - - // Add Dispatchers - program.with_dispatchers(( - // Your dispatchers - )); - - // Execute - program.exec(); -} -``` diff --git a/docs/pages/3-features/3-comp.md b/docs/pages/3-features/3-comp.md deleted file mode 100644 index 259e174..0000000 --- a/docs/pages/3-features/3-comp.md +++ /dev/null @@ -1,118 +0,0 @@ -<h1 align="center">Completion</h1> -<p align="center"> - Mingling's Features -</p> - ---- - -## Enable Feature - -`comp` is the command-line completion feature provided by **Mingling**. Its approach is not static completion but rather dynamic completion by invoking your program itself. - -Enable this feature as follows: - -```toml -[dependencies] -mingling = { - version = "...", - features = ["comp"] -} -``` - -## Setup - -Once `comp` is enabled, `gen_program!` will automatically generate a `CompletionDispatcher`, which is a command with the node `__comp`: the completion script will call this subcommand. - -Add this [Dispatcher](pages/2-basic/3-dispatcher) to your [Program](pages/2-basic/1-program): - -```rust -fn main() { - let mut program = ThisProgram::new(); - program.with_dispatcher(CompletionDispatcher); - program.exec(); -} -``` - -## Usage - -You can use the `completion!` macro to bind completion logic to your command entry point. The syntax is as follows: - -```rust -// Define Dispatcher -dispatcher!("test-comp", - TestCompletionCommand => TestCompletionEntry -); - -// Establish completion logic, bound to `TestCompletionEntry` -#[completion(TestCompletionEntry)] -fn comp_test_comp_cmd(_ctx: &ShellContext) -> Suggest { - suggest!() -} -``` - -You can obtain the context passed by the shell via `ShellContext` and return the generated suggestions: - -```rust -#[completion(TestCompletionEntry)] -fn comp_test_comp_cmd(ctx: &ShellContext) -> Suggest { - if ctx.current_word.starts_with("-") { - // Comp flags - return suggest!( - "--name": "Names", - "--age": "Age" - ); - } - - if ctx.previous_word == "--name" { - return suggest!("Bob", "Alice"); // Comp names - } - - if ctx.previous_word == "--age" { - return suggest!(); // If typing age, suggest nothing - } - - suggest!() // Comp nothing -} -``` - -> 🎬 Logic -> -> When the user inputs `bin test-<TAB>`, it completes to `bin test-comp`. -> -> When the user inputs `bin test-comp -<TAB>`, it suggests `--age` / `--name`. -> -> When the user inputs `bin test-comp --name <TAB>`, it suggests `Bob` / `Alice`. -> -> In other cases, no suggestions are generated. - -## Generate Completion Script - -Any shell requires registering a relevant completion script to enable your command's completion capability. However, **Mingling** provides a related build script: - -Please add the following to `build-dependencies` in your `Cargo.toml`: - -```toml -[build-dependencies] -mingling = { version = "...", features = ["comp"] } -``` - -Next, call the following logic in your project's `build.rs`: - -```rust -use mingling::build::build_comp_scripts; - -fn main() { - // Generate completion scripts for the current program - // build_comp_scripts().unwrap(); - - // Or specify a specific name - build_comp_scripts("your_cmd").unwrap(); -} -``` - -`build_comp_scripts` will generate the corresponding completion scripts based on your platform and output them to the `target` directory. - -> [!Note] -> The completion script does not contain the actual completion logic; -> -> it is just a thin invocation layer. diff --git a/docs/pages/3-features/4-async.md b/docs/pages/3-features/4-async.md deleted file mode 100644 index 08cbb9a..0000000 --- a/docs/pages/3-features/4-async.md +++ /dev/null @@ -1,6 +0,0 @@ -<h1 align="center">Async</h1> -<p align="center"> - Mingling's Features -</p> - ---- diff --git a/docs/pages/3-features/5-clap-parser.md b/docs/pages/3-features/5-clap-parser.md deleted file mode 100644 index 5cb68e6..0000000 --- a/docs/pages/3-features/5-clap-parser.md +++ /dev/null @@ -1,6 +0,0 @@ -<h1 align="center">Clap Parser</h1> -<p align="center"> - Mingling's Features -</p> - ---- |
