diff options
| -rw-r--r-- | mingling/Cargo.lock | 181 | ||||
| -rw-r--r-- | mingling/Cargo.toml | 1 | ||||
| -rw-r--r-- | mingling/src/any.rs | 2 | ||||
| -rw-r--r-- | mingling/src/asset/dispatcher.rs | 192 | ||||
| -rw-r--r-- | mingling/src/lib.rs | 8 | ||||
| -rw-r--r-- | mingling/src/program.rs | 107 | ||||
| -rw-r--r-- | mingling/src/program/config.rs | 2 | ||||
| -rw-r--r-- | mingling/src/program/exec.rs | 130 | ||||
| -rw-r--r-- | mingling/src/program/exec/error.rs | 44 | ||||
| -rw-r--r-- | mingling/src/program/flag.rs | 41 | ||||
| -rw-r--r-- | mingling/src/program/hint.rs | 52 | ||||
| -rw-r--r-- | mingling/src/program/setup.rs | 16 | ||||
| -rw-r--r-- | mingling/src/program/setup/basic.rs | 12 | ||||
| -rw-r--r-- | mingling_macros/Cargo.lock | 7 | ||||
| -rw-r--r-- | mingling_macros/Cargo.toml | 1 | ||||
| -rw-r--r-- | mingling_macros/src/chain.rs | 36 | ||||
| -rw-r--r-- | mingling_macros/src/chain_struct.rs | 23 | ||||
| -rw-r--r-- | mingling_macros/src/dispatcher.rs | 72 | ||||
| -rw-r--r-- | mingling_macros/src/dispatcher_chain.rs | 94 | ||||
| -rw-r--r-- | mingling_macros/src/lib.rs | 151 | ||||
| -rw-r--r-- | mingling_macros/src/node.rs | 10 | ||||
| -rw-r--r-- | mingling_macros/src/render.rs | 26 | ||||
| -rw-r--r-- | mingling_macros/src/renderer.rs | 149 |
23 files changed, 1022 insertions, 335 deletions
diff --git a/mingling/Cargo.lock b/mingling/Cargo.lock index 34edcd9..2ddc3e3 100644 --- a/mingling/Cargo.lock +++ b/mingling/Cargo.lock @@ -3,12 +3,55 @@ version = 4 [[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] name = "just_fmt" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5454cda0d57db59778608d7a47bff5b16c6705598265869fb052b657f66cf05e" [[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] name = "mingling" version = "0.1.0" dependencies = [ @@ -16,6 +59,7 @@ dependencies = [ "mingling_macros", "serde", "thiserror", + "tokio", ] [[package]] @@ -23,12 +67,59 @@ name = "mingling_macros" version = "0.1.0" dependencies = [ "just_fmt", + "once_cell", "proc-macro2", "quote", "syn", ] [[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] name = "proc-macro2" version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -47,6 +138,21 @@ dependencies = [ ] [[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -77,6 +183,32 @@ dependencies = [ ] [[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] name = "syn" version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -108,7 +240,56 @@ dependencies = [ ] [[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/mingling/Cargo.toml b/mingling/Cargo.toml index 5cd681a..00f87c0 100644 --- a/mingling/Cargo.toml +++ b/mingling/Cargo.toml @@ -15,3 +15,4 @@ mingling_macros = { path = "../mingling_macros", optional = true } just_fmt = "0.1.2" serde = { version = "1.0", features = ["derive"], optional = true } thiserror = "2" +tokio = { version = "1", features = ["full"] } diff --git a/mingling/src/any.rs b/mingling/src/any.rs index a74cd54..5ff8cfe 100644 --- a/mingling/src/any.rs +++ b/mingling/src/any.rs @@ -8,7 +8,7 @@ pub type ChainProcess = Result<AnyOutput, ChainProcessError>; #[derive(Debug)] pub struct AnyOutput { inner: Box<dyn std::any::Any + Send + 'static>, - type_id: std::any::TypeId, + pub type_id: std::any::TypeId, } impl AnyOutput { diff --git a/mingling/src/asset/dispatcher.rs b/mingling/src/asset/dispatcher.rs index 31623d3..09bd386 100644 --- a/mingling/src/asset/dispatcher.rs +++ b/mingling/src/asset/dispatcher.rs @@ -1,19 +1,195 @@ use crate::{ChainProcess, Program, asset::node::Node}; -pub use mingling_macros::Dispatcher; - pub trait Dispatcher { fn node(&self) -> Node; + fn begin(&self, args: Vec<String>) -> ChainProcess; + fn clone_dispatcher(&self) -> Box<dyn Dispatcher>; } -pub trait DispatcherChain { - fn begin(&self) -> ChainProcess; +impl Clone for Box<dyn Dispatcher> { + fn clone(&self) -> Self { + self.clone_dispatcher() + } } -impl Program { +impl<C: crate::program::ProgramCollect> Program<C> { /// Adds a dispatcher to the program. - pub fn with_dispatcher<D: Dispatcher + 'static>(&mut self, dispatcher: D) { - let dispatcher = Box::new(dispatcher); - self.dispatcher.push(dispatcher); + pub fn with_dispatcher<D>(&mut self, dispatcher: D) + where + D: Into<Dispatchers>, + { + let dispatchers = dispatcher.into().dispatcher; + self.dispatcher.extend(dispatchers); + } +} + +pub struct Dispatchers { + dispatcher: Vec<Box<dyn Dispatcher + 'static>>, +} + +impl<D> From<D> for Dispatchers +where + D: Dispatcher + 'static, +{ + fn from(dispatcher: D) -> Self { + Self { + dispatcher: vec![Box::new(dispatcher)], + } + } +} + +impl From<Vec<Box<dyn Dispatcher>>> for Dispatchers { + fn from(dispatcher: Vec<Box<dyn Dispatcher>>) -> Self { + Self { dispatcher } + } +} + +impl From<Box<dyn Dispatcher>> for Dispatchers { + fn from(dispatcher: Box<dyn Dispatcher>) -> Self { + Self { + dispatcher: vec![dispatcher], + } + } +} + +impl<D> From<(D,)> for Dispatchers +where + D: Dispatcher + 'static, +{ + fn from(dispatcher: (D,)) -> Self { + Self { + dispatcher: vec![Box::new(dispatcher.0)], + } + } +} + +impl<D1, D2> From<(D1, D2)> for Dispatchers +where + D1: Dispatcher + 'static, + D2: Dispatcher + 'static, +{ + fn from(dispatchers: (D1, D2)) -> Self { + Self { + dispatcher: vec![Box::new(dispatchers.0), Box::new(dispatchers.1)], + } + } +} + +impl<D1, D2, D3> From<(D1, D2, D3)> for Dispatchers +where + D1: Dispatcher + 'static, + D2: Dispatcher + 'static, + D3: Dispatcher + 'static, +{ + fn from(dispatchers: (D1, D2, D3)) -> Self { + Self { + dispatcher: vec![ + Box::new(dispatchers.0), + Box::new(dispatchers.1), + Box::new(dispatchers.2), + ], + } + } +} + +impl<D1, D2, D3, D4> From<(D1, D2, D3, D4)> for Dispatchers +where + D1: Dispatcher + 'static, + D2: Dispatcher + 'static, + D3: Dispatcher + 'static, + D4: Dispatcher + 'static, +{ + fn from(dispatchers: (D1, D2, D3, D4)) -> Self { + Self { + dispatcher: vec![ + Box::new(dispatchers.0), + Box::new(dispatchers.1), + Box::new(dispatchers.2), + Box::new(dispatchers.3), + ], + } + } +} + +impl<D1, D2, D3, D4, D5> From<(D1, D2, D3, D4, D5)> for Dispatchers +where + D1: Dispatcher + 'static, + D2: Dispatcher + 'static, + D3: Dispatcher + 'static, + D4: Dispatcher + 'static, + D5: Dispatcher + 'static, +{ + fn from(dispatchers: (D1, D2, D3, D4, D5)) -> Self { + Self { + dispatcher: vec![ + Box::new(dispatchers.0), + Box::new(dispatchers.1), + Box::new(dispatchers.2), + Box::new(dispatchers.3), + Box::new(dispatchers.4), + ], + } + } +} + +impl<D1, D2, D3, D4, D5, D6> From<(D1, D2, D3, D4, D5, D6)> for Dispatchers +where + D1: Dispatcher + 'static, + D2: Dispatcher + 'static, + D3: Dispatcher + 'static, + D4: Dispatcher + 'static, + D5: Dispatcher + 'static, + D6: Dispatcher + 'static, +{ + fn from(dispatchers: (D1, D2, D3, D4, D5, D6)) -> Self { + Self { + dispatcher: vec![ + Box::new(dispatchers.0), + Box::new(dispatchers.1), + Box::new(dispatchers.2), + Box::new(dispatchers.3), + Box::new(dispatchers.4), + Box::new(dispatchers.5), + ], + } + } +} + +impl<D1, D2, D3, D4, D5, D6, D7> From<(D1, D2, D3, D4, D5, D6, D7)> for Dispatchers +where + D1: Dispatcher + 'static, + D2: Dispatcher + 'static, + D3: Dispatcher + 'static, + D4: Dispatcher + 'static, + D5: Dispatcher + 'static, + D6: Dispatcher + 'static, + D7: Dispatcher + 'static, +{ + fn from(dispatchers: (D1, D2, D3, D4, D5, D6, D7)) -> Self { + Self { + dispatcher: vec![ + Box::new(dispatchers.0), + Box::new(dispatchers.1), + Box::new(dispatchers.2), + Box::new(dispatchers.3), + Box::new(dispatchers.4), + Box::new(dispatchers.5), + Box::new(dispatchers.6), + ], + } + } +} + +impl std::ops::Deref for Dispatchers { + type Target = Vec<Box<dyn Dispatcher + 'static>>; + + fn deref(&self) -> &Self::Target { + &self.dispatcher + } +} + +impl Into<Vec<Box<dyn Dispatcher + 'static>>> for Dispatchers { + fn into(self) -> Vec<Box<dyn Dispatcher + 'static>> { + self.dispatcher } } diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs index c172559..060fa30 100644 --- a/mingling/src/lib.rs +++ b/mingling/src/lib.rs @@ -3,6 +3,7 @@ pub use crate::any::*; pub mod error { pub use crate::asset::chain::error::*; + pub use crate::exec::error::*; } mod program; @@ -10,13 +11,19 @@ pub use crate::program::*; pub mod setup { pub use crate::program::setup::*; } +pub mod hint { + pub use crate::program::hint::*; +} #[cfg(feature = "macros")] #[allow(unused_imports)] pub mod macros { pub use mingling_macros::chain; pub use mingling_macros::chain_struct; + pub use mingling_macros::dispatcher_chain; + pub use mingling_macros::dispatcher_render; pub use mingling_macros::node; + pub use mingling_macros::program; pub use mingling_macros::r_print; pub use mingling_macros::r_println; pub use mingling_macros::renderer; @@ -30,4 +37,3 @@ pub use crate::asset::dispatcher::*; pub use crate::asset::node::*; pub use crate::asset::renderer::*; pub use crate::renderer::render_result::*; -pub use mingling_macros::Dispatcher; diff --git a/mingling/src/program.rs b/mingling/src/program.rs index e4bc3d0..5e9d1f2 100644 --- a/mingling/src/program.rs +++ b/mingling/src/program.rs @@ -1,6 +1,11 @@ -use crate::asset::dispatcher::Dispatcher; -use std::env; +use crate::{ + AnyOutput, ChainProcess, RenderResult, asset::dispatcher::Dispatcher, + error::ProgramExecuteError, +}; +use std::{env, pin::Pin}; +pub mod exec; +pub mod hint; pub mod setup; mod config; @@ -8,9 +13,12 @@ pub use config::*; mod flag; pub use flag::*; +use tokio::io::AsyncWriteExt; #[derive(Default)] -pub struct Program { +pub struct Program<C: ProgramCollect> { + pub(crate) collect: std::marker::PhantomData<C>, + pub(crate) args: Vec<String>, pub(crate) dispatcher: Vec<Box<dyn Dispatcher>>, @@ -18,18 +26,105 @@ pub struct Program { pub user_context: ProgramUserContext, } -impl Program { +impl<C> Program<C> +where + C: ProgramCollect, +{ /// Creates a new Program instance, initializing args from environment. pub fn new() -> Self { Program { + collect: std::marker::PhantomData, args: env::args().collect(), dispatcher: Vec::new(), - ..Default::default() + stdout_setting: Default::default(), + user_context: Default::default(), } } /// Run the command line program + pub async fn exec_without_render(mut self) -> Result<RenderResult, ProgramExecuteError> { + self.args = self.args.iter().skip(1).cloned().collect(); + crate::exec::exec(self).await.map_err(|e| e.into()) + } + + /// Run the command line program pub async fn exec(self) { - todo!() + let stdout_setting = self.stdout_setting.clone(); + let result = match self.exec_without_render().await { + Ok(r) => r, + Err(e) => match e { + ProgramExecuteError::DispatcherNotFound => { + eprintln!("Dispatcher not found"); + return; + } + ProgramExecuteError::Other(e) => { + eprintln!("{}", e); + return; + } + }, + }; + + // Render result + if stdout_setting.render_output { + if !result.is_empty() { + print!("{}", result); + if let Err(e) = tokio::io::stdout().flush().await + && stdout_setting.error_output + { + eprintln!("{}", e.to_string()); + } + } + } } } + +pub trait ProgramCollect { + fn render(any: AnyOutput, r: &mut RenderResult); + fn do_chain(any: AnyOutput) -> Pin<Box<dyn Future<Output = ChainProcess> + Send>>; +} + +#[macro_export] +macro_rules! __dispatch_program_renderers { + ( + $( $render_ty:ty => $prev_ty:ty, )* + ) => { + fn render(any: mingling::AnyOutput, r: &mut mingling::RenderResult) { + match any.type_id { + $( + id if id == std::any::TypeId::of::<$prev_ty>() => { + let value = any.downcast::<$prev_ty>().unwrap(); + <$render_ty as mingling::Renderer>::render(value, r); + } + )* + _ => (), + } + } + }; +} + +#[macro_export] +macro_rules! __dispatch_program_chains { + ( + $( $chain_ty:ty => $chain_prev:ty, )* + ) => { + fn do_chain( + any: mingling::AnyOutput, + ) -> std::pin::Pin<Box<dyn Future<Output = mingling::ChainProcess> + Send>> { + match any.type_id { + $( + id if id == std::any::TypeId::of::<$chain_prev>() => { + let value = any.downcast::<$chain_prev>().unwrap(); + let fut = async { <$chain_ty as mingling::Chain>::proc(value).await }; + Box::pin(fut) + } + )* + _ => Box::pin(async move { + mingling::AnyOutput::new(mingling::hint::NoChainFound { + name: format!("{:?}", any.type_id).to_string(), + }) + .route_chain() + }), + } + } + }; +} diff --git a/mingling/src/program/config.rs b/mingling/src/program/config.rs index 93507e7..ffcade3 100644 --- a/mingling/src/program/config.rs +++ b/mingling/src/program/config.rs @@ -1,3 +1,4 @@ +#[derive(Debug, Clone)] pub struct ProgramStdoutSetting { /// Output error messages pub error_output: bool, @@ -15,6 +16,7 @@ impl Default for ProgramStdoutSetting { } } +#[derive(Debug, Clone)] pub struct ProgramUserContext { /// View help information instead of running the command pub help: bool, diff --git a/mingling/src/program/exec.rs b/mingling/src/program/exec.rs new file mode 100644 index 0000000..99eb419 --- /dev/null +++ b/mingling/src/program/exec.rs @@ -0,0 +1,130 @@ +use crate::{ + AnyOutput, ChainProcess, Dispatcher, Program, ProgramCollect, RenderResult, + error::{ChainProcessError, ProgramInternalExecuteError}, + hint::{DispatcherNotFound, NoChainFound, ProgramEnd}, +}; + +pub mod error; + +pub async fn exec<C: ProgramCollect>( + program: Program<C>, +) -> Result<RenderResult, ProgramInternalExecuteError> { + // Match user input + let matched: (Box<dyn Dispatcher>, Vec<String>) = match match_user_input(&program) { + Ok(r) => (r.0.clone(), r.1), + Err(ProgramInternalExecuteError::DispatcherNotFound) => { + // If no Dispatcher is found, dispatch to the DispatcherNotFound Dispatcher + // to route it to the NoDispatcherFound struct + let disp: Box<dyn Dispatcher> = Box::new(DispatcherNotFound); + (disp, program.args) + } + Err(e) => return Err(e), + }; + + // Entry point + let (dispatcher, args) = matched; + let mut current = match handle_chain_process::<C>(dispatcher.begin(args)) { + Ok(Next::RenderResult(render_result)) => return Ok(render_result), + Ok(Next::AnyOutput(any)) => any, + Err(e) => return Err(e), + }; + + loop { + current = match handle_chain_process::<C>(C::do_chain(current).await) { + Ok(Next::RenderResult(render_result)) => return Ok(render_result), + Ok(Next::AnyOutput(any)) => any, + Err(e) => return Err(e), + }; + if current.is::<ProgramEnd>() || current.is::<NoChainFound>() { + break; + } + } + + Ok(RenderResult::default()) +} + +/// Match user input against registered dispatchers and return the matched dispatcher and remaining arguments. +fn match_user_input<C: ProgramCollect>( + program: &Program<C>, +) -> Result<(&Box<dyn Dispatcher>, Vec<String>), ProgramInternalExecuteError> { + let nodes = get_nodes(&program); + let command = format!("{} ", program.args.join(" ")); + + // Find all nodes that match the command prefix + let matching_nodes: Vec<&(String, &Box<dyn Dispatcher>)> = nodes + .iter() + // Also add a space to the node string to ensure consistent matching logic + .filter(|(node_str, _)| command.starts_with(&format!("{} ", node_str))) + .collect(); + + match matching_nodes.len() { + 0 => { + // No matching node found + Err(ProgramInternalExecuteError::DispatcherNotFound) + } + 1 => { + let matched_prefix = matching_nodes[0]; + let prefix_len = matched_prefix.0.split_whitespace().count(); + let trimmed_args: Vec<String> = program.args.iter().skip(prefix_len).cloned().collect(); + Ok((matched_prefix.1, trimmed_args)) + } + _ => { + // Multiple matching nodes found + // Find the node with the longest length (most specific match) + let matched_prefix = matching_nodes + .iter() + .max_by_key(|node| node.0.len()) + .unwrap(); + + let prefix_len = matched_prefix.0.split_whitespace().count(); + let trimmed_args: Vec<String> = program.args.iter().skip(prefix_len).cloned().collect(); + Ok((matched_prefix.1, trimmed_args)) + } + } +} + +#[inline(always)] +fn render<C: ProgramCollect>(any: AnyOutput) -> RenderResult { + let mut render_result = RenderResult::default(); + C::render(any, &mut render_result); + render_result +} + +fn handle_chain_process<C: ProgramCollect>( + process: ChainProcess, +) -> Result<Next, ProgramInternalExecuteError> { + match process { + Ok(any) => Ok(Next::AnyOutput(any)), + Err(e) => match e { + ChainProcessError::Broken(any_output) => { + let render_result = render::<C>(any_output); + return Ok(Next::RenderResult(render_result)); + } + _ => { + return Err(e.into()); + } + }, + } +} + +// Get all registered dispatcher names from the program +fn get_nodes<C: ProgramCollect>(program: &Program<C>) -> Vec<(String, &Box<dyn Dispatcher>)> { + program + .dispatcher + .iter() + .map(|disp| { + let node_str = disp + .node() + .to_string() + .split('.') + .collect::<Vec<_>>() + .join(" "); + (node_str, disp) + }) + .collect() +} + +enum Next { + RenderResult(RenderResult), + AnyOutput(AnyOutput), +} diff --git a/mingling/src/program/exec/error.rs b/mingling/src/program/exec/error.rs new file mode 100644 index 0000000..0523ea5 --- /dev/null +++ b/mingling/src/program/exec/error.rs @@ -0,0 +1,44 @@ +use crate::error::ChainProcessError; + +#[derive(thiserror::Error, Debug)] +pub enum ProgramExecuteError { + #[error("No Dispatcher Found")] + DispatcherNotFound, + + #[error("Other error: {0}")] + Other(String), +} + +#[derive(thiserror::Error, Debug)] +pub enum ProgramInternalExecuteError { + #[error("No Dispatcher Found")] + DispatcherNotFound, + + #[error("Other error: {0}")] + Other(String), + + #[error("IO error: {0}")] + IO(#[from] std::io::Error), +} + +impl From<ProgramInternalExecuteError> for ProgramExecuteError { + fn from(value: ProgramInternalExecuteError) -> Self { + match value { + ProgramInternalExecuteError::DispatcherNotFound => { + ProgramExecuteError::DispatcherNotFound + } + ProgramInternalExecuteError::Other(s) => ProgramExecuteError::Other(s), + ProgramInternalExecuteError::IO(e) => ProgramExecuteError::Other(format!("{}", e)), + } + } +} + +impl From<ChainProcessError> for ProgramInternalExecuteError { + fn from(value: ChainProcessError) -> Self { + match value { + ChainProcessError::Other(s) => ProgramInternalExecuteError::Other(s), + ChainProcessError::IO(error) => ProgramInternalExecuteError::IO(error), + ChainProcessError::Broken(_) => ProgramInternalExecuteError::Other(format!("Broken")), + } + } +} diff --git a/mingling/src/program/flag.rs b/mingling/src/program/flag.rs index 0178ebe..8372517 100644 --- a/mingling/src/program/flag.rs +++ b/mingling/src/program/flag.rs @@ -1,4 +1,4 @@ -use crate::Program; +use crate::{Program, ProgramCollect}; pub struct Flag { vec: Vec<&'static str>, @@ -80,11 +80,14 @@ macro_rules! special_argument { }}; } -impl Program { +impl<C> Program<C> +where + C: ProgramCollect, +{ /// Registers a global argument (with value) and its handler. pub fn global_argument<F, A>(&mut self, arguments: A, do_fn: F) where - F: Fn(&mut Program, String), + F: Fn(&mut Program<C>, String), A: Into<Flag>, { let flag = arguments.into(); @@ -100,7 +103,7 @@ impl Program { /// Registers a global flag (boolean) and its handler. pub fn global_flag<F, A>(&mut self, flag: A, do_fn: F) where - F: Fn(&mut Program), + F: Fn(&mut Program<C>), A: Into<Flag>, { let flag = flag.into(); @@ -112,4 +115,34 @@ impl Program { } } } + + /// Extracts a global argument (with value) from arguments + pub fn pick_global_argument<F>(&mut self, flag: F) -> Option<String> + where + F: Into<Flag>, + { + let flag: Flag = flag.into(); + for argument in flag.iter() { + let value = special_argument!(self.args, argument); + if value.is_some() { + return value; + } + } + None + } + + /// Extracts global flags from arguments + pub fn pick_global_flag<F>(&mut self, flag: F) -> bool + where + F: Into<Flag>, + { + let flag: Flag = flag.into(); + for argument in flag.iter() { + let enabled = special_flag!(self.args, argument); + if enabled { + return enabled; + } + } + return false; + } } diff --git a/mingling/src/program/hint.rs b/mingling/src/program/hint.rs new file mode 100644 index 0000000..e9c510b --- /dev/null +++ b/mingling/src/program/hint.rs @@ -0,0 +1,52 @@ +use crate::{AnyOutput, ChainProcess, Dispatcher, Node}; + +/// Marker: Program End +/// +/// If a chain outputs ProgramEnd to the Chain, +/// the program will terminate directly. +/// +/// You can implement Renderer for ProgramEnd +/// to render relevant information after the program ends. +#[cfg_attr(feature = "serde_renderer", derive(serde::Serialize))] +pub struct ProgramEnd; + +/// Marker: Chain Not Found +/// +/// If a Chain or Dispatcher outputs NoChainFound to the Chain, +/// the program will terminate directly. +/// +/// You can implement Renderer for NoChainFound +/// to render relevant information when a Chain cannot be found. +#[cfg_attr(feature = "serde_renderer", derive(serde::Serialize))] +pub struct NoChainFound { + pub name: String, +} + +/// Marker: Dispatcher Not Found +/// +/// If a Dispatcher outputs NoDispatcherFound to the Chain, +/// the program will terminate directly. +/// +/// You can implement Renderer for NoDispatcherFound +/// to render relevant information when a Dispatcher cannot be found. +#[cfg_attr(feature = "serde_renderer", derive(serde::Serialize))] +pub struct NoDispatcherFound { + pub args: Vec<String>, +} + +#[derive(Default)] +#[cfg_attr(feature = "serde_renderer", derive(serde::Serialize))] +pub struct DispatcherNotFound; +impl Dispatcher for DispatcherNotFound { + fn node(&self) -> crate::Node { + Node::default().join("_not_found") + } + + fn begin(&self, args: Vec<String>) -> ChainProcess { + AnyOutput::new(NoDispatcherFound { args }).route_renderer() + } + + fn clone_dispatcher(&self) -> Box<dyn Dispatcher> { + Box::new(DispatcherNotFound) + } +} diff --git a/mingling/src/program/setup.rs b/mingling/src/program/setup.rs index fdf7b04..e81247e 100644 --- a/mingling/src/program/setup.rs +++ b/mingling/src/program/setup.rs @@ -1,15 +1,19 @@ -use crate::program::Program; +use crate::{ProgramCollect, program::Program}; mod basic; pub use basic::*; -pub trait ProgramSetup { - fn setup(program: &mut Program); +pub trait ProgramSetup<C: ProgramCollect> { + fn setup(&mut self, program: &mut Program<C>); } -impl Program { +impl<C> Program<C> +where + C: ProgramCollect, +{ /// Load and execute init logic - pub fn with_setup<S: ProgramSetup + 'static>(&mut self, _setup: S) { - S::setup(self); + pub fn with_setup<S: ProgramSetup<C> + 'static>(&mut self, mut setup: S) -> S { + S::setup(&mut setup, self); + setup } } diff --git a/mingling/src/program/setup/basic.rs b/mingling/src/program/setup/basic.rs index 0ea72a6..43c14b9 100644 --- a/mingling/src/program/setup/basic.rs +++ b/mingling/src/program/setup/basic.rs @@ -1,4 +1,7 @@ -use crate::program::{Program, setup::ProgramSetup}; +use crate::{ + ProgramCollect, + program::{Program, setup::ProgramSetup}, +}; /// Performs basic program initialization: /// @@ -7,8 +10,11 @@ use crate::program::{Program, setup::ProgramSetup}; /// - Collects `--confirm` flag to skip user confirmation pub struct BasicProgramSetup; -impl ProgramSetup for BasicProgramSetup { - fn setup(program: &mut Program) { +impl<C> ProgramSetup<C> for BasicProgramSetup +where + C: ProgramCollect, +{ + fn setup(&mut self, program: &mut Program<C>) { program.global_flag(["--quiet", "-q"], |p| { p.stdout_setting.render_output = false; p.stdout_setting.error_output = false; diff --git a/mingling_macros/Cargo.lock b/mingling_macros/Cargo.lock index b2fef0e..9d159fe 100644 --- a/mingling_macros/Cargo.lock +++ b/mingling_macros/Cargo.lock @@ -13,12 +13,19 @@ name = "mingling_macros" version = "0.1.0" dependencies = [ "just_fmt", + "once_cell", "proc-macro2", "quote", "syn", ] [[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] name = "proc-macro2" version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/mingling_macros/Cargo.toml b/mingling_macros/Cargo.toml index 683b5b4..39c6d36 100644 --- a/mingling_macros/Cargo.toml +++ b/mingling_macros/Cargo.toml @@ -16,3 +16,4 @@ quote = "1.0" proc-macro2 = "1.0" just_fmt = "0.1.2" +once_cell = "1.21" diff --git a/mingling_macros/src/chain.rs b/mingling_macros/src/chain.rs index 0c21c79..ddedc05 100644 --- a/mingling_macros/src/chain.rs +++ b/mingling_macros/src/chain.rs @@ -72,32 +72,6 @@ fn extract_return_type(sig: &Signature) -> syn::Result<TypePath> { } } -/// Implementation of the `#[chain]` attribute macro -/// -/// This macro transforms an async function into a struct that implements -/// the `Chain` trait. The struct name is specified in the attribute. -/// -/// # Examples -/// -/// ```ignore -/// use mingling_macros::chain; -/// -/// #[chain(InitEntry)] -/// pub async fn process(data: InitBegin) -> mingling::AnyOutput { -/// AnyOutput::new::<InitResult>("初始化成功!".to_string().into()) -/// } -/// ``` -/// -/// This generates: -/// ```ignore -/// pub struct InitEntry; -/// impl Chain for InitEntry { -/// type Previous = InitBegin; -/// async fn proc(data: Self::Previous) -> mingling::AnyOutput { -/// AnyOutput::new::<InitResult>("初始化成功!".to_string().into()) -/// } -/// } -/// ``` pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { // Parse the attribute arguments let chain_attr = parse_macro_input!(attr as ChainAttribute); @@ -160,5 +134,15 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { } }; + // Record the chain mapping + let chain_entry = quote! { + #struct_name => #previous_type, + }; + let mut chains = crate::CHAINS.lock().unwrap(); + let entry = chain_entry.to_string(); + if !chains.contains(&entry) { + chains.push(entry); + } + expanded.into() } diff --git a/mingling_macros/src/chain_struct.rs b/mingling_macros/src/chain_struct.rs index 82a596d..7305a67 100644 --- a/mingling_macros/src/chain_struct.rs +++ b/mingling_macros/src/chain_struct.rs @@ -27,29 +27,6 @@ impl Parse for ChainStructInput { } } -/// Implementation of the `chain_struct!` macro -/// -/// This macro creates a wrapper struct with automatic implementations of: -/// - `From<InnerType>` and `Into<InnerType>` -/// - `new()` constructor -/// - `Default` (if the inner type implements Default) -/// - `AsRef<InnerType>` and `AsMut<InnerType>` -/// - `Deref` and `DerefMut` to the inner type -/// -/// # Examples -/// -/// ```ignore -/// use mingling_macros::chain_struct; -/// -/// // Creates a wrapper type around String -/// chain_struct!(NameString = String); -/// -/// // Usage: -/// let name = NameString::new("Hello".to_string()); -/// let inner: String = name.into(); // Into conversion -/// let name2 = NameString::from("World".to_string()); // From conversion -/// let ref_str: &String = name2.as_ref(); // AsRef -/// ``` pub fn chain_struct(input: TokenStream) -> TokenStream { let ChainStructInput { type_name, diff --git a/mingling_macros/src/dispatcher.rs b/mingling_macros/src/dispatcher.rs deleted file mode 100644 index a411081..0000000 --- a/mingling_macros/src/dispatcher.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! Dispatcher Derive Macro Implementation -//! -//! This module provides the `Dispatcher` derive macro for automatically -//! implementing the `mingling::Dispatcher` trait for structs. - -use just_fmt::dot_case; -use proc_macro::TokenStream; -use quote::quote; -use syn::{Attribute, DeriveInput, Ident, Lit, Meta, MetaNameValue, parse_macro_input}; - -/// Parses the `#[dispatcher("path")]` attribute if present -fn parse_dispatcher_attribute(attrs: &[Attribute]) -> Option<String> { - for attr in attrs { - if attr.path().is_ident("dispatcher") { - match attr.parse_args::<Meta>() { - Ok(Meta::NameValue(MetaNameValue { - value: - syn::Expr::Lit(syn::ExprLit { - lit: Lit::Str(lit_str), - .. - }), - .. - })) => { - return Some(lit_str.value()); - } - Ok(_) => { - // If it's not a string literal, we'll use a default - return None; - } - Err(_) => { - // If parsing fails, we'll use a default - return None; - } - } - } - } - None -} - -/// Generates the command node path from the struct name or attribute -fn generate_command_path(struct_name: &Ident, attr_path: Option<String>) -> String { - if let Some(path) = attr_path { - path - } else { - // Convert struct name to dot_case for default path using the dot_case! macro - dot_case!(struct_name.to_string()) - } -} - -/// Implementation of the `Dispatcher` derive macro -pub fn dispatcher_derive(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - - let struct_name = &input.ident; - - // Parse the dispatcher attribute if present - let attr_path = parse_dispatcher_attribute(&input.attrs); - - // Generate the command path - let command_path = generate_command_path(struct_name, attr_path); - - // Generate the implementation - let expanded = quote! { - impl ::mingling::Dispatcher for #struct_name { - fn node(&self) -> ::mingling::Node { - ::mingling::macros::node!(#command_path) - } - } - }; - - expanded.into() -} diff --git a/mingling_macros/src/dispatcher_chain.rs b/mingling_macros/src/dispatcher_chain.rs new file mode 100644 index 0000000..d9d95a5 --- /dev/null +++ b/mingling_macros/src/dispatcher_chain.rs @@ -0,0 +1,94 @@ +//! Dispatcher Chain and Dispatcher Render Macros +//! +//! This module provides macros for creating dispatcher chain and dispatcher render structs +//! with automatic implementations of the `DispatcherChain` trait. + +use proc_macro::TokenStream; +use quote::quote; +use syn::parse::{Parse, ParseStream}; +use syn::{Ident, Result as SynResult, Token}; + +/// Parses input in the format: `"command_name", CommandStruct => ChainStruct` +struct DispatcherChainInput { + command_name: syn::LitStr, + command_struct: Ident, + chain_struct: Ident, +} + +impl Parse for DispatcherChainInput { + fn parse(input: ParseStream) -> SynResult<Self> { + let command_name = input.parse()?; + input.parse::<Token![,]>()?; + let command_struct = input.parse()?; + input.parse::<Token![=>]>()?; + let chain_struct = input.parse()?; + + Ok(DispatcherChainInput { + command_name, + command_struct, + chain_struct, + }) + } +} + +pub fn dispatcher_chain(input: TokenStream) -> TokenStream { + let DispatcherChainInput { + command_name, + command_struct, + chain_struct, + } = syn::parse_macro_input!(input as DispatcherChainInput); + + let command_name_str = command_name.value(); + + let expanded = quote! { + #[derive(Debug, Default)] + pub struct #command_struct; + + ::mingling::macros::chain_struct!(#chain_struct = Vec<String>); + + 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 { + #chain_struct::new(args).to_chain() + } + fn clone_dispatcher(&self) -> Box<dyn ::mingling::Dispatcher> { + Box::new(#command_struct) + } + } + }; + + expanded.into() +} + +pub fn dispatcher_render(input: TokenStream) -> TokenStream { + let DispatcherChainInput { + command_name, + command_struct, + chain_struct, + } = syn::parse_macro_input!(input as DispatcherChainInput); + + let command_name_str = command_name.value(); + + let expanded = quote! { + #[derive(Debug, Default)] + pub struct #command_struct; + + ::mingling::macros::chain_struct!(#chain_struct = Vec<String>); + + 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 { + #chain_struct::new(args).to_render() + } + fn clone_dispatcher(&self) -> Box<dyn ::mingling::Dispatcher> { + Box::new(#command_struct) + } + } + }; + + expanded.into() +} diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs index 3fdd130..b32534b 100644 --- a/mingling_macros/src/lib.rs +++ b/mingling_macros/src/lib.rs @@ -4,14 +4,24 @@ //! Macros are implemented in separate modules and re-exported here. use proc_macro::TokenStream; +use proc_macro2::Ident; +use quote::quote; +use syn::parse_macro_input; mod chain; mod chain_struct; -mod dispatcher; +mod dispatcher_chain; mod node; mod render; mod renderer; +use once_cell::sync::Lazy; +use std::sync::Mutex; + +// Global variable declarations for storing chain and renderer mappings +pub(crate) static CHAINS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new())); +pub(crate) static RENDERERS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new())); + /// Creates a command node from a dot-separated path string. /// /// # Examples @@ -26,31 +36,6 @@ pub fn node(input: TokenStream) -> TokenStream { node::node(input) } -/// Derive macro for automatically implementing the `Dispatcher` trait. -/// -/// This macro generates an implementation of `mingling::Dispatcher` for a struct. -/// By default, it uses the struct name converted to snake_case as the command path. -/// You can also specify a custom path using the `#[dispatcher("path")]` attribute. -/// -/// # Examples -/// -/// ```ignore -/// use mingling_macros::Dispatcher; -/// -/// // Uses default path: "remote.add" -/// #[derive(Dispatcher)] -/// pub struct RemoteAdd; -/// -/// // Uses custom path: "remote.rm" -/// #[derive(Dispatcher)] -/// #[dispatcher("remote.rm")] -/// pub struct MyCommand; -/// ``` -#[proc_macro_derive(Dispatcher, attributes(dispatcher))] -pub fn dispatcher_derive(input: TokenStream) -> TokenStream { - dispatcher::dispatcher_derive(input) -} - /// Macro for creating wrapper types with automatic trait implementations. /// /// This macro creates a new struct that wraps an inner type and automatically @@ -80,6 +65,16 @@ pub fn chain_struct(input: TokenStream) -> TokenStream { chain_struct::chain_struct(input) } +#[proc_macro] +pub fn dispatcher_chain(input: TokenStream) -> TokenStream { + dispatcher_chain::dispatcher_chain(input) +} + +#[proc_macro] +pub fn dispatcher_render(input: TokenStream) -> TokenStream { + dispatcher_chain::dispatcher_render(input) +} + /// Macro for printing to a RenderResult without newline. /// /// This macro expands to a call to `RenderResult::print` with formatted arguments. @@ -127,8 +122,8 @@ pub fn r_println(input: TokenStream) -> TokenStream { /// use mingling_macros::chain; /// /// #[chain(InitEntry)] -/// pub async fn proc(_: InitBegin) -> mingling::AnyOutput { -/// AnyOutput::new::<InitResult>("初始化成功!".to_string().into()) +/// pub async fn proc(_: InitBegin) -> mingling::ChainProcess { +/// AnyOutput::new::<InitResult>("Init!".to_string().into()).route_chain() /// } /// ``` /// @@ -137,8 +132,8 @@ pub fn r_println(input: TokenStream) -> TokenStream { /// pub struct InitEntry; /// impl Chain for InitEntry { /// type Previous = InitBegin; -/// async fn proc(_: Self::Previous) -> mingling::AnyOutput { -/// AnyOutput::new::<InitResult>("初始化成功!".to_string().into()) +/// async fn proc(_: Self::Previous) -> mingling::ChainProcess { +/// AnyOutput::new::<InitResult>("Init!".to_string().into()).route_chain() /// } /// } /// ``` @@ -158,7 +153,7 @@ pub fn chain(attr: TokenStream, item: TokenStream) -> TokenStream { /// use mingling_macros::renderer; /// /// #[renderer(InitResultRenderer)] -/// fn render(p: InitResult, r: &mut RenderResult) { +/// fn render(p: InitResult) { /// let str: String = p.into(); /// r_println!("{}", str); /// } @@ -180,3 +175,97 @@ pub fn chain(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn renderer(attr: TokenStream, item: TokenStream) -> TokenStream { renderer::renderer_attr(attr, item) } + +/// Macro for creating a program structure that collects all chains and renderers. +/// +/// This macro creates a struct that implements the `ProgramCollect` trait, +/// which collects all chains and renderers registered with `#[chain]` and `#[renderer]` +/// attribute macros. The program can then be used to execute the command chain. +/// +/// # Examples +/// +/// ```ignore +/// use mingling_macros::program; +/// +/// program!(MyProgram); +/// +/// // This generates: +/// pub struct MyProgram; +/// impl mingling::ProgramCollect for MyProgram { +/// mingling::__dispatch_program_renderers!(...); +/// mingling::__dispatch_program_chains!(...); +/// } +/// impl MyProgram { +/// pub fn new() -> mingling::Program<MyProgram> { +/// mingling::Program::new() +/// } +/// } +/// ``` +#[proc_macro] +pub fn program(input: TokenStream) -> TokenStream { + let name = parse_macro_input!(input as Ident); + + let renderers = RENDERERS.lock().unwrap().clone(); + let chains = CHAINS.lock().unwrap().clone(); + + let renderer_tokens: Vec<proc_macro2::TokenStream> = renderers + .iter() + .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) + .collect(); + + let chain_tokens: Vec<proc_macro2::TokenStream> = chains + .iter() + .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) + .collect(); + + let expanded = quote! { + pub struct #name; + + impl ::mingling::ProgramCollect for #name { + ::mingling::__dispatch_program_renderers!( + #(#renderer_tokens)* + ); + ::mingling::__dispatch_program_chains!( + #(#chain_tokens)* + ); + } + + impl #name { + pub fn new() -> ::mingling::Program<#name> { + ::mingling::Program::new() + } + } + }; + + TokenStream::from(expanded) +} + +/// Internal macro for registering chains. +/// +/// This macro is used internally by the `#[chain]` attribute macro +/// and should not be used directly. +#[doc(hidden)] +#[proc_macro] +pub fn __register_chain(input: TokenStream) -> TokenStream { + let chain_entry = parse_macro_input!(input as syn::LitStr); + let entry_str = chain_entry.value(); + + CHAINS.lock().unwrap().push(entry_str); + + TokenStream::new() +} + +/// Internal macro for registering renderers. +/// +/// This macro is used internally by the `#[renderer]` attribute macro +/// and should not be used directly. +#[doc(hidden)] +#[proc_macro] +pub fn __register_renderer(input: TokenStream) -> TokenStream { + let renderer_entry = parse_macro_input!(input as syn::LitStr); + let entry_str = renderer_entry.value(); + + RENDERERS.lock().unwrap().push(entry_str); + + TokenStream::new() +} diff --git a/mingling_macros/src/node.rs b/mingling_macros/src/node.rs index 3d9473f..e56f14b 100644 --- a/mingling_macros/src/node.rs +++ b/mingling_macros/src/node.rs @@ -23,16 +23,6 @@ impl Parse for NodeInput { } } -/// Implementation of the `node` procedural macro -/// -/// # Examples -/// -/// ```ignore -/// use mingling_macros::node; -/// -/// // Creates: Node::default().join("root").join("subcommand").join("action") -/// let node = node!("root.subcommand.action"); -/// ``` pub fn node(input: TokenStream) -> TokenStream { // Parse the input as a string literal let input_parsed = syn::parse_macro_input!(input as NodeInput); diff --git a/mingling_macros/src/render.rs b/mingling_macros/src/render.rs index 8b75f34..3f1bbe8 100644 --- a/mingling_macros/src/render.rs +++ b/mingling_macros/src/render.rs @@ -8,19 +8,6 @@ use quote::quote; use syn::parse::Parser; use syn::{Expr, Token}; -/// Implementation of the `r_print!` procedural macro -/// -/// This macro expands to a call to `RenderResult::print` with formatted arguments. -/// It expects a mutable reference to a `RenderResult` named `r` to be in scope. -/// -/// # Examples -/// -/// ```ignore -/// use mingling_macros::r_print; -/// -/// let mut r = RenderResult::default(); -/// r_print!("Hello, {}!", "world"); -/// ``` pub fn r_print(input: TokenStream) -> TokenStream { // Parse the input as format arguments let parser = syn::punctuated::Punctuated::<Expr, Token![,]>::parse_terminated; @@ -47,19 +34,6 @@ pub fn r_print(input: TokenStream) -> TokenStream { expanded.into() } -/// Implementation of the `r_println!` procedural macro -/// -/// This macro expands to a call to `RenderResult::println` with formatted arguments. -/// It expects a mutable reference to a `RenderResult` named `r` to be in scope. -/// -/// # Examples -/// -/// ```ignore -/// use mingling_macros::r_println; -/// -/// let mut r = RenderResult::default(); -/// r_println!("Hello, {}!", "world"); -/// ``` pub fn r_println(input: TokenStream) -> TokenStream { // Parse the input as format arguments let parser = syn::punctuated::Punctuated::<Expr, Token![,]>::parse_terminated; diff --git a/mingling_macros/src/renderer.rs b/mingling_macros/src/renderer.rs index 3fd01bd..14c26df 100644 --- a/mingling_macros/src/renderer.rs +++ b/mingling_macros/src/renderer.rs @@ -25,15 +25,15 @@ impl Parse for RendererAttribute { /// Extracts the previous type and parameter name from function arguments fn extract_previous_info(sig: &Signature) -> syn::Result<(Pat, TypePath)> { - // The function should have exactly two parameters - if sig.inputs.len() != 2 { + // The function should have exactly one parameter + if sig.inputs.len() != 1 { return Err(syn::Error::new( sig.inputs.span(), - "Renderer function must have exactly two parameters", + "Renderer function must have exactly one parameter (the previous type)", )); } - // First parameter is the previous type + // First and only parameter is the previous type let arg = &sig.inputs[0]; match arg { FnArg::Typed(PatType { pat, ty, .. }) => { @@ -45,7 +45,7 @@ fn extract_previous_info(sig: &Signature) -> syn::Result<(Pat, TypePath)> { Type::Path(type_path) => Ok((param_pat, type_path.clone())), _ => Err(syn::Error::new( ty.span(), - "First parameter type must be a type path", + "Parameter type must be a type path", )), } } @@ -56,81 +56,6 @@ fn extract_previous_info(sig: &Signature) -> syn::Result<(Pat, TypePath)> { } } -/// Validates that the second parameter is r: &mut RenderResult -fn validate_render_result_param(sig: &Signature) -> syn::Result<()> { - // Second parameter should be &mut RenderResult - let arg = &sig.inputs[1]; - - match arg { - FnArg::Typed(PatType { pat, ty, .. }) => { - // Check parameter name is "r" - let param_name = match &**pat { - Pat::Ident(pat_ident) => pat_ident.ident.to_string(), - _ => { - return Err(syn::Error::new( - pat.span(), - "Second parameter must be named 'r'", - )); - } - }; - - if param_name != "r" { - return Err(syn::Error::new( - pat.span(), - "Second parameter must be named 'r'", - )); - } - - // Check type is &mut RenderResult - match &**ty { - Type::Reference(type_ref) => { - // Check mutability - if !type_ref.mutability.is_some() { - return Err(syn::Error::new( - ty.span(), - "Second parameter must be mutable reference: &mut RenderResult", - )); - } - - // Check inner type is RenderResult - match &*type_ref.elem { - Type::Path(type_path) => { - let type_name = - type_path.path.segments.last().unwrap().ident.to_string(); - if type_name != "RenderResult" { - return Err(syn::Error::new( - ty.span(), - "Second parameter must be &mut RenderResult", - )); - } - } - _ => { - return Err(syn::Error::new( - ty.span(), - "Second parameter must be &mut RenderResult", - )); - } - } - } - _ => { - return Err(syn::Error::new( - ty.span(), - "Second parameter must be &mut RenderResult", - )); - } - } - } - FnArg::Receiver(_) => { - return Err(syn::Error::new( - arg.span(), - "Renderer function cannot have self parameter", - )); - } - } - - Ok(()) -} - /// Extracts the return type from the function signature fn extract_return_type(sig: &Signature) -> syn::Result<()> { // Renderer functions should return () or have no return type @@ -149,35 +74,6 @@ fn extract_return_type(sig: &Signature) -> syn::Result<()> { } } -/// Implementation of the `#[renderer]` attribute macro -/// -/// This macro transforms a function into a struct that implements -/// the `Renderer` trait. The struct name is specified in the attribute. -/// -/// # Examples -/// -/// ```ignore -/// use mingling_macros::renderer; -/// -/// #[renderer(InitResultRenderer)] -/// fn render(data: InitResult, r: &mut RenderResult) { -/// let str: String = data.into(); -/// r_println!("{}", str); -/// } -/// ``` -/// -/// This generates: -/// ```ignore -/// pub struct InitResultRenderer; -/// impl Renderer for InitResultRenderer { -/// type Previous = InitResult; -/// -/// fn render(data: Self::Previous, r: &mut RenderResult) { -/// let str: String = data.into(); -/// r_println!("{}", str); -/// } -/// } -/// ``` pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream { // Parse the attribute arguments let renderer_attr = parse_macro_input!(attr as RendererAttribute); @@ -199,11 +95,6 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream { Err(e) => return e.to_compile_error().into(), }; - // Validate second parameter is r: &mut RenderResult - if let Err(e) = validate_render_result_param(&input_fn.sig) { - return e.to_compile_error().into(); - } - // Validate return type if let Err(e) = extract_return_type(&input_fn.sig) { return e.to_compile_error().into(); @@ -214,6 +105,7 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream { // Get function attributes (excluding the renderer attribute) let mut fn_attrs = input_fn.attrs.clone(); + // Remove any #[renderer(...)] attributes to avoid infinite recursion fn_attrs.retain(|attr| !attr.path().is_ident("renderer")); @@ -223,7 +115,19 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream { // Get function name let fn_name = &input_fn.sig.ident; + // Register the renderer in the global list + let renderer_entry = quote! { + #struct_name => #previous_type, + }; + + let mut renderers = crate::RENDERERS.lock().unwrap(); + let entry_str = renderer_entry.to_string(); + if !renderers.contains(&entry_str) { + renderers.push(entry_str); + } + // Generate the struct and implementation + // We need to create a wrapper function that adds the r parameter let expanded = quote! { #(#fn_attrs)* #vis struct #struct_name; @@ -232,14 +136,23 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream { type Previous = #previous_type; fn render(#prev_param: Self::Previous, r: &mut ::mingling::RenderResult) { - // Call the original function - #fn_name(#prev_param, r) + // Create a local wrapper function that includes r parameter + // This allows r_println! to access r + #[allow(non_snake_case)] + fn render_wrapper(#prev_param: #previous_type, r: &mut ::mingling::RenderResult) { + #fn_body + } + + // Call the wrapper function + render_wrapper(#prev_param, r); } } - // Keep the original function for internal use + // Keep the original function for internal use (without r parameter) #(#fn_attrs)* - #vis fn #fn_name(#prev_param: #previous_type, r: &mut ::mingling::RenderResult) { + #vis fn #fn_name(#prev_param: #previous_type) { + let mut dummy_r = ::mingling::RenderResult::default(); + let r = &mut dummy_r; #fn_body } }; |
