diff options
Diffstat (limited to 'docs/pages/3-features')
| -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 |
5 files changed, 0 insertions, 581 deletions
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> - ---- |
