diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-04-22 13:27:43 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-04-22 13:30:13 +0800 |
| commit | 3785202ec5b949cba5501b20729b16f4c29ea626 (patch) | |
| tree | 2a276df54128e73557463af54d626a1a7c250e94 | |
| parent | c2f9fb47832c5e12fe37762616c81b016de00464 (diff) | |
Add new_with_args and dispatch_args_dynamic to Program
Remove Display bound from Group type parameter and delete
dispatcher_render macro. This is a breaking change.
| -rw-r--r-- | CHANGELOG.md | 18 | ||||
| -rw-r--r-- | mingling/src/lib.rs | 2 | ||||
| -rw-r--r-- | mingling_core/src/any.rs | 88 | ||||
| -rw-r--r-- | mingling_core/src/asset/chain.rs | 7 | ||||
| -rw-r--r-- | mingling_core/src/asset/chain/error.rs | 33 | ||||
| -rw-r--r-- | mingling_core/src/asset/dispatcher.rs | 5 | ||||
| -rw-r--r-- | mingling_core/src/program.rs | 65 | ||||
| -rw-r--r-- | mingling_core/src/program/exec.rs | 76 | ||||
| -rw-r--r-- | mingling_core/src/program/setup.rs | 4 | ||||
| -rw-r--r-- | mingling_core/src/program/string_vec.rs | 56 | ||||
| -rw-r--r-- | mingling_macros/src/dispatcher_chain.rs | 78 | ||||
| -rw-r--r-- | mingling_macros/src/lib.rs | 5 |
12 files changed, 187 insertions, 250 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cc9b11..c5f70af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelogs +### Release 0.1.7 + +#### Fixes: + +None + +#### Features: + +1. Added function `new_with_args` to `Program` +2. Added function `dispatch_args_dynamic` to `Program` + +#### **BREAKING CHANGES**: + +1. Removed macro `dispatcher_render!` from `mingling_macros` +2. The `<..., Group>` in `Program<Collect, Group>` no longer requires `std::fmt::Display` + +--- + ### Release 0.1.6 `Mingling` 0.1.6 primarily focuses on optimizing the writing experience and code completion. diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs index 91bce8d..d581e83 100644 --- a/mingling/src/lib.rs +++ b/mingling/src/lib.rs @@ -79,8 +79,6 @@ pub mod macros { pub use mingling_macros::completion; /// Used to create a dispatcher that routes to a `Chain` pub use mingling_macros::dispatcher; - /// Used to create a dispatcher that routes to a `Renderer` - pub use mingling_macros::dispatcher_render; /// Used to collect data and create a command-line context pub use mingling_macros::gen_program; /// Used to create a `Node` struct via a literal diff --git a/mingling_core/src/any.rs b/mingling_core/src/any.rs index 57eddfb..b077fba 100644 --- a/mingling_core/src/any.rs +++ b/mingling_core/src/any.rs @@ -1,5 +1,3 @@ -use std::fmt::Display; - #[cfg(feature = "general_renderer")] use serde::Serialize; @@ -19,19 +17,13 @@ pub mod group; /// - Under the `general_renderer` feature, the passed value must ensure it implements `serde::Serialize` /// - It is recommended to use the `pack!` macro from [mingling_macros](https://crates.io/crates/mingling_macros) to create types that can be converted to `AnyOutput`, which guarantees runtime safety #[derive(Debug)] -pub struct AnyOutput<G> -where - G: Display, -{ +pub struct AnyOutput<G> { pub(crate) inner: Box<dyn std::any::Any + Send + 'static>, pub type_id: std::any::TypeId, pub member_id: G, } -impl<G> AnyOutput<G> -where - G: Display, -{ +impl<G> AnyOutput<G> { /// Create an AnyOutput from a `Send + Groupped<G> + Serialize` type #[cfg(feature = "general_renderer")] pub fn new<T>(value: T) -> Self @@ -96,10 +88,7 @@ where } } -impl<G> std::ops::Deref for AnyOutput<G> -where - G: Display, -{ +impl<G> std::ops::Deref for AnyOutput<G> { type Target = dyn std::any::Any + Send + 'static; fn deref(&self) -> &Self::Target { @@ -107,10 +96,7 @@ where } } -impl<G> std::ops::DerefMut for AnyOutput<G> -where - G: Display, -{ +impl<G> std::ops::DerefMut for AnyOutput<G> { fn deref_mut(&mut self) -> &mut Self::Target { &mut *self.inner } @@ -122,13 +108,7 @@ where /// - Returns `Ok((`[`AnyOutput`](./struct.AnyOutput.html)`, `[`Next::Chain`](./enum.Next.html)`))` to continue execution with this type next /// - Returns `Ok((`[`AnyOutput`](./struct.AnyOutput.html)`, `[`Next::Renderer`](./enum.Next.html)`))` to render this type next and output to the terminal /// - Returns `Err(`[`ChainProcessError`](./error/enum.ChainProcessError.html)`]` to terminate the program directly -pub enum ChainProcess<G> -where - G: Display, -{ - Ok((AnyOutput<G>, Next)), - Err(ChainProcessError), -} +pub type ChainProcess<G> = Result<(AnyOutput<G>, Next), ChainProcessError>; /// Indicates the next step after processing /// @@ -139,60 +119,8 @@ pub enum Next { Renderer, } -impl<G> ChainProcess<G> -where - G: Display, -{ - /// Returns true if the result is Ok (has a next step) - pub fn is_next(&self) -> bool { - matches!(self, Self::Ok(_)) - } - - /// Returns true if the result is an error - pub fn is_err(&self) -> bool { - matches!(self, Self::Err(_)) - } - - /// Returns the next step if the result is Ok - pub fn next(&self) -> Option<&Next> { - match self { - Self::Ok((_, next)) => Some(next), - Self::Err(_) => None, - } - } - - /// Returns the error if the result is Err - pub fn err(&self) -> Option<&ChainProcessError> { - match self { - Self::Ok(_) => None, - Self::Err(err) => Some(err), - } - } - - /// Unwraps the result, panics if it's an error - pub fn unwrap(self) -> (AnyOutput<G>, Next) { - match self { - Self::Ok(tuple) => tuple, - Self::Err(_) => panic!("called `ChainProcess2::unwrap()` on an `Error` value"), - } - } - - /// Returns the Ok value or a provided default - pub fn unwrap_or(self, default: (AnyOutput<G>, Next)) -> (AnyOutput<G>, Next) { - match self { - Self::Ok(tuple) => tuple, - Self::Err(_) => default, - } - } - - /// Returns the Ok value or computes it from the error - pub fn unwrap_or_else<F>(self, f: F) -> (AnyOutput<G>, Next) - where - F: FnOnce(ChainProcessError) -> (AnyOutput<G>, Next), - { - match self { - Self::Ok(tuple) => tuple, - Self::Err(err) => f(err), - } +impl<G> From<AnyOutput<G>> for ChainProcess<G> { + fn from(value: AnyOutput<G>) -> Self { + ChainProcess::Ok((value, Next::Chain)) } } diff --git a/mingling_core/src/asset/chain.rs b/mingling_core/src/asset/chain.rs index c41b716..1b488fe 100644 --- a/mingling_core/src/asset/chain.rs +++ b/mingling_core/src/asset/chain.rs @@ -1,15 +1,10 @@ -use std::fmt::Display; - use crate::ChainProcess; #[doc(hidden)] pub mod error; /// Takes over a type (G: Previous) and converts it to another [AnyOutput](./struct.AnyOutput.html) -pub trait Chain<G> -where - G: Display, -{ +pub trait Chain<G> { /// The previous type in the chain type Previous; diff --git a/mingling_core/src/asset/chain/error.rs b/mingling_core/src/asset/chain/error.rs index cc22bdc..5221e57 100644 --- a/mingling_core/src/asset/chain/error.rs +++ b/mingling_core/src/asset/chain/error.rs @@ -1,3 +1,5 @@ +use crate::error::{ProgramExecuteError, ProgramInternalExecuteError}; + #[derive(thiserror::Error, Debug)] pub enum ChainProcessError { #[error("Other error: {0}")] @@ -6,3 +8,34 @@ pub enum ChainProcessError { #[error("IO error: {0}")] IO(#[from] std::io::Error), } + +impl From<ProgramExecuteError> for ChainProcessError { + fn from(value: ProgramExecuteError) -> Self { + match value { + ProgramExecuteError::DispatcherNotFound => { + ChainProcessError::Other("DispatcherNotFound".into()) + } + ProgramExecuteError::RendererNotFound(r) => { + ChainProcessError::Other(format!("RendererNotFound: {}", r)) + } + ProgramExecuteError::Other(e) => ChainProcessError::Other(e), + } + } +} + +impl From<ProgramInternalExecuteError> for ChainProcessError { + fn from(value: ProgramInternalExecuteError) -> Self { + match value { + ProgramInternalExecuteError::DispatcherNotFound => { + ChainProcessError::Other("DispatcherNotFound".into()) + } + ProgramInternalExecuteError::RendererNotFound(r) => { + ChainProcessError::Other(format!("RendererNotFound: {}", r)) + } + ProgramInternalExecuteError::Other(e) => ChainProcessError::Other(e), + ProgramInternalExecuteError::IO(e) => { + ChainProcessError::Other(format!("IOError: {:?}", e)) + } + } + } +} diff --git a/mingling_core/src/asset/dispatcher.rs b/mingling_core/src/asset/dispatcher.rs index 0f4675c..72b31e9 100644 --- a/mingling_core/src/asset/dispatcher.rs +++ b/mingling_core/src/asset/dispatcher.rs @@ -6,10 +6,7 @@ use crate::{ChainProcess, Program, asset::node::Node}; /// /// Note: If you are using [mingling_macros](https://crates.io/crates/mingling_macros), /// you can use the `dispatcher!("node.subnode", CommandType => Entry)` macro to declare a `Dispatcher` -pub trait Dispatcher<G> -where - G: Display, -{ +pub trait Dispatcher<G> { /// Returns a command node for matching user input fn node(&self) -> Node; diff --git a/mingling_core/src/program.rs b/mingling_core/src/program.rs index db5957b..d655c74 100644 --- a/mingling_core/src/program.rs +++ b/mingling_core/src/program.rs @@ -8,8 +8,9 @@ use crate::error::GeneralRendererSerializeError; use std::env; use crate::{ - AnyOutput, ChainProcess, RenderResult, asset::dispatcher::Dispatcher, - error::ProgramExecuteError, + AnyOutput, ChainProcess, RenderResult, + asset::dispatcher::Dispatcher, + error::{ChainProcessError, ProgramExecuteError}, }; use std::{fmt::Display, sync::OnceLock}; @@ -27,13 +28,16 @@ pub use config::*; mod flag; pub use flag::*; +mod string_vec; +pub use string_vec::*; + /// Global static reference to the current program instance static THIS_PROGRAM: OnceLock<Option<Box<dyn std::any::Any + Send + Sync>>> = OnceLock::new(); /// Returns a reference to the current program instance, panics if not set. pub fn this<C>() -> &'static Program<C, C> where - C: ProgramCollect + Display + 'static, + C: ProgramCollect + 'static, { try_get_this_program().expect("Program not initialized") } @@ -41,7 +45,7 @@ where /// Returns a reference to the current program instance, if set. fn try_get_this_program<C>() -> Option<&'static Program<C, C>> where - C: ProgramCollect + Display + 'static, + C: ProgramCollect + 'static, { THIS_PROGRAM .get()? @@ -54,7 +58,6 @@ where pub struct Program<C, G> where C: ProgramCollect, - G: Display, { pub(crate) collect: std::marker::PhantomData<C>, pub(crate) group: std::marker::PhantomData<G>, @@ -72,26 +75,31 @@ where impl<C, G> Program<C, G> where C: ProgramCollect<Enum = G>, - G: Display, { - /// Creates a new Program instance, initializing args from environment. + /// Creates a new Program instance, initializing command-line arguments from the environment. pub fn new() -> Self { + #[cfg(not(windows))] + return Self::new_with_args(env::args().collect::<Vec<String>>()); + + #[cfg(windows)] + return Self::new_with_args({ + std::env::args_os() + .map(|arg| { + use std::os::windows::ffi::OsStrExt; + + let wide: Vec<u16> = arg.encode_wide().collect(); + String::from_utf16_lossy(&wide) + }) + .collect() + }); + } + + /// Creates a new Program instance with the provided command-line arguments. + pub fn new_with_args(args: impl Into<StringVec>) -> Self { Program { collect: std::marker::PhantomData, group: std::marker::PhantomData, - #[cfg(not(windows))] - args: env::args().collect(), - #[cfg(windows)] - args: { - std::env::args_os() - .map(|arg| { - use std::os::windows::ffi::OsStrExt; - - let wide: Vec<u16> = arg.encode_wide().collect(); - String::from_utf16_lossy(&wide) - }) - .collect() - }, + args: args.into().into(), dispatcher: Vec::new(), stdout_setting: Default::default(), user_context: Default::default(), @@ -116,10 +124,21 @@ where .unwrap() } - // Get all registered dispatcher names from the program + /// Get all registered dispatcher names from the program pub fn get_nodes(&self) -> Vec<(String, &(dyn Dispatcher<G> + Send + Sync))> { get_nodes(self) } + + /// Dynamically dispatch input arguments to registered entry types + pub fn dispatch_args_dynamic( + &self, + args: impl Into<StringVec>, + ) -> Result<AnyOutput<G>, ChainProcessError> { + match exec::dispatch_args_dynamic(self, args.into().into()) { + Ok(ok) => Ok(ok), + Err(e) => Err(e.into()), + } + } } // Async program @@ -127,7 +146,6 @@ where impl<C, G> Program<C, G> where C: ProgramCollect<Enum = G>, - G: Display, { /// Sets the current program instance and runs the provided async function. async fn set_instance_and_run<F, Fut>(self, f: F) -> Fut::Output @@ -201,7 +219,6 @@ where impl<C, G> Program<C, G> where C: ProgramCollect<Enum = G>, - G: Display, { /// Sets the current program instance and runs the provided function. fn set_instance_and_run<F, R>(self, f: F) -> R @@ -386,7 +403,7 @@ macro_rules! __dispatch_program_chains { } /// Get all registered dispatcher names from the program -pub fn get_nodes<C: ProgramCollect<Enum = G>, G: Display>( +pub fn get_nodes<C: ProgramCollect<Enum = G>, G>( program: &Program<C, G>, ) -> Vec<(String, &(dyn Dispatcher<G> + Send + Sync))> { program diff --git a/mingling_core/src/program/exec.rs b/mingling_core/src/program/exec.rs index 8ab2036..68a694e 100644 --- a/mingling_core/src/program/exec.rs +++ b/mingling_core/src/program/exec.rs @@ -1,7 +1,5 @@ #![allow(clippy::borrowed_box)] -use std::fmt::Display; - use crate::{ AnyOutput, ChainProcess, Dispatcher, Next, Program, ProgramCollect, RenderResult, error::ProgramInternalExecuteError, @@ -16,30 +14,10 @@ pub async fn exec<C, G>( ) -> Result<RenderResult, ProgramInternalExecuteError> where C: ProgramCollect<Enum = G>, - G: Display, { - let mut current; + let mut current = dispatch_args_dynamic(program, program.args.clone())?; let mut stop_next = false; - // Match user input - match match_user_input(program, program.args.clone()) { - Ok((dispatcher, args)) => { - // Entry point - current = match dispatcher.begin(args) { - ChainProcess::Ok((any, Next::Renderer)) => { - return Ok(render::<C, G>(program, any)); - } - ChainProcess::Ok((any, Next::Chain)) => any, - ChainProcess::Err(e) => return Err(e.into()), - }; - } - Err(ProgramInternalExecuteError::DispatcherNotFound) => { - // No matching Dispatcher is found - current = C::build_dispatcher_not_found(program.args.clone()); - } - Err(e) => return Err(e), - }; - loop { let final_exec = stop_next; @@ -76,30 +54,10 @@ where pub fn exec<C, G>(program: &Program<C, G>) -> Result<RenderResult, ProgramInternalExecuteError> where C: ProgramCollect<Enum = G>, - G: Display, { - let mut current; + let mut current = dispatch_args_dynamic(program, program.args.clone())?; let mut stop_next = false; - // Match user input - match match_user_input(program, program.args.clone()) { - Ok((dispatcher, args)) => { - // Entry point - current = match dispatcher.begin(args) { - ChainProcess::Ok((any, Next::Renderer)) => { - return Ok(render::<C, G>(program, any)); - } - ChainProcess::Ok((any, Next::Chain)) => any, - ChainProcess::Err(e) => return Err(e.into()), - }; - } - Err(ProgramInternalExecuteError::DispatcherNotFound) => { - // No matching Dispatcher is found - current = C::build_dispatcher_not_found(program.args.clone()); - } - Err(e) => return Err(e), - }; - loop { let final_exec = stop_next; @@ -132,15 +90,39 @@ where Ok(RenderResult::default()) } +/// Dynamically dispatch input arguments to registered entry types +pub(crate) fn dispatch_args_dynamic<C, G>( + program: &Program<C, G>, + args: Vec<String>, +) -> Result<AnyOutput<G>, ProgramInternalExecuteError> +where + C: ProgramCollect<Enum = G>, +{ + let next = match match_user_input(program, args) { + Ok((dispatcher, args)) => { + // Entry point + match dispatcher.begin(args) { + ChainProcess::Ok((any, _)) => any, + ChainProcess::Err(e) => return Err(e.into()), + } + } + Err(ProgramInternalExecuteError::DispatcherNotFound) => { + // No matching Dispatcher is found + C::build_dispatcher_not_found(program.args.clone()) + } + Err(e) => return Err(e), + }; + Ok(next) +} + /// Match user input against registered dispatchers and return the matched dispatcher and remaining arguments. #[allow(clippy::type_complexity)] -pub fn match_user_input<C, G>( +pub(crate) fn match_user_input<C, G>( program: &Program<C, G>, args: Vec<String>, ) -> Result<(&(dyn Dispatcher<G> + Send + Sync), Vec<String>), ProgramInternalExecuteError> where C: ProgramCollect<Enum = G>, - G: Display, { let nodes = program.get_nodes(); let command = format!("{} ", args.join(" ")); @@ -180,7 +162,7 @@ where #[inline(always)] #[allow(unused_variables)] -fn render<C: ProgramCollect<Enum = G>, G: Display>( +fn render<C: ProgramCollect<Enum = G>, G>( program: &Program<C, G>, any: AnyOutput<G>, ) -> RenderResult { diff --git a/mingling_core/src/program/setup.rs b/mingling_core/src/program/setup.rs index 7b534f3..f095ed3 100644 --- a/mingling_core/src/program/setup.rs +++ b/mingling_core/src/program/setup.rs @@ -1,5 +1,3 @@ -use std::fmt::Display; - use crate::{ProgramCollect, program::Program}; mod basic; @@ -13,7 +11,6 @@ pub use general_renderer::*; pub trait ProgramSetup<C, G> where C: ProgramCollect, - G: Display, { fn setup(&mut self, program: &mut Program<C, G>); } @@ -21,7 +18,6 @@ where impl<C, G> Program<C, G> where C: ProgramCollect, - G: Display, { /// Load and execute init logic pub fn with_setup<S: ProgramSetup<C, G> + 'static>(&mut self, mut setup: S) -> S { diff --git a/mingling_core/src/program/string_vec.rs b/mingling_core/src/program/string_vec.rs new file mode 100644 index 0000000..478ad74 --- /dev/null +++ b/mingling_core/src/program/string_vec.rs @@ -0,0 +1,56 @@ +#[derive(Debug, Clone)] +pub struct StringVec { + vec: Vec<String>, +} + +impl std::ops::Deref for StringVec { + type Target = Vec<String>; + + fn deref(&self) -> &Self::Target { + &self.vec + } +} + +impl From<StringVec> for Vec<String> { + fn from(val: StringVec) -> Self { + val.vec + } +} + +impl<const N: usize> From<[&str; N]> for StringVec { + fn from(slice: [&str; N]) -> Self { + StringVec { + vec: slice.iter().map(|&s| s.to_string()).collect(), + } + } +} + +impl From<&[&str]> for StringVec { + fn from(slice: &[&str]) -> Self { + StringVec { + vec: slice.iter().map(|&s| s.to_string()).collect(), + } + } +} + +impl From<Vec<String>> for StringVec { + fn from(vec: Vec<String>) -> Self { + StringVec { vec } + } +} + +impl From<&[String]> for StringVec { + fn from(slice: &[String]) -> Self { + StringVec { + vec: slice.to_vec(), + } + } +} + +impl From<Vec<&str>> for StringVec { + fn from(vec: Vec<&str>) -> Self { + StringVec { + vec: vec.iter().map(|&s| s.to_string()).collect(), + } + } +} diff --git a/mingling_macros/src/dispatcher_chain.rs b/mingling_macros/src/dispatcher_chain.rs index 4038600..6223a81 100644 --- a/mingling_macros/src/dispatcher_chain.rs +++ b/mingling_macros/src/dispatcher_chain.rs @@ -145,84 +145,6 @@ pub fn dispatcher_chain(input: TokenStream) -> TokenStream { expanded.into() } -pub fn dispatcher_render(input: TokenStream) -> TokenStream { - // Parse the input - let dispatcher_input = syn::parse_macro_input!(input as DispatcherChainInput); - - // Determine if we're using default or explicit group - let (group_name, command_name, command_struct, pack, use_default) = match dispatcher_input { - DispatcherChainInput::Explicit { - group_name, - command_name, - command_struct, - pack, - } => (group_name, command_name, command_struct, pack, false), - DispatcherChainInput::Default { - command_name, - command_struct, - pack, - } => ( - Ident::new("ThisProgram", proc_macro2::Span::call_site()), - command_name, - command_struct, - pack, - true, - ), - }; - - let command_name_str = command_name.value(); - - let comp_entry = get_comp_entry(&pack); - - let expanded = if use_default { - // For default case, use ThisProgram - quote! { - #[derive(Debug, Default)] - pub struct #command_struct; - - ::mingling::macros::pack!(ThisProgram, #pack = Vec<String>); - - #comp_entry - - impl ::mingling::Dispatcher for #command_struct { - fn node(&self) -> ::mingling::Node { - ::mingling::macros::node!(#command_name_str) - } - fn begin(&self, args: Vec<String>) -> ::mingling::ChainProcess { - #pack::new(args).to_render() - } - fn clone_dispatcher(&self) -> Box<dyn ::mingling::Dispatcher> { - Box::new(#command_struct) - } - } - } - } else { - // For explicit case, use the provided group_name - quote! { - #[derive(Debug, Default)] - pub struct #command_struct; - - ::mingling::macros::pack!(#group_name, #pack = Vec<String>); - - #comp_entry - - impl ::mingling::Dispatcher for #command_struct { - fn node(&self) -> ::mingling::Node { - ::mingling::macros::node!(#command_name_str) - } - fn begin(&self, args: Vec<String>) -> ::mingling::ChainProcess { - #pack::new(args).to_render() - } - fn clone_dispatcher(&self) -> Box<dyn ::mingling::Dispatcher> { - Box::new(#command_struct) - } - } - } - }; - - expanded.into() -} - #[cfg(feature = "comp")] fn get_comp_entry(entry_name: &Ident) -> proc_macro2::TokenStream { let comp_entry = quote! { diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs index 8df8ad7..a1493ba 100644 --- a/mingling_macros/src/lib.rs +++ b/mingling_macros/src/lib.rs @@ -59,11 +59,6 @@ pub fn dispatcher(input: TokenStream) -> TokenStream { } #[proc_macro] -pub fn dispatcher_render(input: TokenStream) -> TokenStream { - dispatcher_chain::dispatcher_render(input) -} - -#[proc_macro] pub fn r_print(input: TokenStream) -> TokenStream { render::r_print(input) } |
