diff options
Diffstat (limited to 'docs/pages')
| -rw-r--r-- | docs/pages/10-help.md | 18 | ||||
| -rw-r--r-- | docs/pages/11-resource-system.md | 12 | ||||
| -rw-r--r-- | docs/pages/13-hook.md | 2 | ||||
| -rw-r--r-- | docs/pages/14-testing.md | 9 | ||||
| -rw-r--r-- | docs/pages/4-render-result.md | 81 | ||||
| -rw-r--r-- | docs/pages/5-multiple-commands.md | 16 | ||||
| -rw-r--r-- | docs/pages/6-argument-parse-picker.md | 38 | ||||
| -rw-r--r-- | docs/pages/7-argument-parse-clap.md | 23 | ||||
| -rw-r--r-- | docs/pages/9-error-handling.md | 24 | ||||
| -rw-r--r-- | docs/pages/advanced/2-structural-renderer.md | 12 | ||||
| -rw-r--r-- | docs/pages/other/naming_rule.md | 12 |
11 files changed, 151 insertions, 96 deletions
diff --git a/docs/pages/10-help.md b/docs/pages/10-help.md index 2f3b74f..7ef1f26 100644 --- a/docs/pages/10-help.md +++ b/docs/pages/10-help.md @@ -15,9 +15,11 @@ Write a help function directly for an Entry: @@@use mingling::macros::help; @@@dispatcher!("greet", CMDGreet => EntryGreet); #[help] -fn help_greet(_entry: EntryGreet) { - r_println!("Usage: greet [name]"); - r_println!("Say hello to someone."); +fn help_greet(entry: EntryGreet) -> RenderResult { + let mut r = RenderResult::new(); + writeln!(r, "Usage: greet [name]").ok(); + writeln!(r, "Say hello to someone.").ok(); + r } ``` @@ -32,10 +34,12 @@ You can also write help for `ErrorDispatcherNotFound` as the "root help": @@@use mingling::macros::help; // Triggered when user passes --help directly #[help] -fn help_root(entry: ErrorDispatcherNotFound) { - r_println!("Usage: my-cli <command>"); - r_println!("Commands:"); - r_println!(" greet Say hello"); +fn help_root(entry: ErrorDispatcherNotFound) -> RenderResult { + let mut r = RenderResult::new(); + writeln!(r, "Usage: my-cli <command>").ok(); + writeln!(r, "Commands:").ok(); + writeln!(r, " greet Say hello").ok(); + r } ``` diff --git a/docs/pages/11-resource-system.md b/docs/pages/11-resource-system.md index b54938a..bc2197f 100644 --- a/docs/pages/11-resource-system.md +++ b/docs/pages/11-resource-system.md @@ -38,8 +38,10 @@ fn handle_pwd(_args: EntryPrintWorkingDir, cwd: &ResCurrentDir) -> Next { } #[renderer] -fn render_path(result: ResultPath) { - r_println!("{}", *result); +fn render_path(result: ResultPath) -> RenderResult { + let mut r = RenderResult::new(); + writeln!(r, "{}", *result).ok(); + r } ``` @@ -59,8 +61,10 @@ fn handle_visit(_args: EntryVisit, counter: &mut ResVisitCount) -> Next { } #[renderer] -fn render_done(_done: ResultDone, counter: &ResVisitCount) { - r_println!("visit count is : {}", counter.0); +fn render_done(_done: ResultDone, counter: &ResVisitCount) -> RenderResult { + let mut r = RenderResult::new(); + writeln!(r, "visit count is : {}", counter.0).ok(); + r } ``` diff --git a/docs/pages/13-hook.md b/docs/pages/13-hook.md index 0c5e460..26f8712 100644 --- a/docs/pages/13-hook.md +++ b/docs/pages/13-hook.md @@ -60,7 +60,7 @@ Each hook callback receives a corresponding `Hook*Info` struct containing contex @@@#[chain] fn handle_greet(args: EntryGreet) -> Next { @@@ ResultName::new(args.inner.first().cloned().unwrap_or_default()).to_render() @@@} -@@@#[renderer] fn render_name(r: ResultName) { r_println!("Hello, {}!", *r); } +@@@#[renderer] fn render_name(r: ResultName) -> RenderResult { RenderResult::new() } fn main() { let mut program = ThisProgram::new(); diff --git a/docs/pages/14-testing.md b/docs/pages/14-testing.md index 789da96..fa196b0 100644 --- a/docs/pages/14-testing.md +++ b/docs/pages/14-testing.md @@ -13,16 +13,17 @@ Renderer is the easiest to test — call the function, assert the result: ```rust @@@pack!(ResultName = String); -// Returns String instead of () #[renderer] -fn render_name(name: ResultName) -> String { - r_println!("Hello, {}!", *name); +fn render_greet(result: ResultName) -> RenderResult { + let mut r = RenderResult::new(); + writeln!(r, "Hello, {}!", *result).ok(); + r } #[test] fn test_render_name() { let result = render_name(ResultName::new("Alice".to_string())); - assert_eq!(result, "Hello, Alice!\n"); + assert_eq!(result.to_string().as_str(), "Hello, Alice!\n"); } ``` diff --git a/docs/pages/4-render-result.md b/docs/pages/4-render-result.md index 5093a52..b1365b8 100644 --- a/docs/pages/4-render-result.md +++ b/docs/pages/4-render-result.md @@ -1,47 +1,50 @@ <h1 align="center">Rendering Results</h1> <p align="center"> - Use the <code>#[renderer]</code> macro to declare a renderer and output results + Declare a renderer using the <code>#[renderer]</code> macro to output results. </p> -Now that we've created a Dispatcher and a Chain, and produced a Result type via `pack!`, there's one final step: **presenting the result to the user**. +Now we've created a Dispatcher and a Chain, and produced a Result type via `pack!`. The final step: **present the result to the user**. ## The `#[renderer]` Macro Similar to `#[chain]`, `#[renderer]` marks an output function: ```rust +use std::io::Write; + pack!(ResultName = String); #[renderer] -fn render_name(name: ResultName) { - r_println!("Hello, {}!", *name); +fn render_name(name: ResultName) -> RenderResult { + let mut result = RenderResult::new(); + writeln!(result, "Hello, {}!", *name).ok(); + result } ``` -A Renderer takes the result produced by a Chain and outputs it using `r_println!`. What's the difference between `r_println!` and the usual `println!`? - -## The `r_println!` and `r_print!` Macros +A Renderer takes the result produced by a Chain and returns a `RenderResult`. Inside the function, create a `RenderResult`, write content using `write!` / `writeln!` (from [`std::io::Write`](https://doc.rust-lang.org/std/io/trait.Write.html)), and return it. -`r_println!` and `r_print!` are printing macros provided by Mingling. They write content into a `RenderResult` instead of printing directly to the terminal. This offers several benefits: +## The `RenderResult` Type -1. **RenderResult holds an exit code** — you can make the program exit with a specific code -2. **Easier testing** — you can capture rendered output and make assertions -3. **Post-processing** — you can capture results and apply uniform text post-processing +`RenderResult` is a buffer type that holds rendered text and an exit code. Instead of writing directly to the terminal, it writes content into an internal buffer. This approach gives us: -> [!TIP] -> For simple printing, you can think of it as a drop-in replacement for `println!`. Using `r_println!` instead of `println!` is a safe choice. +1. **Exit code support**—you can set the program to exit with a specific exit code +2. **Testability**—rendered output can be captured and asserted against +3. **Post-processing**—the result can be captured and further processed uniformly ## A Complete Runnable Program -Putting the content of all three tutorials together, here's your first complete Mingling program: +Putting all three tutorials together, here's your first complete Mingling program: ```rust -// 1. Declare a command with Dispatcher +use std::io::Write; + +// 1. Declare commands with a Dispatcher dispatcher!("greet", CMDGreet => EntryGreet); // 2. Declare result data with pack! pack!(ResultName = String); -// 3. Handle logic with Chain +// 3. Handle logic with a Chain #[chain] fn handle_greet(args: EntryGreet) -> Next { let name = args.inner @@ -51,10 +54,12 @@ fn handle_greet(args: EntryGreet) -> Next { ResultName::new(name) } -// 4. Output results with Renderer +// 4. Output results with a Renderer #[renderer] -fn render_name(name: ResultName) { - r_println!("Hello, {}!", *name); +fn render_name(name: ResultName) -> RenderResult { + let mut result = RenderResult::new(); + writeln!(result, "Hello, {}!", *name).ok(); + result } // 5. Assemble and run the program in main @@ -64,7 +69,7 @@ fn main() { program.exec_and_exit(); } -// 6. Generate the complete program with gen_program! +// 6. Use gen_program! to generate the full program gen_program!(); ``` @@ -88,7 +93,7 @@ Try without arguments: Hello, World! ``` -Try an unknown command: +Try a non-existent command: ```bash cargo run -- great @@ -98,22 +103,26 @@ cargo run -- great # No output! ``` -## Add a Fallback +## Adding a Fallback -`gen_program!()` auto-generates an `ErrorDispatcherNotFound` type that wraps `Vec<String>` — it holds the user's unmatched input. You just need to write a Renderer for it: +`gen_program!()` auto-generates an `ErrorDispatcherNotFound` type wrapping `Vec<String>`—it holds the user input that didn't match any command. You just need to write a Renderer for it: ```rust +use std::io::Write; + #[renderer] -fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) { +fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) -> RenderResult { + let mut result = RenderResult::new(); if err.inner.is_empty() { - r_println!("Unknown command"); + writeln!(result, "Unknown command").ok(); } else { - r_println!("Command not found: \"{}\"", err.inner.join(" ")); + writeln!(result, "Command not found: \"{}\"", err.inner.join(" ")).ok(); } + result } ``` -With that added, try the unknown command again: +With that added, try the non-existent command again: ```bash cargo run -- great @@ -125,17 +134,17 @@ Command not found: "great" ## Congratulations -You've completed your first full Mingling program! Here's a recap of what you've learned: +You've completed your first full Mingling program! Let's recap what you've learned: -| Concept | Macro/Function | In a Nutshell | -| --------------- | ---------------- | ------------------------------------- | -| Declare command | `dispatcher!` | Tell the program what users can input | -| Handle logic | `#[chain]` | What to do with the arguments | -| Output results | `#[renderer]` | How to present results to users | -| Type wrapper | `pack!` | Give your data a meaningful name | -| Program entry | `gen_program!()` | Auto-generate the pipeline wiring | +| Concept | Macro / Function | One-liner | +| -------------- | ---------------- | --------------------------------------- | +| Declare cmds | `dispatcher!` | Tell the program what the user can type | +| Handle logic | `#[chain]` | What to do when args are received | +| Output results | `#[renderer]` | How to present results to the user | +| Type wrapping | `pack!` | Give your data a meaningful name | +| Program entry | `gen_program!()` | Auto-generate the pipeline wiring | -In real projects you'll also use advanced features like resource injection, hooks, autocompletion, REPL, etc., but the core skeleton stays the same: **Dispatcher → Chain → Renderer**. +In real projects you'll also use advanced features like resource injection, hooks, completions, REPL, etc., but the core skeleton stays the same: **Dispatcher → Chain → Renderer**. <p align="center" style="font-size: 0.85em; color: gray;"> Written by @Weicao-CatilGrass diff --git a/docs/pages/5-multiple-commands.md b/docs/pages/5-multiple-commands.md index 9ad7ef4..a56f7b0 100644 --- a/docs/pages/5-multiple-commands.md +++ b/docs/pages/5-multiple-commands.md @@ -30,13 +30,17 @@ fn handle_add(args: EntryAdd) -> Next { } #[renderer] -fn render_greet(result: ResultGreeting) { - r_println!("Hello, {}!", *result); +fn render_greet(result: ResultGreeting) -> RenderResult { + let mut r = RenderResult::new(); + writeln!(r, "Hello, {}!", *result).ok(); + r } #[renderer] -fn render_sum(result: ResultSum) { - r_println!("Sum: {}", *result); +fn render_sum(result: ResultSum) -> RenderResult { + let mut r = RenderResult::new(); + writeln!(r, "Sum: {}", *result).ok(); + r } fn main() { @@ -67,9 +71,9 @@ Notice `with_dispatchers`? When you need to register multiple dispatchers, just @@@pack!(ResultGreeting = String); @@@pack!(ResultSum = i32); @@@#[chain] fn handle_greet(_args: EntryGreet) -> Next { ResultGreeting::new("ok".into()) } -@@@#[renderer] fn render_greet(_greeting: ResultGreeting) { r_println!("hi"); } +@@@#[renderer] fn render_greet(_greeting: ResultGreeting) -> RenderResult { RenderResult::new() } @@@#[chain] fn handle_add(_args: EntryAdd) -> Next { ResultSum::new(0) } -@@@#[renderer] fn render_sum(_sum: ResultSum) { r_println!("sum"); } +@@@#[renderer] fn render_sum(_sum: ResultSum) -> RenderResult { RenderResult::new() } fn main() { let mut program = ThisProgram::new(); program.with_dispatchers((CMDGreet, CMDAdd)); diff --git a/docs/pages/6-argument-parse-picker.md b/docs/pages/6-argument-parse-picker.md index 7e91af8..c80c25c 100644 --- a/docs/pages/6-argument-parse-picker.md +++ b/docs/pages/6-argument-parse-picker.md @@ -156,13 +156,17 @@ fn handle_greet_entry(prev: EntryGreet) -> Next { } #[renderer] -fn render_no_name(_prev: ErrorNoName) { - r_println!("Error: No name provided."); +fn render_no_name(_prev: ErrorNoName) -> RenderResult { + let mut r = RenderResult::new(); + writeln!(r, "Error: No name provided.").ok(); + r } #[renderer] -fn render_name(prev: ResultName) { - r_println!("Hello, {}!", *prev); +fn render_name(prev: ResultName) -> RenderResult { + let mut r = RenderResult::new(); + writeln!(r, "Hello, {}!", *prev).ok(); + r } ``` @@ -251,14 +255,18 @@ fn handle_greet_entry(prev: EntryGreet) -> Next { } #[renderer] -fn render_name_too_long(prev: ErrorNameTooLong) { +fn render_name_too_long(prev: ErrorNameTooLong) -> RenderResult { + let mut r = RenderResult::new(); let len = *prev; - r_println!("Error: name too long (length: {} > 32)", len); + writeln!(r, "Error: name too long (length: {} > 32)", len).ok(); + r } #[renderer] -fn render_name(prev: ResultName) { - r_println!("Hello, {}!", *prev); +fn render_name(prev: ResultName) -> RenderResult { + let mut r = RenderResult::new(); + writeln!(r, "Hello, {}!", *prev).ok(); + r } ``` @@ -315,7 +323,7 @@ Implement the `Pickable` trait to make your type parseable by `Picker` — this // Features: ["parser"] @@@use mingling::parser::{Pickable, Argument}; @@@use mingling::Flag; -#[derive(Default)] +#[derive(Default, Clone)] pub struct Address { ip: String, port: u16, @@ -341,9 +349,11 @@ fn handle_connect_entry(prev: EntryConnect) -> Next { } #[renderer] -fn render_connected(prev: ResultConnected) { +fn render_connected(prev: ResultConnected) -> RenderResult { + let mut r = RenderResult::new(); let addr = prev.inner; - r_println!("Connected: IP: {} PORT: {}", addr.ip, addr.port); + writeln!(r, "Connected: IP: {} PORT: {}", addr.ip, addr.port).ok(); + r } ``` @@ -381,8 +391,10 @@ fn handle_eat_entry(prev: EntryEat) -> Next { } #[renderer] -fn render_fruit(prev: ResultFruit) { - r_println!("Picked fruit: {:?}", *prev); +fn render_fruit(prev: ResultFruit) -> RenderResult { + let mut r = RenderResult::new(); + writeln!(r, "Picked fruit: {:?}", *prev).ok(); + r } ``` diff --git a/docs/pages/7-argument-parse-clap.md b/docs/pages/7-argument-parse-clap.md index e912c38..b11e00e 100644 --- a/docs/pages/7-argument-parse-clap.md +++ b/docs/pages/7-argument-parse-clap.md @@ -35,18 +35,22 @@ pub struct EntryGreet { } #[renderer] -fn render_greet(greet: EntryGreet) { +fn render_greet(greet: EntryGreet) -> RenderResult { + let mut r = RenderResult::new(); let count = greet.repeat.max(0) as usize; - r_print!("Hello, "); + write!(r, "Hello, ").ok(); for _ in 0..count { - r_print!("{} ", greet.name); + write!(r, "{} ", greet.name).ok(); } - r_println!("!"); + writeln!(r, "!").ok(); + r } #[renderer] -fn render_greet_parse_failed(err: ErrorGreetParsed) { - r_println!("{}", *err); +fn render_greet_parse_failed(err: ErrorGreetParsed) -> RenderResult { + let mut r = RenderResult::new(); + writeln!(r, "{}", *err).ok(); + r } ``` @@ -66,10 +70,11 @@ If you need `--help` support, register `BasicProgramSetup` in main and set the c @@@ name: String, @@@} @@@#[renderer] -@@@fn render_greet(greet: EntryGreet) { -@@@ r_println!("Hello, {}!", greet.name); +@@@fn render_greet(greet: EntryGreet) -> RenderResult { +@@@ let mut r = RenderResult::new(); +@@@ write!(r, "Hello, {}!", greet.name).ok(); +@@@ r @@@} -@@@ fn main() { let mut program = ThisProgram::new(); program.with_setup(BasicProgramSetup); diff --git a/docs/pages/9-error-handling.md b/docs/pages/9-error-handling.md index ec1f422..3e004f2 100644 --- a/docs/pages/9-error-handling.md +++ b/docs/pages/9-error-handling.md @@ -44,13 +44,17 @@ Then write separate Renderers: @@@#[chain] fn handle_greet(args: EntryGreet) -> Next { ResultGreeting::new(args.inner.first().cloned().unwrap_or_default()).to_render() } #[renderer] -fn render_greeting(result: ResultGreeting) { - r_println!("Hello, {}!", *result); +fn render_greeting(result: ResultGreeting) -> RenderResult { + let mut r = RenderResult::new(); + writeln!(r, "Hello, {}!", *result).ok(); + r } #[renderer] -fn render_error_name_empty(err: ErrorNameEmpty) { - r_println!("Error: {}", *err); +fn render_error_name_empty(err: ErrorNameEmpty) -> RenderResult { + let mut r = RenderResult::new(); + writeln!(r, "Error: {}", *err).ok(); + r } ``` @@ -75,13 +79,17 @@ fn handle_greet(args: EntryGreet) -> Next { } #[renderer] -fn render_greeting(result: ResultGreeting) { - r_println!("Hello, {}!", *result); +fn render_greeting(result: ResultGreeting) -> RenderResult { + let mut r = RenderResult::new(); + writeln!(r, "Hello, {}!", *result).ok(); + r } #[renderer] -fn render_error_name_empty(err: ErrorNameEmpty) { - r_println!("Error: {}", *err); +fn render_error_name_empty(err: ErrorNameEmpty) -> RenderResult { + let mut r = RenderResult::new(); + writeln!(r, "Error: {}", *err).ok(); + r } fn main() { diff --git a/docs/pages/advanced/2-structural-renderer.md b/docs/pages/advanced/2-structural-renderer.md index 09c86d1..f31f758 100644 --- a/docs/pages/advanced/2-structural-renderer.md +++ b/docs/pages/advanced/2-structural-renderer.md @@ -41,8 +41,10 @@ fn handle_render(args: EntryRender) -> Next { } #[renderer] -fn render_info(r: ResultInfo) { - r_println!("{:?}", *r); +fn render_info(r: ResultInfo) -> RenderResult { + let mut result = RenderResult::new(); + writeln!(result, "{:?}", *r).ok(); + result } ``` @@ -86,8 +88,10 @@ fn handle_render(args: EntryRender) -> Next { } #[renderer] -fn render_info(info: Info) { - r_println!("{} is {} years old", info.name, info.age); +fn render_info(info: Info) -> RenderResult { + let mut r = RenderResult::new(); + writeln!(r, "{} is {} years old", info.name, info.age).ok(); + r } @@@ @@@fn main() { diff --git a/docs/pages/other/naming_rule.md b/docs/pages/other/naming_rule.md index 21d7947..2ede61f 100644 --- a/docs/pages/other/naming_rule.md +++ b/docs/pages/other/naming_rule.md @@ -193,14 +193,18 @@ fn handle_state_operation_remotes(state: StateOperationRemotes, db: &ResDatabase // Result rendering #[renderer] -fn render_remote_added(result: ResultRemoteAdded) { - r_println!("Remote added: {}", result.inner); +fn render_remote_added(result: ResultRemoteAdded) -> RenderResult { + let mut r = RenderResult::new(); + writeln!(r, "Remote added: {}", result.inner).ok(); + r } // Error rendering #[renderer] -fn render_error_repository_not_found(err: ErrorRepositoryNotFound) { - r_println!("Error: remote '{}' not found", err.inner); +fn render_error_repository_not_found(err: ErrorRepositoryNotFound) -> RenderResult { + let mut r = RenderResult::new(); + writeln!(r, "Error: remote '{}' not found", err.inner).ok(); + r } ``` |
