summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mingling/Cargo.lock181
-rw-r--r--mingling/Cargo.toml1
-rw-r--r--mingling/src/any.rs2
-rw-r--r--mingling/src/asset/dispatcher.rs192
-rw-r--r--mingling/src/lib.rs8
-rw-r--r--mingling/src/program.rs107
-rw-r--r--mingling/src/program/config.rs2
-rw-r--r--mingling/src/program/exec.rs130
-rw-r--r--mingling/src/program/exec/error.rs44
-rw-r--r--mingling/src/program/flag.rs41
-rw-r--r--mingling/src/program/hint.rs52
-rw-r--r--mingling/src/program/setup.rs16
-rw-r--r--mingling/src/program/setup/basic.rs12
-rw-r--r--mingling_macros/Cargo.lock7
-rw-r--r--mingling_macros/Cargo.toml1
-rw-r--r--mingling_macros/src/chain.rs36
-rw-r--r--mingling_macros/src/chain_struct.rs23
-rw-r--r--mingling_macros/src/dispatcher.rs72
-rw-r--r--mingling_macros/src/dispatcher_chain.rs94
-rw-r--r--mingling_macros/src/lib.rs151
-rw-r--r--mingling_macros/src/node.rs10
-rw-r--r--mingling_macros/src/render.rs26
-rw-r--r--mingling_macros/src/renderer.rs149
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
}
};