diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-07-11 15:15:12 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-07-11 15:21:40 +0800 |
| commit | 95815041fe2817b4b6bdba6b4f332c90320af7bb (patch) | |
| tree | 224f3479eca9e44caa247132ecab42f8825b167c | |
| parent | 356879cfd6149bdb235b4e02c0f351a4d2246202 (diff) | |
refactor(examples): migrate renderers to return RenderResult and add
std::io::Write import
Replace r_println!/r_print! macro usage across all example renderers
with
explicit RenderResult construction using std::io::Write, enabling more
flexible output handling and reducing reliance on macro-side effects.
27 files changed, 561 insertions, 280 deletions
diff --git a/examples/example-argument-parse/src/main.rs b/examples/example-argument-parse/src/main.rs index 4929b7c..59499a2 100644 --- a/examples/example-argument-parse/src/main.rs +++ b/examples/example-argument-parse/src/main.rs @@ -19,6 +19,7 @@ //! ``` use mingling::{macros::route, prelude::*}; +use std::io::Write; dispatcher!("transfer", CMDTransfer => EntryTransfer); dispatcher!("strict-transfer", CMDStrictTransfer => EntryStrictTransfer); @@ -71,20 +72,26 @@ fn handle_strict_transfer_parse(args: EntryStrictTransfer) -> Next { /// Renders the parsed transfer result (file/dir, size, name). #[renderer] -fn render_result_file(result: ResultFile) { +fn render_result_file(result: ResultFile) -> RenderResult { let (is_dir, size, name) = result.into(); - r_println!( + let mut result = RenderResult::new(); + writeln!( + result, "{}: {} ({})", if is_dir { "dir" } else { "file" }, name, size ) + .ok(); + result } /// Renders the error when no name is provided. #[renderer] -fn render_error_no_name_provided(_: ErrorNoNameProvided) { - r_println!("Error: name is not provided") +fn render_error_no_name_provided(_: ErrorNoNameProvided) -> RenderResult { + let mut result = RenderResult::new(); + writeln!(result, "Error: name is not provided").ok(); + result } gen_program!(); diff --git a/examples/example-async-support/src/main.rs b/examples/example-async-support/src/main.rs index 847bfea..ac85aaf 100644 --- a/examples/example-async-support/src/main.rs +++ b/examples/example-async-support/src/main.rs @@ -24,6 +24,7 @@ //! ``` use mingling::{hook::ProgramHook, prelude::*}; +use std::io::Write; #[tokio::main] async fn main() { @@ -55,8 +56,10 @@ pub async fn handle_download(args: EntryDownload) -> Next { /// Renders the downloaded file name. #[renderer] // But renderers cannot use the `async` keyword -pub fn render_downloaded(result: ResultDownloaded) { - r_println!("\"{}\" downloaded.", *result); +pub fn render_downloaded(result: ResultDownloaded) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "\"{}\" downloaded.", *result).ok(); + render_result } // --------- IMPORTANT --------- diff --git a/examples/example-basic/src/main.rs b/examples/example-basic/src/main.rs index 3d94a4b..7fc0bce 100644 --- a/examples/example-basic/src/main.rs +++ b/examples/example-basic/src/main.rs @@ -14,6 +14,7 @@ // Import commonly used Mingling modules use mingling::prelude::*; +use std::io::Write; // Define the `greet` subcommand // _____________________________ subcmd name, can be nested (e.g. "remote.add" "remote.rm") @@ -60,8 +61,10 @@ fn handle_greet(args: EntryGreet) -> Next { // Define renderer `render_name`, used to render `ResultName` /// Renders the greeting message with the provided name. #[renderer] -fn render_name(name: ResultName) { - r_println!("Hello, {}!", *name); +fn render_name(name: ResultName) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "Hello, {}!", *name).ok(); + render_result } // Note: This macro generates the program entry point. diff --git a/examples/example-clap-binding/src/main.rs b/examples/example-clap-binding/src/main.rs index 19d794b..d99f8d1 100644 --- a/examples/example-clap-binding/src/main.rs +++ b/examples/example-clap-binding/src/main.rs @@ -38,7 +38,8 @@ //! For more information, try '--help'. //! ``` -use mingling::{macros::dispatcher_clap, prelude::*, setup::BasicProgramSetup, Groupped}; +use mingling::{Groupped, macros::dispatcher_clap, prelude::*, setup::BasicProgramSetup}; +use std::io::Write; fn main() { let mut program = ThisProgram::new(); @@ -89,24 +90,29 @@ pub struct EntryGreet { /// Renders the greet output with optional repetition. #[renderer] -fn render_greet(greet: EntryGreet) { +fn render_greet(greet: EntryGreet) -> RenderResult { let name = greet.name; let count = greet.repeat.max(0) as usize; - r_print!("Hello, "); + let mut render_result = RenderResult::default(); + write!(render_result, "Hello, ").ok(); for i in 0..count { - r_print!("{name}"); + write!(render_result, "{name}").ok(); if i < count - 1 { - r_print!(", "); + write!(render_result, ", ").ok(); } } - r_println!("!"); + writeln!(render_result, "!").ok(); + render_result } /// Renders the error message when greet argument parsing fails. #[renderer] -fn render_greet_parse_failed(err: ErrorGreetParsed) { - r_println!("{}", *err); +// renderers can return a RenderResult instead of using r_println! +pub fn render_greet_parse_failed(err: ErrorGreetParsed) -> RenderResult { + let mut render_result = RenderResult::default(); + writeln!(render_result, "{}", *err).ok(); + render_result } gen_program!(); diff --git a/examples/example-combine-pathf-dispatch-tree/src/sub/mod.rs b/examples/example-combine-pathf-dispatch-tree/src/sub/mod.rs index e0b7743..5ab0ece 100644 --- a/examples/example-combine-pathf-dispatch-tree/src/sub/mod.rs +++ b/examples/example-combine-pathf-dispatch-tree/src/sub/mod.rs @@ -1,5 +1,6 @@ use crate::Next; -use mingling::{macros::r_println, prelude::*}; +use mingling::prelude::*; +use std::io::Write; dispatcher!("hello"); @@ -17,6 +18,8 @@ pub fn handle_my(args: EntryHello) -> Next { } #[renderer] -pub fn render_my(msg: ResultMessage) { - r_println!("Hello, {}!", *msg); +pub fn render_my(msg: ResultMessage) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "Hello, {}!", *msg).ok(); + render_result } diff --git a/examples/example-completion/src/main.rs b/examples/example-completion/src/main.rs index 0c64d03..0159807 100644 --- a/examples/example-completion/src/main.rs +++ b/examples/example-completion/src/main.rs @@ -45,6 +45,7 @@ //! ``` use mingling::{macros::suggest, prelude::*, ShellContext, Suggest}; +use std::io::Write; fn main() { let mut program = ThisProgram::new(); @@ -118,13 +119,15 @@ fn handle_greet(args: EntryGreet) -> Next { /// Renders the greeting with the result name and repeat count. #[renderer] -fn render_name(result: ResultName) { +fn render_name(result: ResultName) -> RenderResult { let (repeat, name) = result.inner; + let mut render_result = RenderResult::new(); let mut parts = Vec::with_capacity(repeat as usize); for _ in 0..repeat { parts.push(name.clone()); } - r_println!("Hello, {}!", parts.join(", ")); + writeln!(render_result, "Hello, {}!", parts.join(", ")).ok(); + render_result } gen_program!(); diff --git a/examples/example-custom-pickable/src/main.rs b/examples/example-custom-pickable/src/main.rs index 015cc62..d203815 100644 --- a/examples/example-custom-pickable/src/main.rs +++ b/examples/example-custom-pickable/src/main.rs @@ -15,6 +15,7 @@ //! ``` use mingling::{macros::route, parser::Pickable, prelude::*, Groupped}; +use std::io::Write; // Define types that can be recognized by Mingling // ________________________ `Pickable` trait needs to implement Default @@ -52,14 +53,18 @@ fn handle_connect(prev: EntryConnect) -> Next { /// Renders the connected address. #[renderer] -fn render_address(addr: Address) { - r_println!("Connected to \"{}\"", addr.to_string()); +pub fn render_address(addr: Address) -> RenderResult { + let mut render_result = RenderResult::new(); + write!(render_result, "Connected to \"{}\"", addr).ok(); + render_result } /// Renders the error message when address parsing fails. #[renderer] -fn render_error_parse_address_failed(_: ErrorParseAddressFailed) { - r_println!("Failed to parse address"); +pub fn render_error_parse_address_failed(_: ErrorParseAddressFailed) -> RenderResult { + let mut render_result = RenderResult::new(); + write!(render_result, "Failed to parse address").ok(); + render_result } gen_program!(); diff --git a/examples/example-dispatch-tree/src/main.rs b/examples/example-dispatch-tree/src/main.rs index e087b1e..9f76f15 100644 --- a/examples/example-dispatch-tree/src/main.rs +++ b/examples/example-dispatch-tree/src/main.rs @@ -21,6 +21,7 @@ //! use mingling::prelude::*; +use std::io::Write; // --------- IMPORTANT --------- // You have a large number of subcommands @@ -53,8 +54,10 @@ fn main() { /// Renders the confirmation message for the `cmd5` command. #[renderer] -fn render_cmd5(_: Entry5) { - r_println!("It's works!"); +fn render_cmd5(_: Entry5) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "It's works!").ok(); + render_result } gen_program!(); diff --git a/examples/example-enum-tag/src/main.rs b/examples/example-enum-tag/src/main.rs index ad9db82..01c7767 100644 --- a/examples/example-enum-tag/src/main.rs +++ b/examples/example-enum-tag/src/main.rs @@ -19,6 +19,7 @@ use mingling::{ macros::suggest_enum, parser::PickableEnum, prelude::*, EnumTag, Groupped, ShellContext, Suggest, }; +use std::io::Write; // Define the enum and derive the EnumTag trait // ________ adds metadata to the enum, enabling it to: @@ -83,10 +84,11 @@ fn handle_language_selection(args: EntryLanguageSelection) -> Next { /// Renders the selected programming language with its name and description. #[renderer] -fn render_programming_language(lang: ProgrammingLanguages) { - // You can use `enum_info()` to get the name and description of the current enum +pub fn render_programming_language(lang: ProgrammingLanguages) -> RenderResult { + let mut render_result = RenderResult::new(); let (name, desc) = lang.enum_info(); - r_println!("Selected: {} ({})", name, desc) + writeln!(render_result, "Selected: {} ({})", name, desc).ok(); + render_result } #[completion(EntryLanguageSelection)] diff --git a/examples/example-error-handling/src/main.rs b/examples/example-error-handling/src/main.rs index ae79b97..de9792d 100644 --- a/examples/example-error-handling/src/main.rs +++ b/examples/example-error-handling/src/main.rs @@ -21,6 +21,7 @@ //! ``` use mingling::prelude::*; +use std::io::Write; // In Mingling, instead of using ? to propagate errors upward, // errors are treated as branches that continue execution. @@ -61,36 +62,47 @@ fn handle_hello(args: EntryHello) -> Next { /// Renders a successful greeting with the given name. #[renderer] -fn render_result_name(name: ResultName) { - r_println!("Hello, {}", *name); +fn render_result_name(name: ResultName) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "Hello, {}", *name).ok(); + render_result } /// Renders the error when no name is provided. #[renderer] -fn render_error_no_name_provided(_: ErrorNoNameProvided) { - // Prompt when no name is provided - r_println!("No name provided"); +fn render_error_no_name_provided(_: ErrorNoNameProvided) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "No name provided").ok(); + render_result } /// Renders the error when the name is already taken. #[renderer] -fn render_error_name_not_available(_: ErrorNameNotAvailable) { - // Prompt when name is already taken - r_println!("Name not available"); +fn render_error_name_not_available(_: ErrorNameNotAvailable) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "Name not available").ok(); + render_result } /// Renders the error when the name exceeds the maximum length. #[renderer] -fn render_error_name_too_long(len: ErrorNameTooLong) { - // Prompt when name is too long, showing actual length - r_println!("Name too long: {} > 10", *len); +fn render_error_name_too_long(len: ErrorNameTooLong) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "Name too long: {} > 10", *len).ok(); + render_result } /// Renders the error when the dispatcher (subcommand) is not found. #[renderer] -fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) { - // Prompt when command is not found, showing the input command - r_println!("Command not found: \"{}\"", err.inner.join(" ")); +fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!( + render_result, + "Command not found: \"{}\"", + err.inner.join(" ") + ) + .ok(); + render_result } gen_program!(); diff --git a/examples/example-exitcode/src/main.rs b/examples/example-exitcode/src/main.rs index b0b7467..6b22eae 100644 --- a/examples/example-exitcode/src/main.rs +++ b/examples/example-exitcode/src/main.rs @@ -20,6 +20,7 @@ use mingling::{ res::ResExitCode, setup::{BasicProgramSetup, ExitCodeSetup}, }; +use std::io::Write; fn main() { let mut program = ThisProgram::new(); @@ -52,25 +53,32 @@ fn handle_hello(args: EntryHello) -> Next { /// Renders a successful greeting with the given name. #[renderer] -fn render_result_name(name: ResultName) { - r_println!("Hello, {}", *name); +fn render_result_name(name: ResultName) -> RenderResult { + let mut result = RenderResult::new(); + writeln!(result, "Hello, {}", *name).ok(); + result } #[help] -fn help_hello(_p: EntryHello, ec: &mut ResExitCode) { - r_println!("Usage: hello <NAME>"); +fn help_hello(_p: EntryHello, ec: &mut ResExitCode) -> RenderResult { + let mut result = RenderResult::new(); + writeln!(result, "Usage: hello <NAME>").ok(); ec.exit_code = 2; + result } // Define renderer, render error message _______________ Inject exit code resource // / /// Renders the error when no name is provided | #[renderer] // vvvvvvvvvvvvvvvv -fn render_error_no_name_provided(_: ErrorNoNameProvided, ec: &mut ResExitCode) { +fn render_error_no_name_provided(_: ErrorNoNameProvided, ec: &mut ResExitCode) -> RenderResult { ec.exit_code = 1; + let mut result = RenderResult::new(); + // Prompt when no name is provided - r_println!("No name provided (with exit code 1)"); + writeln!(result, "No name provided (with exit code 1)").ok(); + result } gen_program!(); diff --git a/examples/example-help/src/main.rs b/examples/example-help/src/main.rs index 9567c49..3282683 100644 --- a/examples/example-help/src/main.rs +++ b/examples/example-help/src/main.rs @@ -14,14 +14,17 @@ //! ``` use mingling::{macros::help, prelude::*, setup::BasicProgramSetup}; +use std::io::Write; dispatcher!("greet", CMDGreet => EntryGreet); // Define help _________ When `program.user_context.help` is `true` // / the command will not enter `#[chain]` / `#[renderer]` #[help] // vvvvvvvvvv but instead enter this `#[help]` function -fn help_greet(_prev: EntryGreet) { - r_println!("Usage: greet <NAME>"); +fn help_greet(_prev: EntryGreet) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "Usage: greet <NAME>").ok(); + render_result } fn main() { diff --git a/examples/example-hook/src/main.rs b/examples/example-hook/src/main.rs index d9a8fd1..41928ca 100644 --- a/examples/example-hook/src/main.rs +++ b/examples/example-hook/src/main.rs @@ -24,6 +24,7 @@ use mingling::{ hook::{ProgramControlUnit, ProgramHook}, prelude::*, }; +use std::io::Write; dispatcher!("greet", CMDGreet => EntryGreet); @@ -67,9 +68,12 @@ fn handle_greet(args: EntryGreet) -> Next { } /// Renders the greeting message with the provided name. +/// Renders the greeting message with the provided name. #[renderer] -fn render_name(name: ResultName) { - r_println!("Hello, {}!", *name); +fn render_name(name: ResultName) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "Hello, {}!", *name).ok(); + render_result } gen_program!(); diff --git a/examples/example-lazy-resources/src/main.rs b/examples/example-lazy-resources/src/main.rs index 3533ae2..aaafb98 100644 --- a/examples/example-lazy-resources/src/main.rs +++ b/examples/example-lazy-resources/src/main.rs @@ -22,6 +22,7 @@ //! ``` use std::collections::BTreeMap; +use std::io::Write; use mingling::{LazyInit, LazyRes, prelude::*}; @@ -73,20 +74,25 @@ fn main() { // | _____________________ Use LazyRes<ResLargeData> // | / instead of ResLargeData #[renderer] // vvvv vvvvvvvvvvvvvvvvvvvvv -fn render_entry_show(_args: EntryShow, res: &mut LazyRes<ResLargeData>) { +fn render_entry_show(_args: EntryShow, res: &mut LazyRes<ResLargeData>) -> RenderResult { + let mut render_result = RenderResult::new(); + // _______ Initialization happens here // / // vvvvvvv let res = res.get_ref(); for (key, value) in &res.data { - r_println!("{}: {}", key, value); + writeln!(render_result, "{}: {}", key, value).ok(); } + render_result } // When not using LazyRes<ResLargeData>, it will not be initialized #[renderer] -fn render_entry_none(_args: EntryNone) { - r_println!("None"); +fn render_entry_none(_args: EntryNone) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "None").ok(); + render_result } gen_program!(); diff --git a/examples/example-outside-type/src/main.rs b/examples/example-outside-type/src/main.rs index 7cf4d40..925878a 100644 --- a/examples/example-outside-type/src/main.rs +++ b/examples/example-outside-type/src/main.rs @@ -19,6 +19,7 @@ //! ``` use mingling::{macros::group, prelude::*}; +use std::io::Write; use std::{io::ErrorKind::Other, num::ParseIntError}; dispatcher!("parse"); @@ -61,8 +62,10 @@ fn parse_number(args: EntryParse) -> Next { // _____________ Using std::num::ParseIntError as a chain input // / #[renderer] // vvvvvvvvvvvv -fn render_number(num: ParsedNumber) { - r_println!("Parsed number: {}", *num); +fn render_number(num: ParsedNumber) -> RenderResult { + let mut render_result = RenderResult::new(); + write!(render_result, "Parsed number: {}", *num).ok(); + render_result } /// Renderer for parse errors — using the outside `ParseIntError` type. @@ -70,16 +73,20 @@ fn render_number(num: ParsedNumber) { /// The `ParseIntError` type is registered via `group!` above, so it implements /// `Groupped<ThisProgram>` and can be used directly in a `#[renderer]` function. #[renderer] -fn render_parse_error(err: ParseIntError) { - r_println!("Parse error: {}", err); +fn render_parse_error(err: ParseIntError) -> RenderResult { + let mut render_result = RenderResult::new(); + write!(render_result, "Parse error: {}", err).ok(); + render_result } /// Renderer for IO errors — using `std::io::Error` registered as `ErrorIo`. // ________ Must use alias `ErrorIo` here, not bare `std::io::Error` // / #[renderer] // vvvvvvv -fn render_error_io(err: ErrorIo) { - r_println!("IO_ERROR: {}", err.to_string()); +fn render_error_io(err: ErrorIo) -> RenderResult { + let mut render_result = RenderResult::new(); + write!(render_result, "IO_ERROR: {}", err).ok(); + render_result } fn main() { diff --git a/examples/example-pack-err/src/main.rs b/examples/example-pack-err/src/main.rs index 8716333..5bf5066 100644 --- a/examples/example-pack-err/src/main.rs +++ b/examples/example-pack-err/src/main.rs @@ -28,6 +28,7 @@ use mingling::prelude::*; use mingling::setup::StructuralRendererSetup; +use std::io::Write; use std::path::PathBuf; dispatcher!("find", CMDFind => EntryFind); @@ -100,32 +101,42 @@ fn handle_find_structural(args: EntryFindStructural) -> Next { /// Renders the successful result with the found directory path. #[renderer] -fn render_result_path(path: ResultPath) { - r_println!("Found directory: {}", path.display()); +fn render_result_path(path: ResultPath) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "Found directory: {}", path.display()).ok(); + render_result } /// Renders the error when no search path is provided. #[renderer] -fn render_error_not_found(_: ErrorNotFound) { - r_println!("Search path not provided"); +fn render_error_not_found(_: ErrorNotFound) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "Search path not provided").ok(); + render_result } /// Renders the error when the given path is not a directory. #[renderer] -fn render_error_not_dir(err: ErrorNotDir) { - r_println!("Not a directory: {}", err.info.display()); +fn render_error_not_dir(err: ErrorNotDir) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "Not a directory: {}", err.info.display()).ok(); + render_result } /// Renders the structural error when no search path is provided. #[renderer] -fn render_error_not_found_structural(_: ErrorNotFoundStructural) { - r_println!("Search path not provided"); +fn render_error_not_found_structural(_: ErrorNotFoundStructural) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "Search path not provided").ok(); + render_result } /// Renders the structural error when the given path is not a directory. #[renderer] -fn render_error_not_dir_structural(err: ErrorNotDirStructural) { - r_println!("Not a directory: {}", err.info.display()); +fn render_error_not_dir_structural(err: ErrorNotDirStructural) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "Not a directory: {}", err.info.display()).ok(); + render_result } gen_program!(); diff --git a/examples/example-panic-unwind/src/main.rs b/examples/example-panic-unwind/src/main.rs index ed032c5..a829158 100644 --- a/examples/example-panic-unwind/src/main.rs +++ b/examples/example-panic-unwind/src/main.rs @@ -16,6 +16,7 @@ //! ``` use mingling::{hook::ProgramHook, prelude::*}; +use std::io::Write; dispatcher!("panic", CMDPanic => EntryPanic); pack!(NotPanic = ()); @@ -51,9 +52,12 @@ fn handle_panic(prev: EntryPanic) -> Next { } /// Renders the message when no panic occurs. +/// Renders the message when no panic occurs. #[renderer] -fn render(_: NotPanic) { - r_println!("Program not panic"); +pub fn render(_: NotPanic) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "Program not panic").ok(); + render_result } gen_program!(); diff --git a/examples/example-pathfinder/src/sub/mod.rs b/examples/example-pathfinder/src/sub/mod.rs index ef10a75..6d15930 100644 --- a/examples/example-pathfinder/src/sub/mod.rs +++ b/examples/example-pathfinder/src/sub/mod.rs @@ -1,5 +1,6 @@ -use mingling::prelude::*; use crate::Next; +use mingling::prelude::*; +use std::io::Write; dispatcher!("greet", CMDGreet => EntryGreet); pack!(ResultName = String); @@ -15,7 +16,11 @@ pub fn handle_greet(args: EntryGreet) -> Next { name } +/// Renders the name. #[renderer] -pub fn render_name(name: ResultName) { - r_println!("Hello, {}!", *name); +// But renderers cannot use the `async` keyword +pub fn render_name(name: ResultName) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "Hello, {}!", *name).ok(); + render_result } diff --git a/examples/example-repl-basic/src/main.rs b/examples/example-repl-basic/src/main.rs index 8df8c22..d15319e 100644 --- a/examples/example-repl-basic/src/main.rs +++ b/examples/example-repl-basic/src/main.rs @@ -14,6 +14,7 @@ use mingling::{ setup::{BasicREPLOutputSetup, BasicREPLPromptSetup, BasicREPLReadlineSetup}, this, }; +use std::io::Write; use std::{env::current_dir, path::PathBuf}; // Resource to store the current directory @@ -136,10 +137,12 @@ fn handle_ls(_prev: EntryLs, current_dir: &ResCurrentDir) -> Next { /// Render ResultList data #[renderer] -fn render_list(list: ResultList) { +fn render_list(list: ResultList) -> RenderResult { + let mut render_result = RenderResult::new(); for item in list.inner { - r_println!("{}", item); + writeln!(render_result, "{}", item).ok(); } + render_result } // Handle exit command event @@ -161,15 +164,24 @@ fn handle_clear(_prev: EntryClear) { /// Handle path not found event #[renderer] -fn render_error_directory_not_exist(err: ErrorDirectoryNotExist) { - r_println!("Directory not found: {}", err.inner.display()) +fn render_error_directory_not_exist(err: ErrorDirectoryNotExist) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!( + render_result, + "Directory not found: {}", + err.inner.display() + ) + .ok(); + render_result } /// Handle dispatcher not found event /// Renders the error when a command is not found. #[renderer] -fn dispatcher_not_found(prev: ErrorDispatcherNotFound) { - r_println!("Command not found: \"{}\"", prev.join(", ")) +fn dispatcher_not_found(prev: ErrorDispatcherNotFound) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "Command not found: \"{}\"", prev.join(", ")).ok(); + render_result } gen_program!(); diff --git a/examples/example-resources/src/main.rs b/examples/example-resources/src/main.rs index 6c5bf84..b08ee26 100644 --- a/examples/example-resources/src/main.rs +++ b/examples/example-resources/src/main.rs @@ -14,9 +14,9 @@ //! Current directory: /home/alice/mingling/src //! ``` -use std::path::PathBuf; - use mingling::prelude::*; +use std::io::Write; +use std::path::PathBuf; // Create resource // ______________ Resource needs to @@ -58,8 +58,15 @@ fn render_modify_current(args: EntryModifyCurrent, current_dir: &mut ResCurrentD // / /// Renders the current directory path. | #[renderer] // vvvvvvvvvvvvvv -fn render_current(_: EntryCurrent, current_dir: &ResCurrentDir) { - r_println!("Current directory: {}", current_dir.current_dir.display()); +fn render_current(_: EntryCurrent, current_dir: &ResCurrentDir) -> RenderResult { + let mut render_result = RenderResult::new(); + write!( + render_result, + "Current directory: {}", + current_dir.current_dir.display() + ) + .ok(); + render_result } gen_program!(); diff --git a/examples/example-structural-renderer/src/main.rs b/examples/example-structural-renderer/src/main.rs index 21077e7..13c4c84 100644 --- a/examples/example-structural-renderer/src/main.rs +++ b/examples/example-structural-renderer/src/main.rs @@ -18,8 +18,9 @@ //! ``` use mingling::prelude::*; -use mingling::{parser::Picker, setup::StructuralRendererSetup, StructuralData, Groupped}; +use mingling::{Groupped, StructuralData, parser::Picker, setup::StructuralRendererSetup}; use serde::Serialize; +use std::io::Write; dispatcher!("render", CMDRender => EntryRender); @@ -64,8 +65,10 @@ fn parse_render(prev: EntryRender) -> Next { /// Implement default renderer for when structural_renderer is not specified #[renderer] -fn render_info(prev: Info) { - r_println!("{} is {} years old", prev.name, prev.age); +fn render_info(prev: Info) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "{} is {} years old", prev.name, prev.age).ok(); + render_result } gen_program!(); diff --git a/examples/example-unit-test/src/main.rs b/examples/example-unit-test/src/main.rs index b89c629..b85ff01 100644 --- a/examples/example-unit-test/src/main.rs +++ b/examples/example-unit-test/src/main.rs @@ -7,6 +7,7 @@ //! ``` use mingling::prelude::*; +use std::io::Write; #[cfg(test)] mod tests { @@ -41,25 +42,25 @@ mod tests { #[test] fn test_render_result_name() { let r = render_result_name(ResultName::new("Peter".into())); - assert_eq!(r, "Hello, Peter!\n") + assert_eq!(r.to_string().as_str(), "Hello, Peter!\n") } #[test] fn test_render_error_no_name_provided() { let r = render_error_no_name_provided(ErrorNoNameProvided::default()); - assert_eq!(r, "No name provided\n") + assert_eq!(r.to_string().as_str(), "No name provided\n") } #[test] fn test_render_error_name_not_available() { let r = render_error_name_not_available(ErrorNameNotAvailable::default()); - assert_eq!(r, "Name not available\n") + assert_eq!(r.to_string().as_str(), "Name not available\n") } #[test] fn test_render_error_name_too_long() { let r = render_error_name_too_long(ErrorNameTooLong::new(17)); - assert_eq!(r, "Name too long: 17 > 10\n") + assert_eq!(r.to_string().as_str(), "Name too long: 17 > 10\n") } // --------- IMPORTANT --------- } @@ -93,32 +94,47 @@ fn handle_hello(args: EntryHello) -> Next { /// Renders a successful greeting with the given name. #[renderer] -fn render_result_name(name: ResultName) -> String { - r_println!("Hello, {}!", *name); +fn render_result_name(name: ResultName) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "Hello, {}!", *name).ok(); + render_result } /// Renders the error when no name is provided. #[renderer] -fn render_error_no_name_provided(_: ErrorNoNameProvided) -> String { - r_println!("No name provided"); +fn render_error_no_name_provided(_: ErrorNoNameProvided) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "No name provided").ok(); + render_result } /// Renders the error when the name is already taken. #[renderer] -fn render_error_name_not_available(_: ErrorNameNotAvailable) -> String { - r_println!("Name not available"); +fn render_error_name_not_available(_: ErrorNameNotAvailable) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "Name not available").ok(); + render_result } /// Renders the error when the name exceeds the maximum length. #[renderer] -fn render_error_name_too_long(len: ErrorNameTooLong) -> String { - r_println!("Name too long: {} > 10", *len); +fn render_error_name_too_long(len: ErrorNameTooLong) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "Name too long: {} > 10", *len).ok(); + render_result } /// Renders the error when the dispatcher (subcommand) is not found. #[renderer] -fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) { - r_println!("Command not found: \"{}\"", err.inner.join(" ")); +fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!( + render_result, + "Command not found: \"{}\"", + err.inner.join(" ") + ) + .ok(); + render_result } gen_program!(); diff --git a/examples/full-todolist/src/help.rs b/examples/full-todolist/src/help.rs index ab33176..2f8228a 100644 --- a/examples/full-todolist/src/help.rs +++ b/examples/full-todolist/src/help.rs @@ -1,15 +1,16 @@ //! This module provides help information for the `todolist` command line program -use mingling::macros::{help, r_println}; - use crate::{EntryAdd, EntryClean, EntryComplete, EntryList, ErrorDispatcherNotFound}; +use mingling::{RenderResult, macros::help}; +use std::io::Write; +/// Shows the global help message. #[help] -pub fn help_global(_p: ErrorDispatcherNotFound) { - r_println!( - "{}", - r" -Usage: todolist [command] [args] +pub fn help_global(_p: ErrorDispatcherNotFound) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!( + render_result, + r"Usage: todolist [command] [args] Commands: add -- Add a new task @@ -20,74 +21,78 @@ Commands: Args: -h, --help -- Show this help message -V, --version -- Show the version - -A, --all -- All tasks (Clean all / List all) - " - .trim() - ); + -A, --all -- All tasks (Clean all / List all)" + ) + .ok(); + render_result } +/// Shows help for the `add` command. #[help] -pub fn help_add(_p: EntryAdd) { - r_println!( - "{}", - r" -Usage: todolist add [task description] +pub fn help_add(_p: EntryAdd) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!( + render_result, + r"Usage: todolist add [task description] Add a new task to the todo list. Example: todolist add 'Buy groceries' - todolist add 'Finish Rust project' - " - .trim() - ); + todolist add 'Finish Rust project'" + ) + .ok(); + render_result } +/// Shows help for the `list` command. #[help] -pub fn help_list(_p: EntryList) { - r_println!( - "{}", - r" -Usage: todolist list +pub fn help_list(_p: EntryList) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!( + render_result, + r"Usage: todolist list List all tasks. Example: - todolist list - " - .trim() - ); + todolist list" + ) + .ok(); + render_result } +/// Shows help for the `complete` command. #[help] -pub fn help_complete(_p: EntryComplete) { - r_println!( - "{}", - r" -Usage: todolist complete [task_id] +pub fn help_complete(_p: EntryComplete) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!( + render_result, + r"Usage: todolist complete [task_id] Mark a task as complete by its ID. Example: todolist complete 1 - todolist complete 3 - " - .trim() - ); + todolist complete 3" + ) + .ok(); + render_result } +/// Shows help for the `clean` command. #[help] -pub fn help_clean(_p: EntryClean) { - r_println!( - "{}", - r" -Usage: todolist clean +pub fn help_clean(_p: EntryClean) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!( + render_result, + r"Usage: todolist clean Remove all completed tasks from the list. Example: - todolist clean - " - .trim() - ); + todolist clean" + ) + .ok(); + render_result } diff --git a/examples/full-todolist/src/main.rs b/examples/full-todolist/src/main.rs index e4c5aa6..0748832 100644 --- a/examples/full-todolist/src/main.rs +++ b/examples/full-todolist/src/main.rs @@ -7,12 +7,13 @@ //! > This is truly a cliché example, as common as `Hello World`! use mingling::{ + LazyInit, LazyRes, macros::route, prelude::*, res::ResExitCode, - setup::{ExitCodeSetup, StructuralRendererSetup, HelpFlagSetup}, - LazyInit, LazyRes, + setup::{ExitCodeSetup, HelpFlagSetup, StructuralRendererSetup}, }; +use std::io::Write; mod help; pub use help::*; @@ -160,38 +161,47 @@ fn handle_clean( } } +/// Renders error when no task description is provided. #[renderer] -fn render_error_no_task_description_provided( +pub fn render_error_no_task_description_provided( _err: ErrorNoTaskDescriptionProvided, // ExitCode ec: &mut ResExitCode, -) { - r_println!("No task description provided!"); - r_println!(""); - r_println!("Use `todolist add <desc>` to add a task"); +) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "No task description provided!").ok(); + writeln!(render_result).ok(); + writeln!(render_result, "Use `todolist add <desc>` to add a task").ok(); ec.exit_code = 1; + render_result } +/// Renders error when no index is provided. #[renderer] -fn render_error_no_index_provided( +pub fn render_error_no_index_provided( _err: ErrorNoIndexProvided, // ExitCode ec: &mut ResExitCode, -) { - r_println!("No index provided!"); - r_println!(""); - r_println!("Use `todolist list` to query indexes"); +) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "No index provided!").ok(); + writeln!(render_result).ok(); + writeln!(render_result, "Use `todolist list` to query indexes").ok(); ec.exit_code = 2; + render_result } +/// Renders error when index is out of bounds. #[renderer] -fn render_error_index_out_of_bounds( +pub fn render_error_index_out_of_bounds( _err: ErrorIndexOutOfBounds, // ExitCode ec: &mut ResExitCode, -) { - r_println!("Index out of bounds!"); +) -> RenderResult { + let mut render_result = RenderResult::new(); + writeln!(render_result, "Index out of bounds!").ok(); ec.exit_code = 3; + render_result } gen_program!(); diff --git a/examples/full-todolist/src/todolist.rs b/examples/full-todolist/src/todolist.rs index e447f5d..71338c3 100644 --- a/examples/full-todolist/src/todolist.rs +++ b/examples/full-todolist/src/todolist.rs @@ -1,10 +1,8 @@ //! Data structures, read and write logic for the todo list -use mingling::{ - macros::{r_println, renderer}, - Groupped, -}; +use mingling::{Groupped, RenderResult, macros::renderer}; use serde::{Deserialize, Serialize}; +use std::io::Write; use std::{env::current_dir, path::PathBuf}; use crate::ResProgramFlags; @@ -37,22 +35,30 @@ pub fn read_todo_list() -> ResTodoList { serde_json::from_reader(reader).unwrap_or_default() } +/// Renders the todo list. #[renderer] -pub fn render_res_todo_list(todo_list: ResTodoList, program_flags: &ResProgramFlags) { +pub fn render_res_todo_list( + todo_list: ResTodoList, + program_flags: &ResProgramFlags, +) -> RenderResult { + let mut render_result = RenderResult::new(); let mut idx = 0; - r_println!("TODO: "); + writeln!(render_result, "TODO: ").ok(); for item in &todo_list.items { if item.completed && !program_flags.all { idx += 1; continue; } - r_println!( + writeln!( + render_result, " {idx}. [{}] - \"{}\"", if item.completed { "x" } else { " " }, item.item - ); + ) + .ok(); idx += 1; } + render_result } pub fn write_todo_list(todo_list: ResTodoList) { diff --git a/mingling/src/example_docs.rs b/mingling/src/example_docs.rs index 92b13a0..4b34d0d 100644 --- a/mingling/src/example_docs.rs +++ b/mingling/src/example_docs.rs @@ -39,6 +39,7 @@ /// Source code (./src/main.rs) /// ```ignore /// use mingling::{macros::route, prelude::*}; +/// use std::io::Write; /// /// dispatcher!("transfer", CMDTransfer => EntryTransfer); /// dispatcher!("strict-transfer", CMDStrictTransfer => EntryStrictTransfer); @@ -91,20 +92,26 @@ /// /// /// Renders the parsed transfer result (file/dir, size, name). /// #[renderer] -/// fn render_result_file(result: ResultFile) { +/// fn render_result_file(result: ResultFile) -> RenderResult { /// let (is_dir, size, name) = result.into(); -/// r_println!( +/// let mut result = RenderResult::new(); +/// writeln!( +/// result, /// "{}: {} ({})", /// if is_dir { "dir" } else { "file" }, /// name, /// size /// ) +/// .ok(); +/// result /// } /// /// /// Renders the error when no name is provided. /// #[renderer] -/// fn render_error_no_name_provided(_: ErrorNoNameProvided) { -/// r_println!("Error: name is not provided") +/// fn render_error_no_name_provided(_: ErrorNoNameProvided) -> RenderResult { +/// let mut result = RenderResult::new(); +/// writeln!(result, "Error: name is not provided").ok(); +/// result /// } /// /// gen_program!(); @@ -166,6 +173,7 @@ pub mod example_argument_parse {} /// Source code (./src/main.rs) /// ```ignore /// use mingling::{hook::ProgramHook, prelude::*}; +/// use std::io::Write; /// /// #[tokio::main] /// async fn main() { @@ -197,8 +205,10 @@ pub mod example_argument_parse {} /// /// Renders the downloaded file name. /// #[renderer] /// // But renderers cannot use the `async` keyword -/// pub fn render_downloaded(result: ResultDownloaded) { -/// r_println!("\"{}\" downloaded.", *result); +/// pub fn render_downloaded(result: ResultDownloaded) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "\"{}\" downloaded.", *result).ok(); +/// render_result /// } /// // --------- IMPORTANT --------- /// @@ -241,6 +251,7 @@ pub mod example_async_support {} /// ```ignore /// // Import commonly used Mingling modules /// use mingling::prelude::*; +/// use std::io::Write; /// /// // Define the `greet` subcommand /// // _____________________________ subcmd name, can be nested (e.g. "remote.add" "remote.rm") @@ -287,8 +298,10 @@ pub mod example_async_support {} /// // Define renderer `render_name`, used to render `ResultName` /// /// Renders the greeting message with the provided name. /// #[renderer] -/// fn render_name(name: ResultName) { -/// r_println!("Hello, {}!", *name); +/// fn render_name(name: ResultName) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Hello, {}!", *name).ok(); +/// render_result /// } /// /// // Note: This macro generates the program entry point. @@ -365,7 +378,8 @@ pub mod example_basic {} /// /// Source code (./src/main.rs) /// ```ignore -/// use mingling::{macros::dispatcher_clap, prelude::*, setup::BasicProgramSetup, Groupped}; +/// use mingling::{Groupped, macros::dispatcher_clap, prelude::*, setup::BasicProgramSetup}; +/// use std::io::Write; /// /// fn main() { /// let mut program = ThisProgram::new(); @@ -416,24 +430,29 @@ pub mod example_basic {} /// /// /// Renders the greet output with optional repetition. /// #[renderer] -/// fn render_greet(greet: EntryGreet) { +/// fn render_greet(greet: EntryGreet) -> RenderResult { /// let name = greet.name; /// let count = greet.repeat.max(0) as usize; /// -/// r_print!("Hello, "); +/// let mut render_result = RenderResult::default(); +/// write!(render_result, "Hello, ").ok(); /// for i in 0..count { -/// r_print!("{name}"); +/// write!(render_result, "{name}").ok(); /// if i < count - 1 { -/// r_print!(", "); +/// write!(render_result, ", ").ok(); /// } /// } -/// r_println!("!"); +/// writeln!(render_result, "!").ok(); +/// render_result /// } /// /// /// Renders the error message when greet argument parsing fails. /// #[renderer] -/// fn render_greet_parse_failed(err: ErrorGreetParsed) { -/// r_println!("{}", *err); +/// // renderers can return a RenderResult instead of using r_println! +/// pub fn render_greet_parse_failed(err: ErrorGreetParsed) -> RenderResult { +/// let mut render_result = RenderResult::default(); +/// writeln!(render_result, "{}", *err).ok(); +/// render_result /// } /// /// gen_program!(); @@ -584,6 +603,7 @@ pub mod example_combine_pathf_dispatch_tree {} /// Source code (./src/main.rs) /// ```ignore /// use mingling::{macros::suggest, prelude::*, ShellContext, Suggest}; +/// use std::io::Write; /// /// fn main() { /// let mut program = ThisProgram::new(); @@ -657,13 +677,15 @@ pub mod example_combine_pathf_dispatch_tree {} /// /// /// Renders the greeting with the result name and repeat count. /// #[renderer] -/// fn render_name(result: ResultName) { +/// fn render_name(result: ResultName) -> RenderResult { /// let (repeat, name) = result.inner; +/// let mut render_result = RenderResult::new(); /// let mut parts = Vec::with_capacity(repeat as usize); /// for _ in 0..repeat { /// parts.push(name.clone()); /// } -/// r_println!("Hello, {}!", parts.join(", ")); +/// writeln!(render_result, "Hello, {}!", parts.join(", ")).ok(); +/// render_result /// } /// /// gen_program!(); @@ -703,6 +725,7 @@ pub mod example_completion {} /// Source code (./src/main.rs) /// ```ignore /// use mingling::{macros::route, parser::Pickable, prelude::*, Groupped}; +/// use std::io::Write; /// /// // Define types that can be recognized by Mingling /// // ________________________ `Pickable` trait needs to implement Default @@ -740,14 +763,18 @@ pub mod example_completion {} /// /// /// Renders the connected address. /// #[renderer] -/// fn render_address(addr: Address) { -/// r_println!("Connected to \"{}\"", addr.to_string()); +/// pub fn render_address(addr: Address) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// write!(render_result, "Connected to \"{}\"", addr.to_string()).ok(); +/// render_result /// } /// /// /// Renders the error message when address parsing fails. /// #[renderer] -/// fn render_error_parse_address_failed(_: ErrorParseAddressFailed) { -/// r_println!("Failed to parse address"); +/// pub fn render_error_parse_address_failed(_: ErrorParseAddressFailed) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// write!(render_result, "Failed to parse address").ok(); +/// render_result /// } /// /// gen_program!(); @@ -856,6 +883,7 @@ pub mod example_custom_pickable {} /// Source code (./src/main.rs) /// ```ignore /// use mingling::prelude::*; +/// use std::io::Write; /// /// // --------- IMPORTANT --------- /// // You have a large number of subcommands @@ -888,8 +916,10 @@ pub mod example_custom_pickable {} /// /// /// Renders the confirmation message for the `cmd5` command. /// #[renderer] -/// fn render_cmd5(_: Entry5) { -/// r_println!("It's works!"); +/// fn render_cmd5(_: Entry5) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "It's works!").ok(); +/// render_result /// } /// /// gen_program!(); @@ -936,6 +966,7 @@ pub mod example_dispatch_tree {} /// macros::suggest_enum, parser::PickableEnum, prelude::*, EnumTag, Groupped, ShellContext, /// Suggest, /// }; +/// use std::io::Write; /// /// // Define the enum and derive the EnumTag trait /// // ________ adds metadata to the enum, enabling it to: @@ -1000,10 +1031,11 @@ pub mod example_dispatch_tree {} /// /// /// Renders the selected programming language with its name and description. /// #[renderer] -/// fn render_programming_language(lang: ProgrammingLanguages) { -/// // You can use `enum_info()` to get the name and description of the current enum +/// pub fn render_programming_language(lang: ProgrammingLanguages) -> RenderResult { +/// let mut render_result = RenderResult::new(); /// let (name, desc) = lang.enum_info(); -/// r_println!("Selected: {} ({})", name, desc) +/// writeln!(render_result, "Selected: {} ({})", name, desc).ok(); +/// render_result /// } /// /// #[completion(EntryLanguageSelection)] @@ -1060,6 +1092,7 @@ pub mod example_enum_tag {} /// Source code (./src/main.rs) /// ```ignore /// use mingling::prelude::*; +/// use std::io::Write; /// /// // In Mingling, instead of using ? to propagate errors upward, /// // errors are treated as branches that continue execution. @@ -1100,36 +1133,47 @@ pub mod example_enum_tag {} /// /// /// Renders a successful greeting with the given name. /// #[renderer] -/// fn render_result_name(name: ResultName) { -/// r_println!("Hello, {}", *name); +/// fn render_result_name(name: ResultName) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Hello, {}", *name).ok(); +/// render_result /// } /// /// /// Renders the error when no name is provided. /// #[renderer] -/// fn render_error_no_name_provided(_: ErrorNoNameProvided) { -/// // Prompt when no name is provided -/// r_println!("No name provided"); +/// fn render_error_no_name_provided(_: ErrorNoNameProvided) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "No name provided").ok(); +/// render_result /// } /// /// /// Renders the error when the name is already taken. /// #[renderer] -/// fn render_error_name_not_available(_: ErrorNameNotAvailable) { -/// // Prompt when name is already taken -/// r_println!("Name not available"); +/// fn render_error_name_not_available(_: ErrorNameNotAvailable) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Name not available").ok(); +/// render_result /// } /// /// /// Renders the error when the name exceeds the maximum length. /// #[renderer] -/// fn render_error_name_too_long(len: ErrorNameTooLong) { -/// // Prompt when name is too long, showing actual length -/// r_println!("Name too long: {} > 10", *len); +/// fn render_error_name_too_long(len: ErrorNameTooLong) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Name too long: {} > 10", *len).ok(); +/// render_result /// } /// /// /// Renders the error when the dispatcher (subcommand) is not found. /// #[renderer] -/// fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) { -/// // Prompt when command is not found, showing the input command -/// r_println!("Command not found: \"{}\"", err.inner.join(" ")); +/// fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!( +/// render_result, +/// "Command not found: \"{}\"", +/// err.inner.join(" ") +/// ) +/// .ok(); +/// render_result /// } /// /// gen_program!(); @@ -1178,6 +1222,7 @@ pub mod example_error_handling {} /// res::ResExitCode, /// setup::{BasicProgramSetup, ExitCodeSetup}, /// }; +/// use std::io::Write; /// /// fn main() { /// let mut program = ThisProgram::new(); @@ -1210,25 +1255,32 @@ pub mod example_error_handling {} /// /// /// Renders a successful greeting with the given name. /// #[renderer] -/// fn render_result_name(name: ResultName) { -/// r_println!("Hello, {}", *name); +/// fn render_result_name(name: ResultName) -> RenderResult { +/// let mut result = RenderResult::new(); +/// writeln!(result, "Hello, {}", *name).ok(); +/// result /// } /// /// #[help] -/// fn help_hello(_p: EntryHello, ec: &mut ResExitCode) { -/// r_println!("Usage: hello <NAME>"); +/// fn help_hello(_p: EntryHello, ec: &mut ResExitCode) -> RenderResult { +/// let mut result = RenderResult::new(); +/// writeln!(result, "Usage: hello <NAME>").ok(); /// ec.exit_code = 2; +/// result /// } /// /// // Define renderer, render error message _______________ Inject exit code resource /// // / /// /// Renders the error when no name is provided | /// #[renderer] // vvvvvvvvvvvvvvvv -/// fn render_error_no_name_provided(_: ErrorNoNameProvided, ec: &mut ResExitCode) { +/// fn render_error_no_name_provided(_: ErrorNoNameProvided, ec: &mut ResExitCode) -> RenderResult { /// ec.exit_code = 1; /// +/// let mut result = RenderResult::new(); +/// /// // Prompt when no name is provided -/// r_println!("No name provided (with exit code 1)"); +/// writeln!(result, "No name provided (with exit code 1)").ok(); +/// result /// } /// /// gen_program!(); @@ -1265,14 +1317,17 @@ pub mod example_exitcode {} /// Source code (./src/main.rs) /// ```ignore /// use mingling::{macros::help, prelude::*, setup::BasicProgramSetup}; +/// use std::io::Write; /// /// dispatcher!("greet", CMDGreet => EntryGreet); /// /// // Define help _________ When `program.user_context.help` is `true` /// // / the command will not enter `#[chain]` / `#[renderer]` /// #[help] // vvvvvvvvvv but instead enter this `#[help]` function -/// fn help_greet(_prev: EntryGreet) { -/// r_println!("Usage: greet <NAME>"); +/// fn help_greet(_prev: EntryGreet) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Usage: greet <NAME>").ok(); +/// render_result /// } /// /// fn main() { @@ -1333,6 +1388,7 @@ pub mod example_help {} /// hook::{ProgramControlUnit, ProgramHook}, /// prelude::*, /// }; +/// use std::io::Write; /// /// dispatcher!("greet", CMDGreet => EntryGreet); /// @@ -1376,9 +1432,12 @@ pub mod example_help {} /// } /// /// /// Renders the greeting message with the provided name. +/// /// Renders the greeting message with the provided name. /// #[renderer] -/// fn render_name(name: ResultName) { -/// r_println!("Hello, {}!", *name); +/// fn render_name(name: ResultName) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Hello, {}!", *name).ok(); +/// render_result /// } /// /// gen_program!(); @@ -1465,6 +1524,7 @@ pub mod example_implicit_dispatcher {} /// Source code (./src/main.rs) /// ```ignore /// use std::collections::BTreeMap; +/// use std::io::Write; /// /// use mingling::{LazyInit, LazyRes, prelude::*}; /// @@ -1516,20 +1576,25 @@ pub mod example_implicit_dispatcher {} /// // | _____________________ Use LazyRes<ResLargeData> /// // | / instead of ResLargeData /// #[renderer] // vvvv vvvvvvvvvvvvvvvvvvvvv -/// fn render_entry_show(_args: EntryShow, res: &mut LazyRes<ResLargeData>) { +/// fn render_entry_show(_args: EntryShow, res: &mut LazyRes<ResLargeData>) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// /// // _______ Initialization happens here /// // / /// // vvvvvvv /// let res = res.get_ref(); /// for (key, value) in &res.data { -/// r_println!("{}: {}", key, value); +/// writeln!(render_result, "{}: {}", key, value).ok(); /// } +/// render_result /// } /// /// // When not using LazyRes<ResLargeData>, it will not be initialized /// #[renderer] -/// fn render_entry_none(_args: EntryNone) { -/// r_println!("None"); +/// fn render_entry_none(_args: EntryNone) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "None").ok(); +/// render_result /// } /// /// gen_program!(); @@ -1574,6 +1639,7 @@ pub mod example_lazy_resources {} /// Source code (./src/main.rs) /// ```ignore /// use mingling::{macros::group, prelude::*}; +/// use std::io::Write; /// use std::{io::ErrorKind::Other, num::ParseIntError}; /// /// dispatcher!("parse"); @@ -1616,8 +1682,10 @@ pub mod example_lazy_resources {} /// // _____________ Using std::num::ParseIntError as a chain input /// // / /// #[renderer] // vvvvvvvvvvvv -/// fn render_number(num: ParsedNumber) { -/// r_println!("Parsed number: {}", *num); +/// fn render_number(num: ParsedNumber) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// write!(render_result, "Parsed number: {}", *num).ok(); +/// render_result /// } /// /// /// Renderer for parse errors — using the outside `ParseIntError` type. @@ -1625,16 +1693,20 @@ pub mod example_lazy_resources {} /// /// The `ParseIntError` type is registered via `group!` above, so it implements /// /// `Groupped<ThisProgram>` and can be used directly in a `#[renderer]` function. /// #[renderer] -/// fn render_parse_error(err: ParseIntError) { -/// r_println!("Parse error: {}", err); +/// fn render_parse_error(err: ParseIntError) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// write!(render_result, "Parse error: {}", err).ok(); +/// render_result /// } /// /// /// Renderer for IO errors — using `std::io::Error` registered as `ErrorIo`. /// // ________ Must use alias `ErrorIo` here, not bare `std::io::Error` /// // / /// #[renderer] // vvvvvvv -/// fn render_error_io(err: ErrorIo) { -/// r_println!("IO_ERROR: {}", err.to_string()); +/// fn render_error_io(err: ErrorIo) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// write!(render_result, "IO_ERROR: {}", err.to_string()).ok(); +/// render_result /// } /// /// fn main() { @@ -1699,6 +1771,7 @@ pub mod example_outside_type {} /// ```ignore /// use mingling::prelude::*; /// use mingling::setup::StructuralRendererSetup; +/// use std::io::Write; /// use std::path::PathBuf; /// /// dispatcher!("find", CMDFind => EntryFind); @@ -1771,32 +1844,42 @@ pub mod example_outside_type {} /// /// /// Renders the successful result with the found directory path. /// #[renderer] -/// fn render_result_path(path: ResultPath) { -/// r_println!("Found directory: {}", path.display()); +/// fn render_result_path(path: ResultPath) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Found directory: {}", path.display()).ok(); +/// render_result /// } /// /// /// Renders the error when no search path is provided. /// #[renderer] -/// fn render_error_not_found(_: ErrorNotFound) { -/// r_println!("Search path not provided"); +/// fn render_error_not_found(_: ErrorNotFound) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Search path not provided").ok(); +/// render_result /// } /// /// /// Renders the error when the given path is not a directory. /// #[renderer] -/// fn render_error_not_dir(err: ErrorNotDir) { -/// r_println!("Not a directory: {}", err.info.display()); +/// fn render_error_not_dir(err: ErrorNotDir) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Not a directory: {}", err.info.display()).ok(); +/// render_result /// } /// /// /// Renders the structural error when no search path is provided. /// #[renderer] -/// fn render_error_not_found_structural(_: ErrorNotFoundStructural) { -/// r_println!("Search path not provided"); +/// fn render_error_not_found_structural(_: ErrorNotFoundStructural) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Search path not provided").ok(); +/// render_result /// } /// /// /// Renders the structural error when the given path is not a directory. /// #[renderer] -/// fn render_error_not_dir_structural(err: ErrorNotDirStructural) { -/// r_println!("Not a directory: {}", err.info.display()); +/// fn render_error_not_dir_structural(err: ErrorNotDirStructural) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Not a directory: {}", err.info.display()).ok(); +/// render_result /// } /// /// gen_program!(); @@ -1853,6 +1936,7 @@ pub mod example_pack_err {} /// Source code (./src/main.rs) /// ```ignore /// use mingling::{hook::ProgramHook, prelude::*}; +/// use std::io::Write; /// /// dispatcher!("panic", CMDPanic => EntryPanic); /// pack!(NotPanic = ()); @@ -1888,9 +1972,12 @@ pub mod example_pack_err {} /// } /// /// /// Renders the message when no panic occurs. +/// /// Renders the message when no panic occurs. /// #[renderer] -/// fn render(_: NotPanic) { -/// r_println!("Program not panic"); +/// pub fn render(_: NotPanic) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Program not panic").ok(); +/// render_result /// } /// /// gen_program!(); @@ -1994,6 +2081,7 @@ pub mod example_pathfinder {} /// setup::{BasicREPLOutputSetup, BasicREPLPromptSetup, BasicREPLReadlineSetup}, /// this, /// }; +/// use std::io::Write; /// use std::{env::current_dir, path::PathBuf}; /// /// // Resource to store the current directory @@ -2116,10 +2204,12 @@ pub mod example_pathfinder {} /// /// /// Render ResultList data /// #[renderer] -/// fn render_list(list: ResultList) { +/// fn render_list(list: ResultList) -> RenderResult { +/// let mut render_result = RenderResult::new(); /// for item in list.inner { -/// r_println!("{}", item); +/// writeln!(render_result, "{}", item).ok(); /// } +/// render_result /// } /// /// // Handle exit command event @@ -2141,15 +2231,24 @@ pub mod example_pathfinder {} /// /// /// Handle path not found event /// #[renderer] -/// fn render_error_directory_not_exist(err: ErrorDirectoryNotExist) { -/// r_println!("Directory not found: {}", err.inner.display()) +/// fn render_error_directory_not_exist(err: ErrorDirectoryNotExist) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!( +/// render_result, +/// "Directory not found: {}", +/// err.inner.display() +/// ) +/// .ok(); +/// render_result /// } /// /// /// Handle dispatcher not found event /// /// Renders the error when a command is not found. /// #[renderer] -/// fn dispatcher_not_found(prev: ErrorDispatcherNotFound) { -/// r_println!("Command not found: \"{}\"", prev.join(", ")) +/// fn dispatcher_not_found(prev: ErrorDispatcherNotFound) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Command not found: \"{}\"", prev.join(", ")).ok(); +/// render_result /// } /// /// gen_program!(); @@ -2187,9 +2286,9 @@ pub mod example_repl_basic {} /// /// Source code (./src/main.rs) /// ```ignore -/// use std::path::PathBuf; -/// /// use mingling::prelude::*; +/// use std::io::Write; +/// use std::path::PathBuf; /// /// // Create resource /// // ______________ Resource needs to @@ -2231,8 +2330,15 @@ pub mod example_repl_basic {} /// // / /// /// Renders the current directory path. | /// #[renderer] // vvvvvvvvvvvvvv -/// fn render_current(_: EntryCurrent, current_dir: &ResCurrentDir) { -/// r_println!("Current directory: {}", current_dir.current_dir.display()); +/// fn render_current(_: EntryCurrent, current_dir: &ResCurrentDir) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// write!( +/// render_result, +/// "Current directory: {}", +/// current_dir.current_dir.display() +/// ) +/// .ok(); +/// render_result /// } /// /// gen_program!(); @@ -2330,8 +2436,9 @@ pub mod example_setup {} /// Source code (./src/main.rs) /// ```ignore /// use mingling::prelude::*; -/// use mingling::{parser::Picker, setup::StructuralRendererSetup, StructuralData, Groupped}; +/// use mingling::{Groupped, StructuralData, parser::Picker, setup::StructuralRendererSetup}; /// use serde::Serialize; +/// use std::io::Write; /// /// dispatcher!("render", CMDRender => EntryRender); /// @@ -2376,8 +2483,10 @@ pub mod example_setup {} /// /// /// Implement default renderer for when structural_renderer is not specified /// #[renderer] -/// fn render_info(prev: Info) { -/// r_println!("{} is {} years old", prev.name, prev.age); +/// fn render_info(prev: Info) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "{} is {} years old", prev.name, prev.age).ok(); +/// render_result /// } /// /// gen_program!(); @@ -2407,6 +2516,7 @@ pub mod example_structural_renderer {} /// Source code (./src/main.rs) /// ```ignore /// use mingling::prelude::*; +/// use std::io::Write; /// /// #[cfg(test)] /// mod tests { @@ -2441,25 +2551,25 @@ pub mod example_structural_renderer {} /// #[test] /// fn test_render_result_name() { /// let r = render_result_name(ResultName::new("Peter".into())); -/// assert_eq!(r, "Hello, Peter!\n") +/// assert_eq!(r.to_string().as_str(), "Hello, Peter!\n") /// } /// /// #[test] /// fn test_render_error_no_name_provided() { /// let r = render_error_no_name_provided(ErrorNoNameProvided::default()); -/// assert_eq!(r, "No name provided\n") +/// assert_eq!(r.to_string().as_str(), "No name provided\n") /// } /// /// #[test] /// fn test_render_error_name_not_available() { /// let r = render_error_name_not_available(ErrorNameNotAvailable::default()); -/// assert_eq!(r, "Name not available\n") +/// assert_eq!(r.to_string().as_str(), "Name not available\n") /// } /// /// #[test] /// fn test_render_error_name_too_long() { /// let r = render_error_name_too_long(ErrorNameTooLong::new(17)); -/// assert_eq!(r, "Name too long: 17 > 10\n") +/// assert_eq!(r.to_string().as_str(), "Name too long: 17 > 10\n") /// } /// // --------- IMPORTANT --------- /// } @@ -2493,32 +2603,47 @@ pub mod example_structural_renderer {} /// /// /// Renders a successful greeting with the given name. /// #[renderer] -/// fn render_result_name(name: ResultName) -> String { -/// r_println!("Hello, {}!", *name); +/// fn render_result_name(name: ResultName) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Hello, {}!", *name).ok(); +/// render_result /// } /// /// /// Renders the error when no name is provided. /// #[renderer] -/// fn render_error_no_name_provided(_: ErrorNoNameProvided) -> String { -/// r_println!("No name provided"); +/// fn render_error_no_name_provided(_: ErrorNoNameProvided) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "No name provided").ok(); +/// render_result /// } /// /// /// Renders the error when the name is already taken. /// #[renderer] -/// fn render_error_name_not_available(_: ErrorNameNotAvailable) -> String { -/// r_println!("Name not available"); +/// fn render_error_name_not_available(_: ErrorNameNotAvailable) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Name not available").ok(); +/// render_result /// } /// /// /// Renders the error when the name exceeds the maximum length. /// #[renderer] -/// fn render_error_name_too_long(len: ErrorNameTooLong) -> String { -/// r_println!("Name too long: {} > 10", *len); +/// fn render_error_name_too_long(len: ErrorNameTooLong) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!(render_result, "Name too long: {} > 10", *len).ok(); +/// render_result /// } /// /// /// Renders the error when the dispatcher (subcommand) is not found. /// #[renderer] -/// fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) { -/// r_println!("Command not found: \"{}\"", err.inner.join(" ")); +/// fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) -> RenderResult { +/// let mut render_result = RenderResult::new(); +/// writeln!( +/// render_result, +/// "Command not found: \"{}\"", +/// err.inner.join(" ") +/// ) +/// .ok(); +/// render_result /// } /// /// gen_program!(); diff --git a/mingling_macros/src/dispatcher_clap.rs b/mingling_macros/src/dispatcher_clap.rs index 5f06fb4..b89dbad 100644 --- a/mingling_macros/src/dispatcher_clap.rs +++ b/mingling_macros/src/dispatcher_clap.rs @@ -1,9 +1,8 @@ use proc_macro::TokenStream; use quote::quote; use syn::{ - Ident, ItemStruct, LitBool, LitStr, Token, parse::{Parse, ParseStream}, - parse_macro_input, + parse_macro_input, Ident, ItemStruct, LitBool, LitStr, Token, }; /// Parsed key-value options after the first positional arguments @@ -144,7 +143,7 @@ pub fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> TokenStream Some(quote! { #[allow(non_snake_case)] #[::mingling::macros::help] - pub fn #help_fn_name(_prev: #struct_name) { + pub fn #help_fn_name(_prev: #struct_name) -> ::mingling::RenderResult { use std::io::Write; use clap::ColorChoice; @@ -154,11 +153,14 @@ pub fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> TokenStream let mut cmd = <#struct_name as ::clap::CommandFactory>::command() .color(ColorChoice::Always); let styled = cmd.render_help(); - write!(__renderer_inner_result, "{}", styled.ansi()).unwrap(); + let mut result = ::mingling::RenderResult::new(); + let _ = write!(result, "{}", styled.ansi()); + result } ::mingling::ClapHelpPrintBehaviour::PrintDirectly => { let mut command = <#struct_name as ::clap::CommandFactory>::command(); command.print_help().unwrap(); + ::mingling::RenderResult::new() } } } |
