diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-07-11 14:44:17 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-07-11 14:44:17 +0800 |
| commit | c5736152b0adeef17349265c4b9277ba17b8bcfd (patch) | |
| tree | a5a5227ef4ca20ec09d1c8cf0655e9f9f33e03b6 | |
| parent | c08b2d608abe134e37bc4dee7df2113fac968e06 (diff) | |
feat: require renderers to return RenderResult instead of mutating one
BREAKING CHANGE: The `render` method on `Renderer`, `HelpRequest`, and
`ProgramCollect` now returns `RenderResult` instead of taking
`&mut RenderResult`. The `r_print!` and `r_println!` macros have been
removed in favor of using `std::io::Write` directly on `RenderResult`.
| -rw-r--r-- | mingling/src/lib.rs | 31 | ||||
| -rw-r--r-- | mingling_core/src/asset/help.rs | 2 | ||||
| -rw-r--r-- | mingling_core/src/asset/renderer.rs | 2 | ||||
| -rw-r--r-- | mingling_core/src/program/collection.rs | 6 | ||||
| -rw-r--r-- | mingling_core/src/program/collection/mock.rs | 6 | ||||
| -rw-r--r-- | mingling_core/src/program/exec.rs | 16 | ||||
| -rw-r--r-- | mingling_core/src/program/hook.rs | 4 | ||||
| -rw-r--r-- | mingling_macros/src/help.rs | 40 | ||||
| -rw-r--r-- | mingling_macros/src/lib.rs | 209 | ||||
| -rw-r--r-- | mingling_macros/src/render.rs | 56 | ||||
| -rw-r--r-- | mingling_macros/src/renderer.rs | 86 | ||||
| -rw-r--r-- | mling/src/cli.rs | 29 | ||||
| -rw-r--r-- | mling/src/errors/io_error.rs | 93 | ||||
| -rw-r--r-- | mling/src/lib.rs | 2 | ||||
| -rw-r--r-- | mling/src/proj_mgr/generator.rs | 27 | ||||
| -rw-r--r-- | mling/src/proj_mgr/show_binaries.rs | 21 | ||||
| -rw-r--r-- | mling/src/proj_mgr/show_directories.rs | 21 |
17 files changed, 268 insertions, 383 deletions
diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs index ff8890c..524d061 100644 --- a/mingling/src/lib.rs +++ b/mingling/src/lib.rs @@ -33,15 +33,19 @@ //! } //! //! #[renderer] -//! fn render_name(name: ResultName) { -//! r_println!("Hello, {}!", *name); +//! fn render_name(name: ResultName) -> RenderResult { +//! let mut result = RenderResult::default(); +//! result.println(&format!("Hello, {}!", *name)); +//! result //! } //! //! #[renderer] -//! fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) { +//! fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) -> RenderResult { +//! let mut result = RenderResult::default(); //! if err.len() > 0 { -//! r_println!("Command not found: [{}]", err.join(" ")); +//! result.println(&format!("Command not found: [{}]", err.join(" "))); //! } +//! result //! } //! //! gen_program!(); @@ -84,8 +88,8 @@ pub mod picker { /// Re-export of all macros from `mingling_macros`. /// /// This module re-exports all macros provided by the `mingling_macros` crate, -/// including `dispatcher!`, `chain!`, `renderer!`, `gen_program!`, `pack!`, -/// `r_print!`, `r_println!`, and many others. These macros form the core +/// including `dispatcher!`, `chain!`, `renderer!`, +/// `gen_program!`, `pack!`, and many others. These macros form the core /// building blocks of the Mingling framework. /// /// For detailed documentation, usage examples, and the full list of available @@ -143,10 +147,6 @@ pub mod macros { /// `#[program_setup]` - Used to generate program setup #[cfg(feature = "extra_macros")] pub use mingling_macros::program_setup; - /// `r_print!("{someting}")` - Used to print content within a `Renderer` context - pub use mingling_macros::r_print; - /// `r_println!("{someting}")` - Used to print content with a newline within a `Renderer` context - pub use mingling_macros::r_println; #[doc(hidden)] pub use mingling_macros::register_chain; #[doc(hidden)] @@ -222,8 +222,6 @@ pub mod res; /// use mingling::prelude::*; /// ``` pub mod prelude { - /// Re-export of the `Groupped` derive macro for grouping types. - pub use crate::Groupped; /// Re-export of the `chain` macro for defining a chain of commands. pub use crate::macros::chain; /// Re-export of the `dispatcher` macro for routing commands. @@ -238,13 +236,12 @@ pub mod prelude { /// Re-export of the `pack_err` macro for creating error types. #[cfg(feature = "extra_macros")] pub use crate::macros::pack_err; - /// Re-export of the `r_print` macro for printing within a renderer context. - pub use crate::macros::r_print; - /// Re-export of the `r_println` macro for printing with a newline within a renderer - /// context. - pub use crate::macros::r_println; /// Re-export of the `renderer` macro for defining renderer functions. pub use crate::macros::renderer; + /// Re-export of the `Groupped` derive macro for grouping types. + pub use crate::Groupped; + /// Re-export of the `RenderResult` struct for outputting rendering result + pub use crate::RenderResult; /// Like `pack_err!` but also marks the type for structured output #[cfg(all(feature = "structural_renderer", feature = "extra_macros"))] pub use mingling_macros::pack_err_structural; diff --git a/mingling_core/src/asset/help.rs b/mingling_core/src/asset/help.rs index ff2b3d4..b3742f2 100644 --- a/mingling_core/src/asset/help.rs +++ b/mingling_core/src/asset/help.rs @@ -6,5 +6,5 @@ pub trait HelpRequest { type Entry; /// Process the previous value and write the result into the provided [`RenderResult`](./struct.RenderResult.html) - fn render_help(p: Self::Entry, r: &mut RenderResult); + fn render_help(p: Self::Entry) -> RenderResult; } diff --git a/mingling_core/src/asset/renderer.rs b/mingling_core/src/asset/renderer.rs index 1d5a2c1..732f9b7 100644 --- a/mingling_core/src/asset/renderer.rs +++ b/mingling_core/src/asset/renderer.rs @@ -6,5 +6,5 @@ pub trait Renderer { type Previous; /// Process the previous value and write the result into the provided [`RenderResult`](./struct.RenderResult.html) - fn render(p: Self::Previous, r: &mut RenderResult); + fn render(p: Self::Previous) -> RenderResult; } diff --git a/mingling_core/src/program/collection.rs b/mingling_core/src/program/collection.rs index 044379c..cbb9363 100644 --- a/mingling_core/src/program/collection.rs +++ b/mingling_core/src/program/collection.rs @@ -7,7 +7,7 @@ use crate::Dispatcher; use crate::{AnyOutput, ChainProcess, Groupped, RenderResult}; #[cfg(feature = "structural_renderer")] -use crate::{StructuralRendererSetting, error::StructuralRendererSerializeError}; +use crate::{error::StructuralRendererSerializeError, StructuralRendererSetting}; #[cfg(feature = "comp")] use crate::{ShellContext, Suggest}; @@ -53,10 +53,10 @@ pub trait ProgramCollect { fn build_empty_result() -> AnyOutput<Self::Enum>; /// Render the input [`AnyOutput`](./struct.AnyOutput.html) - fn render(any: AnyOutput<Self::Enum>, r: &mut RenderResult); + fn render(any: AnyOutput<Self::Enum>) -> RenderResult; /// Render help for Entry - fn render_help(any: AnyOutput<Self::Enum>, r: &mut RenderResult); + fn render_help(any: AnyOutput<Self::Enum>) -> RenderResult; /// Find a matching chain to continue execution based on the input [AnyOutput](./struct.AnyOutput.html), returning a new [AnyOutput](./struct.AnyOutput.html) #[cfg(feature = "async")] diff --git a/mingling_core/src/program/collection/mock.rs b/mingling_core/src/program/collection/mock.rs index 568000a..20e6658 100644 --- a/mingling_core/src/program/collection/mock.rs +++ b/mingling_core/src/program/collection/mock.rs @@ -7,7 +7,7 @@ use crate::Dispatcher; use crate::{AnyOutput, ChainProcess, Groupped, ProgramCollect, RenderResult}; #[cfg(feature = "structural_renderer")] -use crate::{StructuralRendererSetting, error::StructuralRendererSerializeError}; +use crate::{error::StructuralRendererSerializeError, StructuralRendererSetting}; #[cfg(feature = "comp")] use crate::{ShellContext, Suggest}; @@ -59,11 +59,11 @@ impl ProgramCollect for MockProgramCollect { unreachable!() } - fn render(_any: AnyOutput<Self::Enum>, _r: &mut RenderResult) { + fn render(_any: AnyOutput<Self::Enum>) -> RenderResult { unreachable!() } - fn render_help(_any: AnyOutput<Self::Enum>, _r: &mut RenderResult) { + fn render_help(_any: AnyOutput<Self::Enum>) -> RenderResult { unreachable!() } diff --git a/mingling_core/src/program/exec.rs b/mingling_core/src/program/exec.rs index d1983ed..3224c82 100644 --- a/mingling_core/src/program/exec.rs +++ b/mingling_core/src/program/exec.rs @@ -461,18 +461,14 @@ pub(crate) fn handle_program_control<C: ProgramCollect<Enum = C>>( fn render<C: ProgramCollect<Enum = C>>(program: &Program<C>, any: AnyOutput<C>) -> RenderResult { #[cfg(not(feature = "structural_renderer"))] { - let mut render_result = RenderResult::default(); - C::render(any, &mut render_result); - render_result + C::render(any) } #[cfg(feature = "structural_renderer")] { #[allow(unreachable_patterns)] match program.structural_renderer_name { super::StructuralRendererSetting::Disable => { - let mut render_result = RenderResult::default(); - C::render(any, &mut render_result); - render_result + C::render(any) } _ => C::structural_render(any, &program.structural_renderer_name).unwrap(), } @@ -487,18 +483,14 @@ fn render_help<C: ProgramCollect<Enum = C>>( ) -> RenderResult { #[cfg(not(feature = "structural_renderer"))] { - let mut render_result = RenderResult::default(); - C::render_help(entry, &mut render_result); - render_result + C::render_help(entry) } #[cfg(feature = "structural_renderer")] { #[allow(unreachable_patterns)] match program.structural_renderer_name { super::StructuralRendererSetting::Disable => { - let mut render_result = RenderResult::default(); - C::render_help(entry, &mut render_result); - render_result + C::render_help(entry) } _ => RenderResult::default(), } diff --git a/mingling_core/src/program/hook.rs b/mingling_core/src/program/hook.rs index 630baae..56d8e0e 100644 --- a/mingling_core/src/program/hook.rs +++ b/mingling_core/src/program/hook.rs @@ -718,11 +718,11 @@ mod tests { unreachable!() } - fn render(_any: crate::AnyOutput<MockHookEnum>, _r: &mut crate::RenderResult) { + fn render(_any: crate::AnyOutput<MockHookEnum>) -> crate::RenderResult { unreachable!() } - fn render_help(_any: crate::AnyOutput<MockHookEnum>, _r: &mut crate::RenderResult) { + fn render_help(_any: crate::AnyOutput<MockHookEnum>) -> crate::RenderResult { unreachable!() } diff --git a/mingling_macros/src/help.rs b/mingling_macros/src/help.rs index 6d427b9..7f5b695 100644 --- a/mingling_macros/src/help.rs +++ b/mingling_macros/src/help.rs @@ -1,22 +1,34 @@ use proc_macro::TokenStream; -use quote::{ToTokens, quote}; +use quote::{quote, ToTokens}; use syn::spanned::Spanned; -use syn::{Ident, ItemFn, ReturnType, Signature, Type, TypePath, parse_macro_input}; +use syn::{parse_macro_input, Ident, ItemFn, ReturnType, Type, TypePath}; use crate::get_global_set; use crate::res_injection::{extract_args_info, generate_immut_resource_bindings}; -/// Validates the return type is () or empty -fn validate_return_type(sig: &Signature) -> syn::Result<()> { +/// Validates that the function returns `::mingling::RenderResult`. +fn validate_render_result_return(sig: &syn::Signature) -> syn::Result<()> { match &sig.output { ReturnType::Type(_, ty) => match &**ty { - Type::Tuple(tuple) if tuple.elems.is_empty() => Ok(()), + Type::Path(type_path) => { + let last_seg = type_path.path.segments.last().map(|s| s.ident.to_string()); + match last_seg.as_deref() { + Some("RenderResult") => Ok(()), + _ => Err(syn::Error::new( + ty.span(), + "Help function must return `RenderResult`", + )), + } + } _ => Err(syn::Error::new( ty.span(), - "Help function must return () or have no return type", + "Help function must return `RenderResult`", )), }, - ReturnType::Default => Ok(()), + ReturnType::Default => Err(syn::Error::new( + sig.span(), + "Help function must have a return type `-> RenderResult`", + )), } } @@ -37,8 +49,8 @@ pub fn help_attr(item: TokenStream) -> TokenStream { Err(e) => return e.to_compile_error().into(), }; - // Validate return type - if let Err(e) = validate_return_type(&input_fn.sig) { + // Validate return type is RenderResult + if let Err(e) = validate_render_result_return(&input_fn.sig) { return e.to_compile_error().into(); } @@ -136,19 +148,17 @@ pub fn help_attr(item: TokenStream) -> TokenStream { impl ::mingling::HelpRequest for #struct_name { type Entry = #entry_type; - fn render_help(#prev_param: Self::Entry, __renderer_inner_result: &mut ::mingling::RenderResult) { + fn render_help(#prev_param: Self::Entry) -> ::mingling::RenderResult { #help_render_body } } ::mingling::macros::register_help!(#entry_type, #struct_name); - // Keep the original function for internal use with original params without __renderer_inner_result + // Keep the original function unchanged #(#fn_attrs)* - #vis fn #fn_name(#original_inputs) { - let mut dummy_r = ::mingling::RenderResult::default(); - let __renderer_inner_result = &mut dummy_r; + #vis fn #fn_name(#original_inputs) -> ::mingling::RenderResult { #fn_body } }; @@ -164,7 +174,7 @@ fn build_help_entry(struct_name: &Ident, entry_type: &TypePath) -> proc_macro2:: // SAFETY: The member_id check ensures that `any` contains a value of type `#entry_type`, // so downcasting to `#entry_type` is safe. let value = unsafe { any.downcast::<#entry_type>().unwrap_unchecked() }; - <#struct_name as ::mingling::HelpRequest>::render_help(value, __renderer_inner_result); + <#struct_name as ::mingling::HelpRequest>::render_help(value) } } } diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs index c82466d..242b729 100644 --- a/mingling_macros/src/lib.rs +++ b/mingling_macros/src/lib.rs @@ -68,7 +68,6 @@ //! | [`#[help]`](attr.help.html) | Defines help output for a command entry type | //! | [`route!`] | Routes execution depending on a condition | //! | [`empty_result!`] | Returns an empty result for early termination | -//! | [`r_print!`] / [`r_println!`] | Print to the `RenderResult` buffer inside a renderer | //! | [`#[completion]`](attr.completion.html) | Registers a shell completion handler | //! //! ## Phase 3: Program Generation @@ -170,7 +169,6 @@ mod pack; mod pack_err; #[cfg(feature = "extra_macros")] mod program_setup; -mod render; mod renderer; mod res_injection; #[cfg(feature = "structural_renderer")] @@ -704,95 +702,6 @@ pub fn dispatcher(input: TokenStream) -> TokenStream { dispatcher::dispatcher(input) } -/// Prints formatted text to the current [`RenderResult`] buffer within a -/// `#[renderer]` function. -/// -/// Unlike `print!`, this macro writes to the in-memory `RenderResult` buffer -/// rather than directly to stdout. The buffered output is flushed automatically -/// when the renderer returns, allowing the framework to control output timing -/// and capture (e.g., for testing or structural rendering to JSON/YAML). -/// -/// This macro requires a mutable reference to a [`RenderResult`] named -/// `__renderer_inner_result` to be in scope, which is automatically provided -/// inside `#[renderer]` and `#[help]` functions. -/// -/// # Syntax -/// -/// Same as [`format!`] / [`print!`]: -/// -/// ```rust,ignore -/// r_print!("Hello, {}!", name); -/// r_print!("Value: {value}"); -/// ``` -/// -/// # Example -/// -/// ```rust,ignore -/// use mingling::macros::{renderer, r_print}; -/// -/// #[renderer] -/// fn show_greeting(prev: Greeting) { -/// r_print!("Hello, {}!", *prev); -/// } -/// ``` -/// -/// # Difference from `r_println!` -/// -/// `r_print!` does **not** append a newline. Use [`r_println!`] for newline-terminated output. -/// -/// # Panics -/// -/// Does not panic under normal circumstances. The underlying `RenderResult` -/// buffer operations are infallible. -/// -/// [`RenderResult`]: https://docs.rs/mingling/latest/mingling/struct.RenderResult.html -#[proc_macro] -pub fn r_print(input: TokenStream) -> TokenStream { - render::r_print(input) -} - -/// Prints formatted text followed by a newline to the current [`RenderResult`] buffer -/// within a `#[renderer]` or `#[help]` function. -/// -/// Unlike `println!`, this macro writes to the in-memory `RenderResult` buffer -/// rather than directly to stdout. The buffered output is flushed automatically -/// when the renderer returns. -/// -/// This macro requires a mutable reference to a [`RenderResult`] named -/// `__renderer_inner_result` to be in scope, which is automatically provided -/// inside `#[renderer]` and `#[help]` functions. -/// -/// # Syntax -/// -/// Same as [`println!`]: -/// -/// ```rust,ignore -/// r_println!("Hello, {}!", name); -/// r_println!("Value: {value}"); -/// r_println!(); // just a newline -/// ``` -/// -/// # Example -/// -/// ```rust,ignore -/// use mingling::macros::{renderer, r_println}; -/// -/// #[renderer] -/// fn show_greeting(prev: Greeting) { -/// r_println!("Hello, {}!", *prev); -/// } -/// ``` -/// -/// # See also -/// -/// - [`r_print!`] for output without a trailing newline. -/// -/// [`RenderResult`]: https://docs.rs/mingling/latest/mingling/struct.RenderResult.html -#[proc_macro] -pub fn r_println(input: TokenStream) -> TokenStream { - render::r_println(input) -} - /// Declares a chain processing step that transforms one type into another /// within a Mingling pipeline. /// @@ -893,7 +802,7 @@ pub fn r_println(input: TokenStream) -> TokenStream { /// # Sync Example with Resource Injection /// /// ```rust,ignore -/// use mingling::macros::{chain, pack, gen_program, r_println}; +/// use mingling::macros::{chain, pack, gen_program}; /// /// #[derive(Default, Clone)] /// struct UserName(String); @@ -903,7 +812,6 @@ pub fn r_println(input: TokenStream) -> TokenStream { /// /// #[chain] /// fn greet(prev: HelloEntry, user_name: &UserName, count: &mut u64) -> Next { -/// r_println!("User: {:?}", user_name); /// *count += 1; /// Greeting::new(format!("Hello, {}!", user_name.0)) /// } @@ -971,32 +879,31 @@ pub fn chain(attr: TokenStream, item: TokenStream) -> TokenStream { /// The `#[renderer]` attribute converts a function into a renderer by: /// 1. Generating a hidden struct implementing the `Renderer` trait. /// 2. Registering the renderer mapping in the global renderer registry. -/// 3. Keeping the original function for direct calls. When called directly, -/// a new `RenderResult` is created and the renderer function writes its -/// output directly to the current terminal output buffer. -/// -/// Inside a `#[renderer]` function, you can use `r_print!` and `r_println!` -/// to write output to the `RenderResult` buffer. +/// 3. Keeping the original function for direct calls. /// /// # Syntax /// /// ```rust,ignore /// #[renderer] -/// fn render_my_type(prev: MyType) { -/// r_println!("Output: {:?}", *prev); +/// fn render_my_type(prev: MyType) -> RenderResult { +/// let result = RenderResult::new(); +/// writeln!(result, "Output: {:?}", *prev); +/// result /// } /// ``` /// /// # Example /// /// ```rust,ignore -/// use mingling::macros::{renderer, r_println, pack, gen_program}; +/// use mingling::macros::{renderer, pack, gen_program}; /// /// pack!(Greeting = String); /// /// #[renderer] -/// fn render_greeting(prev: Greeting) { -/// r_println!("Hello, {}!", *prev); +/// fn render_greeting(prev: Greeting) -> RenderResult { +/// let result = RenderResult::new(); +/// writeln!(result, "Hello, {}!", *prev); +/// result /// } /// ``` /// @@ -1015,13 +922,17 @@ pub fn chain(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// ```rust,ignore /// #[renderer] -/// fn fallback_dispatcher_not_found(prev: ErrorDispatcherNotFound) { -/// r_println!("Unknown command: {}", prev.join(", ")); +/// fn fallback_dispatcher_not_found(prev: ErrorDispatcherNotFound) -> RenderResult { +/// let result = RenderResult::new(); +/// writeln!(result, "Unknown command: {}", prev.join(", ")); +/// result /// } /// /// #[renderer] -/// fn fallback_renderer_not_found(prev: ErrorRendererNotFound) { -/// r_println!("No renderer for `{}`", *prev); +/// fn fallback_renderer_not_found(prev: ErrorRendererNotFound) -> RenderResult { +/// let result = RenderResult::new(); +/// writeln!(result, "No renderer for `{}`", *prev); +/// result /// } /// ``` #[proc_macro_attribute] @@ -1275,33 +1186,38 @@ pub fn register_dispatcher(input: TokenStream) -> TokenStream { /// The macro works by: /// 1. Generating a hidden struct implementing the `HelpRequest` trait. /// 2. Registering the help mapping in the global `HELP_REQUESTS` registry. -/// 3. Keeping the original function for direct calls (with a dummy `RenderResult`). +/// 3. Keeping the original function for direct calls. /// -/// Inside a `#[help]` function, you can use [`r_print!`] and [`r_println!`] -/// to write help text to the `RenderResult` buffer. +/// Inside a `#[help]` function, you must manually create a `RenderResult` +/// and return it. Use [`writeln!`](std::fmt::writeln) on the result to +/// write help text. /// /// # Syntax /// /// ```rust,ignore /// #[help] -/// fn help_my_entry(prev: MyEntry) { -/// r_println!("Usage: myapp myentry [options]"); -/// r_println!(" Does something useful."); +/// fn help_my_entry(prev: MyEntry) -> RenderResult { +/// let result = RenderResult::new(); +/// writeln!(result, "Usage: myapp myentry [options]"); +/// writeln!(result, " Does something useful."); +/// result /// } /// ``` /// /// # Example /// /// ```rust,ignore -/// use mingling::macros::{help, r_println, pack, gen_program}; -/// use mingling::{prelude::*, setup::BasicProgramSetup}; +/// use mingling::macros::{help, pack, gen_program}; +/// use mingling::{prelude::*, setup::BasicProgramSetup, RenderResult}; /// /// pack!(MyEntry = Vec<String>); /// /// #[help] -/// fn help_my_entry(prev: MyEntry) { -/// r_println!("Usage: myapp greet [name]"); -/// r_println!("Greets the user."); +/// fn help_my_entry(prev: MyEntry) -> RenderResult { +/// let result = RenderResult::new(); +/// writeln!(result, "Usage: myapp greet [name]"); +/// writeln!(result, "Greets the user."); +/// result /// } /// /// fn main() { @@ -1316,13 +1232,13 @@ pub fn register_dispatcher(input: TokenStream) -> TokenStream { /// /// - The function must have exactly one parameter (the entry type to provide help for). /// - The parameter type must be a single-segment type path (e.g., `MyEntry`, not `other::MyEntry`). -/// - The function must return `()`. +/// - The function must return [`RenderResult`]. /// - The function cannot be async. /// /// # See also /// /// - [`BasicProgramSetup`] — The setup that enables `--help` and `-h` flag processing. -/// - [`r_print!`] / [`r_println!`] — For outputting help text. +/// - [`RenderResult`] — The return type for help functions. /// - [`#[chain]`](attr.chain.html) — For processing the dispatched entry after help. /// /// [`BasicProgramSetup`]: https://docs.rs/mingling/latest/mingling/setup/struct.BasicProgramSetup.html @@ -1496,7 +1412,7 @@ pub fn derive_groupped_serialize(input: TokenStream) -> TokenStream { /// # Example /// /// ```rust,ignore -/// use mingling::macros::{dispatcher, chain, renderer, gen_program}; +/// use mingling::macros::{dispatcher, chain, renderer, gen_program, RenderResult}; /// /// dispatcher!("hello", HelloCommand => HelloEntry); /// @@ -1506,8 +1422,10 @@ pub fn derive_groupped_serialize(input: TokenStream) -> TokenStream { /// } /// /// #[renderer] -/// fn render(prev: /* ... */) { -/// r_println!("Done!"); +/// fn render(prev: /* ... */) -> RenderResult { +/// let result = RenderResult::new(); +/// writeln!(result, "Done!"); +/// result /// } /// /// // Collect everything: @@ -1612,9 +1530,11 @@ pub fn program_comp_gen(_input: TokenStream) -> TokenStream { #[allow(unused)] #[doc(hidden)] #[::mingling::macros::renderer] - pub fn __render_completion(prev: CompletionSuggest) { + pub fn __render_completion(prev: CompletionSuggest) -> ::mingling::RenderResult { + let result = ::mingling::RenderResult::default(); let (ctx, suggest) = prev.inner; ::mingling::CompletionHelper::render_suggest::<crate::ThisProgram>(ctx, suggest); + result } }; @@ -1783,9 +1703,9 @@ pub fn program_fallback_gen(_input: TokenStream) -> TokenStream { /// impl ProgramCollect for MyProgram { /// type Enum = MyProgram; /// type ResultEmpty = ResultEmpty; -/// fn render(any, r) { /* dispatches to all registered renderers */ } +/// fn render(any) -> RenderResult { /* dispatches to all registered renderers */ } /// fn do_chain(any) -> ChainProcess { /* dispatches to all registered chain steps */ } -/// fn render_help(any, r) { /* dispatches to all registered help handlers */ } +/// fn render_help(any) -> RenderResult { /* dispatches to all registered help handlers */ } /// fn has_renderer(any) -> bool { /* checks renderer registry */ } /// fn has_chain(any) -> bool { /* checks chain registry */ } /// // (with comp feature) fn do_comp(...) @@ -2020,12 +1940,15 @@ pub fn program_final_gen(_input: TokenStream) -> TokenStream { let comp = quote! {}; // Build render function arms from stored entries - let render_fn = if renderer_tokens.is_empty() { - quote! { - fn render(_any: ::mingling::AnyOutput<Self::Enum>, _renderer_inner_result: &mut ::mingling::RenderResult) {} - } - } else { - let render_arms: Vec<_> = renderer_tokens.iter().map(|entry| { + let render_fn = + if renderer_tokens.is_empty() { + quote! { + fn render(_any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { + ::mingling::RenderResult::default() + } + } + } else { + let render_arms: Vec<_> = renderer_tokens.iter().map(|entry| { let (struct_ident, variant_ident) = parse_entry_pair(entry); let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map); let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map); @@ -2034,19 +1957,19 @@ pub fn program_final_gen(_input: TokenStream) -> TokenStream { // SAFETY: The `type_id` check ensures that `any` contains a value of type `#variant_ident`, // so downcasting to `#variant_ident` is safe. let value = unsafe { any.downcast::<#downcast_ty>().unwrap_unchecked() }; - <#resolved_struct as ::mingling::Renderer>::render(value, __renderer_inner_result); + <#resolved_struct as ::mingling::Renderer>::render(value) } } }).collect(); - quote! { - fn render(any: ::mingling::AnyOutput<Self::Enum>, __renderer_inner_result: &mut ::mingling::RenderResult) { - match any.member_id { - #(#render_arms)* - _ => (), + quote! { + fn render(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { + match any.member_id { + #(#render_arms)* + _ => ::mingling::RenderResult::default(), + } } } - } - }; + }; // Build do_chain function (async and sync versions) let chain_arms_async: Vec<_> = chain_tokens.iter().map(|entry| { @@ -2162,12 +2085,12 @@ pub fn program_final_gen(_input: TokenStream) -> TokenStream { } #render_fn #do_chain_fn - fn render_help(any: ::mingling::AnyOutput<Self::Enum>, __renderer_inner_result: &mut ::mingling::RenderResult) { + fn render_help(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { #[allow(unused_imports)] #(#pathf_uses)* match any.member_id { #(#help_tokens)* - _ => (), + _ => ::mingling::RenderResult::default(), } } fn has_renderer(any: &::mingling::AnyOutput<Self::Enum>) -> bool { diff --git a/mingling_macros/src/render.rs b/mingling_macros/src/render.rs deleted file mode 100644 index 10fce7d..0000000 --- a/mingling_macros/src/render.rs +++ /dev/null @@ -1,56 +0,0 @@ -use proc_macro::TokenStream; -use quote::quote; -use syn::parse::Parser; -use syn::{Expr, Token}; - -pub fn r_print(input: TokenStream) -> TokenStream { - // Parse the input as format arguments - let parser = syn::punctuated::Punctuated::<Expr, Token![,]>::parse_terminated; - let format_args = match parser.parse(input) { - Ok(args) => args, - Err(e) => return e.to_compile_error().into(), - }; - - // Build the format macro call - let format_call = if format_args.is_empty() { - quote! { ::std::format!("") } - } else { - let args_iter = format_args.iter(); - quote! { ::std::format!(#(#args_iter),*) } - }; - - let expanded = quote! { - { - let formatted = #format_call; - ::mingling::RenderResult::print(__renderer_inner_result, &formatted) - } - }; - - expanded.into() -} - -pub fn r_println(input: TokenStream) -> TokenStream { - // Parse the input as format arguments - let parser = syn::punctuated::Punctuated::<Expr, Token![,]>::parse_terminated; - let format_args = match parser.parse(input) { - Ok(args) => args, - Err(e) => return e.to_compile_error().into(), - }; - - // Build the format macro call - let format_call = if format_args.is_empty() { - quote! { ::std::format!("") } - } else { - let args_iter = format_args.iter(); - quote! { ::std::format!(#(#args_iter),*) } - }; - - let expanded = quote! { - { - let formatted = #format_call; - ::mingling::RenderResult::println(__renderer_inner_result, &formatted) - } - }; - - expanded.into() -} diff --git a/mingling_macros/src/renderer.rs b/mingling_macros/src/renderer.rs index 880c50f..9683426 100644 --- a/mingling_macros/src/renderer.rs +++ b/mingling_macros/src/renderer.rs @@ -1,23 +1,38 @@ use proc_macro::TokenStream; -use quote::{ToTokens, quote}; +use quote::{quote, ToTokens}; use syn::spanned::Spanned; -use syn::{ItemFn, ReturnType, Signature, Type, TypePath, parse_macro_input}; +use syn::{parse_macro_input, ItemFn, ReturnType, Signature, Type, TypePath}; use crate::get_global_set; use crate::res_injection::{extract_args_info, generate_immut_resource_bindings}; -/// Extracts and returns the return type from the function signature (or None for `()` / no return type). -fn extract_return_type(sig: &Signature) -> Option<syn::Type> { +/// Validates that the function returns `::mingling::RenderResult`. +fn validate_render_result_return(sig: &Signature) -> syn::Result<()> { match &sig.output { ReturnType::Type(_, ty) => { + // Check if the return type is RenderResult match &**ty { - // `()` means no custom return type - Type::Tuple(tuple) if tuple.elems.is_empty() => None, - // Any other return type is allowed - custom_ty => Some((*custom_ty).clone()), + Type::Path(type_path) => { + let segments = &type_path.path.segments; + let last_seg = segments.last().map(|s| s.ident.to_string()); + match last_seg.as_deref() { + Some("RenderResult") => Ok(()), + _ => Err(syn::Error::new( + ty.span(), + "Renderer function must return `RenderResult`", + )), + } + } + _ => Err(syn::Error::new( + ty.span(), + "Renderer function must return `RenderResult`", + )), } } - ReturnType::Default => None, + ReturnType::Default => Err(syn::Error::new( + sig.span(), + "Renderer function must have a return type `-> RenderResult`", + )), } } @@ -44,8 +59,10 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream { Err(e) => return e.to_compile_error().into(), }; - // Validate return type – now returns Some(type) if custom type, None if () - let return_type = extract_return_type(&input_fn.sig); + // Validate that the function returns RenderResult + if let Err(e) = validate_render_result_return(&input_fn.sig) { + return e.to_compile_error().into(); + } // Get function body statements let fn_body_stmts: Vec<syn::Stmt> = input_fn.block.stmts.clone(); @@ -76,23 +93,6 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream { let immut_resource_stmts = generate_immut_resource_bindings(resources.iter(), program_type); let mut_resources: Vec<_> = resources.iter().filter(|r| r.is_mut).collect(); - // Determine public return type and the expression to return dummy_r - let (public_return_type, result_return) = if let Some(custom_ty) = &return_type { - // User specified a custom return type (e.g. -> String) - let ret_ty = quote! { #custom_ty }; - let expr = quote! { dummy_r.into() }; - (ret_ty, expr) - } else { - // Return type is () — no custom return type specified - let ret_ty = quote! { () }; - let expr = quote! { - if !dummy_r.is_empty() { - ::std::println!("{}", &*dummy_r); - } - }; - (ret_ty, expr) - }; - let inner_body_with_resources = if has_mut_resources { let mut wrapped = quote! { #(#fn_body_stmts)* }; for res in mut_resources.iter().rev() { @@ -110,8 +110,7 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream { }; // Build the Renderer::render body with resource injection - // Renderer::render returns (), output goes through __renderer_inner_result parameter. - // Resources are injected from the program context here. + // The user's body now directly creates and returns a RenderResult. let render_fn_body = if has_resources { quote! { #(#immut_resource_stmts)* @@ -121,22 +120,13 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream { quote! { #inner_body_with_resources } }; - // Build the original function body - // The original function preserves the user's signature and return type. + // The original function preserves the user's exact signature and body. // Resource parameters are passed directly by the caller, NOT injected from context. - let original_fn_body = { - quote! { - let mut dummy_r = ::mingling::RenderResult::default(); - { - let __renderer_inner_result = &mut dummy_r; - #(#fn_body_stmts)* - } - #result_return - } - }; - - // Keep the original function signature unchanged (same params as user wrote) let original_inputs = input_fn.sig.inputs.clone(); + let original_return_type = match &input_fn.sig.output { + ReturnType::Type(_, ty) => quote! { #ty }, + ReturnType::Default => unreachable!("Already validated that return type is RenderResult"), + }; let expanded = quote! { #(#fn_attrs)* @@ -149,15 +139,15 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream { impl ::mingling::Renderer for #struct_name { type Previous = #previous_type; - fn render(#prev_param: Self::Previous, __renderer_inner_result: &mut ::mingling::RenderResult) { + fn render(#prev_param: Self::Previous) -> ::mingling::RenderResult { #render_fn_body } } - // Keep the original function for internal use (without r parameter) + // Keep the original function unchanged #(#fn_attrs)* - #vis fn #fn_name(#original_inputs) -> #public_return_type { - #original_fn_body + #vis fn #fn_name(#original_inputs) -> #original_return_type { + #(#fn_body_stmts)* } }; diff --git a/mling/src/cli.rs b/mling/src/cli.rs index 3000645..bfe602c 100644 --- a/mling/src/cli.rs +++ b/mling/src/cli.rs @@ -8,13 +8,13 @@ use crate::{ }; use colored::Colorize; use mingling::{ - Groupped, Program, + Groupped, Program, RenderResult, hook::ProgramHook, - macros::{chain, help, pack, program_setup, r_println, renderer}, + macros::{chain, help, pack, program_setup, renderer}, res::ResExitCode, setup::{ExitCodeSetup, HelpFlagSetup, QuietFlagSetup, StructuralRendererSetup}, }; -use std::{env::current_dir, path::PathBuf, process::exit, str::FromStr}; +use std::{env::current_dir, io::Write, path::PathBuf, process::exit, str::FromStr}; pub fn run() { #[cfg(windows)] @@ -167,21 +167,20 @@ pub fn handle_error_dispatcher_not_found(err: ErrorDispatcherNotFound) -> Next { } #[renderer] -pub fn render_mling_help(_prev: ResultMlingHelp, ec: &mut ResExitCode) { - r_println!("{}", markdown(include_str!("helps/mling_help.txt"))); +pub fn render_mling_help(_prev: ResultMlingHelp, ec: &mut ResExitCode) -> RenderResult { + let mut result = RenderResult::default(); + writeln!(result, "{}", markdown(include_str!("helps/mling_help.txt"))).ok(); ec.exit_code = 0; + result } #[renderer] -pub fn render_unknown_command(prev: ResultUnknownCommand, ec: &mut ResExitCode) { - r_println!( - "{}", - eformat_cargo!("no such command: `{}`", prev.bright_yellow().bold()) - ); - r_println!(); - r_println!( - "{}", - hformat_cargo!("view all commands with `cargo help mling`") - ); +pub fn render_unknown_command(prev: ResultUnknownCommand, ec: &mut ResExitCode) -> RenderResult { + let mut result = RenderResult::default(); + writeln!(result, "{}", eformat_cargo!("no such command: `{}`", prev.bright_yellow().bold())).ok(); + writeln!(result).ok(); + writeln!(result, "{}", hformat_cargo!("view all commands with `cargo help mling`")) + .ok(); ec.exit_code = 101; + result } diff --git a/mling/src/errors/io_error.rs b/mling/src/errors/io_error.rs index 92a3951..3b6b8fb 100644 --- a/mling/src/errors/io_error.rs +++ b/mling/src/errors/io_error.rs @@ -1,7 +1,9 @@ use mingling::{ - macros::{group, r_println, renderer}, + macros::{group, renderer}, res::ResExitCode, + RenderResult, }; +use std::io::Write as _; use crate::eformat_cargo; @@ -49,167 +51,174 @@ pub const EC_IO_ERR_OUT_OF_MEMORY: i32 = 1037; pub const EC_IO_ERR_OTHER: i32 = 1038; #[renderer] -pub fn render_error_io(err: ErrorIo, ec: &mut ResExitCode) { +pub fn render_error_io(err: ErrorIo, ec: &mut ResExitCode) -> RenderResult { + let mut result = RenderResult::default(); match err.kind() { std::io::ErrorKind::NotFound => { - r_println!("{}", eformat_cargo!("file or directory not found")); + writeln!(result, "{}", eformat_cargo!("file or directory not found")).ok(); ec.exit_code = EC_IO_ERR_NOT_FOUND; } std::io::ErrorKind::PermissionDenied => { - r_println!("{}", eformat_cargo!("permission denied")); + writeln!(result, "{}", eformat_cargo!("permission denied")).ok(); ec.exit_code = EC_IO_ERR_PERMISSION_DENIED; } std::io::ErrorKind::ConnectionRefused => { - r_println!("{}", eformat_cargo!("connection refused")); + writeln!(result, "{}", eformat_cargo!("connection refused")).ok(); ec.exit_code = EC_IO_ERR_CONNECTION_REFUSED; } std::io::ErrorKind::ConnectionReset => { - r_println!("{}", eformat_cargo!("connection reset")); + writeln!(result, "{}", eformat_cargo!("connection reset")).ok(); ec.exit_code = EC_IO_ERR_CONNECTION_RESET; } std::io::ErrorKind::HostUnreachable => { - r_println!("{}", eformat_cargo!("host unreachable")); + writeln!(result, "{}", eformat_cargo!("host unreachable")).ok(); ec.exit_code = EC_IO_ERR_HOST_UNREACHABLE; } std::io::ErrorKind::NetworkUnreachable => { - r_println!("{}", eformat_cargo!("network unreachable")); + writeln!(result, "{}", eformat_cargo!("network unreachable")).ok(); ec.exit_code = EC_IO_ERR_NETWORK_UNREACHABLE; } std::io::ErrorKind::ConnectionAborted => { - r_println!("{}", eformat_cargo!("connection aborted")); + writeln!(result, "{}", eformat_cargo!("connection aborted")).ok(); ec.exit_code = EC_IO_ERR_CONNECTION_ABORTED; } std::io::ErrorKind::NotConnected => { - r_println!("{}", eformat_cargo!("not connected")); + writeln!(result, "{}", eformat_cargo!("not connected")).ok(); ec.exit_code = EC_IO_ERR_NOT_CONNECTED; } std::io::ErrorKind::AddrInUse => { - r_println!("{}", eformat_cargo!("address in use")); + writeln!(result, "{}", eformat_cargo!("address in use")).ok(); ec.exit_code = EC_IO_ERR_ADDR_IN_USE; } std::io::ErrorKind::AddrNotAvailable => { - r_println!("{}", eformat_cargo!("address not available")); + writeln!(result, "{}", eformat_cargo!("address not available")).ok(); ec.exit_code = EC_IO_ERR_ADDR_NOT_AVAILABLE; } std::io::ErrorKind::NetworkDown => { - r_println!("{}", eformat_cargo!("network down")); + writeln!(result, "{}", eformat_cargo!("network down")).ok(); ec.exit_code = EC_IO_ERR_NETWORK_DOWN; } std::io::ErrorKind::BrokenPipe => { - r_println!("{}", eformat_cargo!("broken pipe")); + writeln!(result, "{}", eformat_cargo!("broken pipe")).ok(); ec.exit_code = EC_IO_ERR_BROKEN_PIPE; } std::io::ErrorKind::AlreadyExists => { - r_println!("{}", eformat_cargo!("file or directory already exists")); + writeln!( + result, + "{}", + eformat_cargo!("file or directory already exists") + ) + .ok(); ec.exit_code = EC_IO_ERR_ALREADY_EXISTS; } std::io::ErrorKind::WouldBlock => { - r_println!("{}", eformat_cargo!("operation would block")); + writeln!(result, "{}", eformat_cargo!("operation would block")).ok(); ec.exit_code = EC_IO_ERR_WOULD_BLOCK; } std::io::ErrorKind::NotADirectory => { - r_println!("{}", eformat_cargo!("not a directory")); + writeln!(result, "{}", eformat_cargo!("not a directory")).ok(); ec.exit_code = EC_IO_ERR_NOT_A_DIRECTORY; } std::io::ErrorKind::IsADirectory => { - r_println!("{}", eformat_cargo!("is a directory")); + writeln!(result, "{}", eformat_cargo!("is a directory")).ok(); ec.exit_code = EC_IO_ERR_IS_A_DIRECTORY; } std::io::ErrorKind::DirectoryNotEmpty => { - r_println!("{}", eformat_cargo!("directory not empty")); + writeln!(result, "{}", eformat_cargo!("directory not empty")).ok(); ec.exit_code = EC_IO_ERR_DIRECTORY_NOT_EMPTY; } std::io::ErrorKind::ReadOnlyFilesystem => { - r_println!("{}", eformat_cargo!("read-only filesystem")); + writeln!(result, "{}", eformat_cargo!("read-only filesystem")).ok(); ec.exit_code = EC_IO_ERR_READ_ONLY_FILESYSTEM; } std::io::ErrorKind::StaleNetworkFileHandle => { - r_println!("{}", eformat_cargo!("stale network file handle")); + writeln!(result, "{}", eformat_cargo!("stale network file handle")).ok(); ec.exit_code = EC_IO_ERR_STALE_NETWORK_FILE_HANDLE; } std::io::ErrorKind::InvalidInput => { - r_println!("{}", eformat_cargo!("invalid input")); + writeln!(result, "{}", eformat_cargo!("invalid input")).ok(); ec.exit_code = EC_IO_ERR_INVALID_INPUT; } std::io::ErrorKind::InvalidData => { - r_println!("{}", eformat_cargo!("invalid data")); + writeln!(result, "{}", eformat_cargo!("invalid data")).ok(); ec.exit_code = EC_IO_ERR_INVALID_DATA; } std::io::ErrorKind::TimedOut => { - r_println!("{}", eformat_cargo!("timed out")); + writeln!(result, "{}", eformat_cargo!("timed out")).ok(); ec.exit_code = EC_IO_ERR_TIMED_OUT; } std::io::ErrorKind::WriteZero => { - r_println!("{}", eformat_cargo!("write zero")); + writeln!(result, "{}", eformat_cargo!("write zero")).ok(); ec.exit_code = EC_IO_ERR_WRITE_ZERO; } std::io::ErrorKind::StorageFull => { - r_println!("{}", eformat_cargo!("storage full")); + writeln!(result, "{}", eformat_cargo!("storage full")).ok(); ec.exit_code = EC_IO_ERR_STORAGE_FULL; } std::io::ErrorKind::NotSeekable => { - r_println!("{}", eformat_cargo!("not seekable")); + writeln!(result, "{}", eformat_cargo!("not seekable")).ok(); ec.exit_code = EC_IO_ERR_NOT_SEEKABLE; } std::io::ErrorKind::QuotaExceeded => { - r_println!("{}", eformat_cargo!("quota exceeded")); + writeln!(result, "{}", eformat_cargo!("quota exceeded")).ok(); ec.exit_code = EC_IO_ERR_QUOTA_EXCEEDED; } std::io::ErrorKind::FileTooLarge => { - r_println!("{}", eformat_cargo!("file too large")); + writeln!(result, "{}", eformat_cargo!("file too large")).ok(); ec.exit_code = EC_IO_ERR_FILE_TOO_LARGE; } std::io::ErrorKind::ResourceBusy => { - r_println!("{}", eformat_cargo!("resource busy")); + writeln!(result, "{}", eformat_cargo!("resource busy")).ok(); ec.exit_code = EC_IO_ERR_RESOURCE_BUSY; } std::io::ErrorKind::ExecutableFileBusy => { - r_println!("{}", eformat_cargo!("executable file busy")); + writeln!(result, "{}", eformat_cargo!("executable file busy")).ok(); ec.exit_code = EC_IO_ERR_EXECUTABLE_FILE_BUSY; } std::io::ErrorKind::Deadlock => { - r_println!("{}", eformat_cargo!("deadlock")); + writeln!(result, "{}", eformat_cargo!("deadlock")).ok(); ec.exit_code = EC_IO_ERR_DEADLOCK; } std::io::ErrorKind::CrossesDevices => { - r_println!("{}", eformat_cargo!("crosses devices")); + writeln!(result, "{}", eformat_cargo!("crosses devices")).ok(); ec.exit_code = EC_IO_ERR_CROSSES_DEVICES; } std::io::ErrorKind::TooManyLinks => { - r_println!("{}", eformat_cargo!("too many links")); + writeln!(result, "{}", eformat_cargo!("too many links")).ok(); ec.exit_code = EC_IO_ERR_TOO_MANY_LINKS; } std::io::ErrorKind::InvalidFilename => { - r_println!("{}", eformat_cargo!("invalid filename")); + writeln!(result, "{}", eformat_cargo!("invalid filename")).ok(); ec.exit_code = EC_IO_ERR_INVALID_FILENAME; } std::io::ErrorKind::ArgumentListTooLong => { - r_println!("{}", eformat_cargo!("argument list too long")); + writeln!(result, "{}", eformat_cargo!("argument list too long")).ok(); ec.exit_code = EC_IO_ERR_ARGUMENT_LIST_TOO_LONG; } std::io::ErrorKind::Interrupted => { - r_println!("{}", eformat_cargo!("interrupted")); + writeln!(result, "{}", eformat_cargo!("interrupted")).ok(); ec.exit_code = EC_IO_ERR_INTERRUPTED; } std::io::ErrorKind::Unsupported => { - r_println!("{}", eformat_cargo!("unsupported")); + writeln!(result, "{}", eformat_cargo!("unsupported")).ok(); ec.exit_code = EC_IO_ERR_UNSUPPORTED; } std::io::ErrorKind::UnexpectedEof => { - r_println!("{}", eformat_cargo!("unexpected end of file")); + writeln!(result, "{}", eformat_cargo!("unexpected end of file")).ok(); ec.exit_code = EC_IO_ERR_UNEXPECTED_EOF; } std::io::ErrorKind::OutOfMemory => { - r_println!("{}", eformat_cargo!("out of memory")); + writeln!(result, "{}", eformat_cargo!("out of memory")).ok(); ec.exit_code = EC_IO_ERR_OUT_OF_MEMORY; } std::io::ErrorKind::Other => { - r_println!("{}", eformat_cargo!(err.to_string())); + writeln!(result, "{}", eformat_cargo!(err.to_string())).ok(); ec.exit_code = EC_IO_ERR_OTHER; } _ => { - r_println!("{}", eformat_cargo!(err.to_string())); + writeln!(result, "{}", eformat_cargo!(err.to_string())).ok(); ec.exit_code = EC_IO_ERR_OTHER; } } + result } diff --git a/mling/src/lib.rs b/mling/src/lib.rs index 1bf38a7..1de1228 100644 --- a/mling/src/lib.rs +++ b/mling/src/lib.rs @@ -1,7 +1,7 @@ #![allow(unused_imports)] use mingling::{ - macros::{chain, gen_program, pack, r_println, renderer}, + macros::{chain, gen_program, pack, renderer}, res::ResExitCode, }; diff --git a/mling/src/proj_mgr/generator.rs b/mling/src/proj_mgr/generator.rs index 48a1753..e560ae0 100644 --- a/mling/src/proj_mgr/generator.rs +++ b/mling/src/proj_mgr/generator.rs @@ -1,11 +1,12 @@ use std::path::{self, PathBuf}; use mingling::{ - Groupped, - macros::{chain, pack, r_println, renderer, route}, + macros::{chain, pack, renderer, route}, + Groupped, RenderResult, }; +use std::io::Write as _; -use crate::{Next, proj_mgr::EntryGenerateProject, res::ResCurrentDir}; +use crate::{proj_mgr::EntryGenerateProject, res::ResCurrentDir, Next}; pack!(StateGenerateProjectReady = PathBuf); pack!(ResultGenerateProjectChecklistCreated = PathBuf); @@ -35,12 +36,22 @@ pub fn handle_state_gen_proj_ready(prev: StateGenerateProjectReady) -> Next { } #[renderer] -pub fn render_gen_proj_checklist_created(result: ResultGenerateProjectChecklistCreated) { - r_println!( +pub fn render_gen_proj_checklist_created( + result: ResultGenerateProjectChecklistCreated, +) -> RenderResult { + let mut res = RenderResult::default(); + writeln!( + res, "Successfully create {} at \"{}\"", CHECK_LIST_NAME, result.to_string_lossy() - ); - r_println!(""); - r_println!("Please fill in {CHECK_LIST_NAME} and run `mling gen` again to continue generating"); + ) + .ok(); + writeln!(res).ok(); + writeln!( + res, + "Please fill in {CHECK_LIST_NAME} and run `mling gen` again to continue generating" + ) + .ok(); + res } diff --git a/mling/src/proj_mgr/show_binaries.rs b/mling/src/proj_mgr/show_binaries.rs index d6872cf..fd2f406 100644 --- a/mling/src/proj_mgr/show_binaries.rs +++ b/mling/src/proj_mgr/show_binaries.rs @@ -2,18 +2,19 @@ use std::path::PathBuf; use colored::Colorize; use mingling::{ - Groupped, - macros::{chain, pack, r_print, r_println, renderer}, + macros::{chain, pack, renderer}, + Groupped, RenderResult, }; use serde::Serialize; +use std::io::Write as _; use crate::{ - Next, proj_mgr::{ + metadata::{read_metadata, CargoLockFile}, EntryShowBinaries, - metadata::{CargoLockFile, read_metadata}, }, res::ResManifestPath, + Next, }; #[derive(Serialize, Groupped)] @@ -58,16 +59,20 @@ pub fn handle_show_binaries(_args: EntryShowBinaries, manifest_path: &ResManifes } #[renderer] -pub fn render_binaries(binaries: ResultBinaries) -> String { - r_println!("{}", "Binaries:".bright_cyan().bold()); +pub fn render_binaries(binaries: ResultBinaries) -> RenderResult { + let mut result = RenderResult::default(); + writeln!(result, "{}", "Binaries:".bright_cyan().bold()).ok(); if let Some(max_name_len) = binaries.binaries.iter().map(|b| b.name.len()).max() { for binary in &binaries.binaries { - r_println!( + writeln!( + result, " {:width$} `{}`", binary.name.bright_yellow().bold(), binary.path.display().to_string().italic(), width = max_name_len - ); + ) + .ok(); } } + result } diff --git a/mling/src/proj_mgr/show_directories.rs b/mling/src/proj_mgr/show_directories.rs index e84bf69..5bfb090 100644 --- a/mling/src/proj_mgr/show_directories.rs +++ b/mling/src/proj_mgr/show_directories.rs @@ -1,14 +1,15 @@ use colored::Colorize; use mingling::{ - Groupped, - macros::{chain, pack, r_println, renderer}, + macros::{chain, pack, renderer}, + Groupped, RenderResult, }; use serde::Serialize; +use std::io::Write as _; use crate::{ - Next, - proj_mgr::{EntryShowTargetDirectories, EntryShowWorkspaceDirectory, metadata::read_metadata}, + proj_mgr::{metadata::read_metadata, EntryShowTargetDirectories, EntryShowWorkspaceDirectory}, res::ResManifestPath, + Next, }; #[derive(Serialize, Groupped)] @@ -46,11 +47,15 @@ pub fn handle_show_target_directory( } #[renderer] -pub fn render_workspace_directory(prev: ResultWorkspaceDirectory) { - r_println!("{}", prev.path.bright_cyan().bold()); +pub fn render_workspace_directory(prev: ResultWorkspaceDirectory) -> RenderResult { + let mut result = RenderResult::default(); + writeln!(result, "{}", prev.path.bright_cyan().bold()).ok(); + result } #[renderer] -pub fn render_target_directory(prev: ResultTargetDirectory) { - r_println!("{}", prev.path.bright_cyan().bold()); +pub fn render_target_directory(prev: ResultTargetDirectory) -> RenderResult { + let mut result = RenderResult::default(); + writeln!(result, "{}", prev.path.bright_cyan().bold()).ok(); + result } |
