diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-04-19 00:31:05 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-04-19 00:31:05 +0800 |
| commit | 532f4ceba2bddb1c84d2e0bdd69808a3ebd5ca4a (patch) | |
| tree | 043514a2aca670d8b2398726b17aab1066938ba1 | |
| parent | ecc1329bbd31cd98fa9b4c2f25a3114f133987ae (diff) | |
Make async an optional feature
| -rw-r--r-- | examples/example-basic/Cargo.lock | 36 | ||||
| -rw-r--r-- | examples/example-basic/Cargo.toml | 1 | ||||
| -rw-r--r-- | examples/example-basic/src/main.rs | 7 | ||||
| -rw-r--r-- | examples/example-completion/Cargo.lock | 36 | ||||
| -rw-r--r-- | examples/example-completion/Cargo.toml | 1 | ||||
| -rw-r--r-- | examples/example-completion/src/main.rs | 7 | ||||
| -rw-r--r-- | examples/example-general-renderer/Cargo.lock | 36 | ||||
| -rw-r--r-- | examples/example-general-renderer/Cargo.toml | 1 | ||||
| -rw-r--r-- | examples/example-general-renderer/src/main.rs | 7 | ||||
| -rw-r--r-- | examples/example-picker/Cargo.lock | 8 | ||||
| -rw-r--r-- | examples/example-picker/src/main.rs | 7 | ||||
| -rw-r--r-- | mingling/Cargo.lock | 1 | ||||
| -rw-r--r-- | mingling/Cargo.toml | 1 | ||||
| -rw-r--r-- | mingling_core/Cargo.lock | 23 | ||||
| -rw-r--r-- | mingling_core/Cargo.toml | 2 | ||||
| -rw-r--r-- | mingling_core/src/asset/chain.rs | 4 | ||||
| -rw-r--r-- | mingling_core/src/program.rs | 127 | ||||
| -rw-r--r-- | mingling_core/src/program/exec.rs | 61 | ||||
| -rw-r--r-- | mingling_macros/Cargo.toml | 1 | ||||
| -rw-r--r-- | mingling_macros/src/chain.rs | 102 | ||||
| -rw-r--r-- | mingling_macros/src/lib.rs | 42 |
21 files changed, 303 insertions, 208 deletions
diff --git a/examples/example-basic/Cargo.lock b/examples/example-basic/Cargo.lock index 114e049..5e3ff98 100644 --- a/examples/example-basic/Cargo.lock +++ b/examples/example-basic/Cargo.lock @@ -3,17 +3,10 @@ version = 4 [[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - -[[package]] name = "example-basic" version = "0.0.1" dependencies = [ "mingling", - "tokio", ] [[package]] @@ -37,7 +30,6 @@ dependencies = [ "just_fmt", "once_cell", "thiserror", - "tokio", ] [[package]] @@ -58,12 +50,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[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" @@ -113,28 +99,6 @@ dependencies = [ ] [[package]] -name = "tokio" -version = "1.51.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" -dependencies = [ - "bytes", - "pin-project-lite", - "tokio-macros", -] - -[[package]] -name = "tokio-macros" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/examples/example-basic/Cargo.toml b/examples/example-basic/Cargo.toml index 49870d7..48c281a 100644 --- a/examples/example-basic/Cargo.toml +++ b/examples/example-basic/Cargo.toml @@ -5,4 +5,3 @@ edition = "2024" [dependencies] mingling = { path = "../../mingling" } -tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } diff --git a/examples/example-basic/src/main.rs b/examples/example-basic/src/main.rs index 9aaff06..a35cac6 100644 --- a/examples/example-basic/src/main.rs +++ b/examples/example-basic/src/main.rs @@ -13,8 +13,7 @@ use mingling::{ // Define dispatcher `HelloCommand`, directing subcommand "hello" to `HelloEntry` dispatcher!("hello", HelloCommand => HelloEntry); -#[tokio::main] -async fn main() { +fn main() { // Create program let mut program = ThisProgram::new(); @@ -22,7 +21,7 @@ async fn main() { program.with_dispatcher(HelloCommand); // Run program - program.exec().await; + program.exec(); } // Register wrapper type `Hello`, setting inner to `String` @@ -30,7 +29,7 @@ pack!(Hello = String); // Register chain to `ThisProgram`, handling logic from `HelloEntry` #[chain] -async fn parse_name(prev: HelloEntry) -> NextProcess { +fn parse_name(prev: HelloEntry) -> NextProcess { // Extract string from `HelloEntry` as argument let name = prev.first().cloned().unwrap_or_else(|| "World".to_string()); diff --git a/examples/example-completion/Cargo.lock b/examples/example-completion/Cargo.lock index 627bf49..08f1f22 100644 --- a/examples/example-completion/Cargo.lock +++ b/examples/example-completion/Cargo.lock @@ -3,17 +3,10 @@ version = 4 [[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - -[[package]] name = "example-completion" version = "0.0.1" dependencies = [ "mingling", - "tokio", ] [[package]] @@ -48,7 +41,6 @@ dependencies = [ "just_template", "once_cell", "thiserror", - "tokio", ] [[package]] @@ -69,12 +61,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[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" @@ -130,28 +116,6 @@ dependencies = [ ] [[package]] -name = "tokio" -version = "1.51.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" -dependencies = [ - "bytes", - "pin-project-lite", - "tokio-macros", -] - -[[package]] -name = "tokio-macros" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/examples/example-completion/Cargo.toml b/examples/example-completion/Cargo.toml index 984adaf..6a76d1f 100644 --- a/examples/example-completion/Cargo.toml +++ b/examples/example-completion/Cargo.toml @@ -5,4 +5,3 @@ edition = "2024" [dependencies] mingling = { path = "../../mingling", features = ["comp", "parser"] } -tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } diff --git a/examples/example-completion/src/main.rs b/examples/example-completion/src/main.rs index 807be3f..2e21a5c 100644 --- a/examples/example-completion/src/main.rs +++ b/examples/example-completion/src/main.rs @@ -54,12 +54,11 @@ fn comp_fruit_command(ctx: &ShellContext) -> Suggest { return suggest!(); } -#[tokio::main] -async fn main() { +fn main() { let mut program = ThisProgram::new(); program.with_dispatcher(CompletionDispatcher); program.with_dispatcher(FruitCommand); - program.exec().await; + program.exec(); } #[derive(Groupped)] @@ -98,7 +97,7 @@ enum FruitType { impl PickableEnum for FruitType {} #[chain] -async fn parse_fruit_info(prev: FruitEntry) -> NextProcess { +fn parse_fruit_info(prev: FruitEntry) -> NextProcess { let picker = Picker::<()>::from(prev.inner); let (fruit_name, fruit_type) = picker.pick("--name").pick("--type").unpack_directly(); let info = FruitInfo { diff --git a/examples/example-general-renderer/Cargo.lock b/examples/example-general-renderer/Cargo.lock index 6d9cb38..c7b5981 100644 --- a/examples/example-general-renderer/Cargo.lock +++ b/examples/example-general-renderer/Cargo.lock @@ -12,12 +12,6 @@ dependencies = [ ] [[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - -[[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -29,7 +23,6 @@ version = "0.0.1" dependencies = [ "mingling", "serde", - "tokio", ] [[package]] @@ -87,7 +80,6 @@ dependencies = [ "serde_json", "serde_yaml", "thiserror", - "tokio", "toml", ] @@ -109,12 +101,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[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" @@ -255,28 +241,6 @@ dependencies = [ ] [[package]] -name = "tokio" -version = "1.51.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" -dependencies = [ - "bytes", - "pin-project-lite", - "tokio-macros", -] - -[[package]] -name = "tokio-macros" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] name = "toml" version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/examples/example-general-renderer/Cargo.toml b/examples/example-general-renderer/Cargo.toml index 09aad80..cc5a541 100644 --- a/examples/example-general-renderer/Cargo.toml +++ b/examples/example-general-renderer/Cargo.toml @@ -9,4 +9,3 @@ mingling = { path = "../../mingling", features = [ "general_renderer", ] } serde = { version = "1", features = ["derive"] } -tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } diff --git a/examples/example-general-renderer/src/main.rs b/examples/example-general-renderer/src/main.rs index 28028cf..e879900 100644 --- a/examples/example-general-renderer/src/main.rs +++ b/examples/example-general-renderer/src/main.rs @@ -43,13 +43,12 @@ use serde::Serialize; dispatcher!("render", RenderCommand => RenderCommandEntry); -#[tokio::main] -async fn main() { +fn main() { let mut program = ThisProgram::new(); // Add `GeneralRendererSetup` to receive user input `--json` `--yaml` parameters program.with_setup(GeneralRendererSetup); program.with_dispatcher(RenderCommand); - program.exec().await; + program.exec(); } // Manually implement Info struct @@ -62,7 +61,7 @@ struct Info { } #[chain] -async fn parse_render(prev: RenderCommandEntry) -> NextProcess { +fn parse_render(prev: RenderCommandEntry) -> NextProcess { let (name, age) = Picker::<()>::new(prev.inner) .pick::<String>(()) .pick::<i32>(()) diff --git a/examples/example-picker/Cargo.lock b/examples/example-picker/Cargo.lock index 7f22d09..0586812 100644 --- a/examples/example-picker/Cargo.lock +++ b/examples/example-picker/Cargo.lock @@ -3,12 +3,6 @@ version = 4 [[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - -[[package]] name = "example-picker" version = "0.0.1" dependencies = [ @@ -38,7 +32,6 @@ dependencies = [ "just_fmt", "once_cell", "thiserror", - "tokio", ] [[package]] @@ -125,7 +118,6 @@ version = "1.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" dependencies = [ - "bytes", "pin-project-lite", "tokio-macros", ] diff --git a/examples/example-picker/src/main.rs b/examples/example-picker/src/main.rs index 7619b98..6bc4bfd 100644 --- a/examples/example-picker/src/main.rs +++ b/examples/example-picker/src/main.rs @@ -25,18 +25,17 @@ use mingling::{ dispatcher!("pick", PickCommand => PickEntry); -#[tokio::main] -async fn main() { +fn main() { let mut program = ThisProgram::new(); program.with_dispatcher(PickCommand); - program.exec().await; + program.exec(); } pack!(NoNameProvided = ()); pack!(ParsedPickInput = (i32, String)); #[chain] -async fn parse(prev: PickEntry) -> NextProcess { +fn parse(prev: PickEntry) -> NextProcess { // Extract arguments from `PickEntry`'s inner and create a `Picker` let picker = Picker::new(prev.inner); let picked = picker diff --git a/mingling/Cargo.lock b/mingling/Cargo.lock index cdc62c3..77afe87 100644 --- a/mingling/Cargo.lock +++ b/mingling/Cargo.lock @@ -247,7 +247,6 @@ dependencies = [ "serde_json", "serde_yaml", "thiserror", - "tokio", "toml", ] diff --git a/mingling/Cargo.toml b/mingling/Cargo.toml index 3d08662..9472cb2 100644 --- a/mingling/Cargo.toml +++ b/mingling/Cargo.toml @@ -17,6 +17,7 @@ mingling = { path = ".", features = ["full"] } [features] debug = ["mingling_core/debug"] +async = ["mingling_core/async", "mingling_macros/async"] default = ["mingling_core/default"] full = ["mingling_core/full", "mingling_macros/full", "comp", "parser"] general_renderer = [ diff --git a/mingling_core/Cargo.lock b/mingling_core/Cargo.lock index 5755757..43c61b6 100644 --- a/mingling_core/Cargo.lock +++ b/mingling_core/Cargo.lock @@ -71,12 +71,6 @@ dependencies = [ ] [[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - -[[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -204,7 +198,6 @@ dependencies = [ "serde_json", "serde_yaml", "thiserror", - "tokio", "toml", ] @@ -221,12 +214,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] name = "portable-atomic" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -405,16 +392,6 @@ dependencies = [ ] [[package]] -name = "tokio" -version = "1.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" -dependencies = [ - "bytes", - "pin-project-lite", -] - -[[package]] name = "toml" version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/mingling_core/Cargo.toml b/mingling_core/Cargo.toml index 3350c48..62dd3c8 100644 --- a/mingling_core/Cargo.toml +++ b/mingling_core/Cargo.toml @@ -8,6 +8,7 @@ repository = "https://github.com/catilgrass/mingling" [features] default = [] +async = [] full = ["comp", "general_renderer"] comp = ["dep:just_template"] debug = ["dep:log", "dep:env_logger"] @@ -22,7 +23,6 @@ general_renderer = [ [dependencies] just_fmt = "0.1.2" thiserror = "2" -tokio = { version = "1", features = ["io-std", "io-util"] } once_cell = "1.21.4" diff --git a/mingling_core/src/asset/chain.rs b/mingling_core/src/asset/chain.rs index 70eda8c..c41b716 100644 --- a/mingling_core/src/asset/chain.rs +++ b/mingling_core/src/asset/chain.rs @@ -14,5 +14,9 @@ where type Previous; /// Process the previous value and return a future that resolves to a [`ChainProcess<G>`](./enum.ChainProcess.html) + #[cfg(feature = "async")] fn proc(p: Self::Previous) -> impl Future<Output = ChainProcess<G>> + Send; + + #[cfg(not(feature = "async"))] + fn proc(p: Self::Previous) -> ChainProcess<G>; } diff --git a/mingling_core/src/program.rs b/mingling_core/src/program.rs index 7ea3a38..ce4a5fa 100644 --- a/mingling_core/src/program.rs +++ b/mingling_core/src/program.rs @@ -8,7 +8,10 @@ use crate::{ AnyOutput, ChainProcess, RenderResult, asset::dispatcher::Dispatcher, error::ProgramExecuteError, }; -use std::{fmt::Display, pin::Pin, sync::OnceLock}; +use std::{fmt::Display, sync::OnceLock}; + +#[cfg(feature = "async")] +use std::pin::Pin; #[doc(hidden)] pub mod exec; @@ -20,7 +23,6 @@ pub use config::*; mod flag; pub use flag::*; -use tokio::io::AsyncWriteExt; /// Global static reference to the current program instance static THIS_PROGRAM: OnceLock<Option<Box<dyn std::any::Any + Send + Sync>>> = OnceLock::new(); @@ -97,7 +99,7 @@ where } /// Returns a reference to the current program instance, if set. - pub async fn this_program() -> &'static Program<C, G> + pub fn this_program() -> &'static Program<C, G> where C: 'static, G: 'static, @@ -111,6 +113,19 @@ where .unwrap() } + // Get all registered dispatcher names from the program + pub fn get_nodes(&self) -> Vec<(String, &(dyn Dispatcher<G> + Send + Sync))> { + get_nodes(self) + } +} + +// Async program +#[cfg(feature = "async")] +impl<C, G> Program<C, G> +where + C: ProgramCollect<Enum = G>, + G: Display, +{ /// Sets the current program instance and runs the provided async function. async fn set_instance_and_run<F, Fut>(self, f: F) -> Fut::Output where @@ -169,17 +184,84 @@ where // Render result if stdout_setting.render_output && !result.is_empty() { print!("{}", result); - if let Err(e) = tokio::io::stdout().flush().await + if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) && stdout_setting.error_output { eprintln!("{}", e); } } } +} - // Get all registered dispatcher names from the program - pub fn get_nodes(&self) -> Vec<(String, &(dyn Dispatcher<G> + Send + Sync))> { - get_nodes(self) +// Sync program +#[cfg(not(feature = "async"))] +impl<C, G> Program<C, G> +where + C: ProgramCollect<Enum = G>, + G: Display, +{ + /// Sets the current program instance and runs the provided function. + fn set_instance_and_run<F, R>(self, f: F) -> R + where + C: 'static + Send + Sync, + G: 'static + Send + Sync, + F: FnOnce(&'static Program<C, G>) -> R + Send + Sync, + { + THIS_PROGRAM.get_or_init(|| Some(Box::new(self))); + let program = THIS_PROGRAM + .get() + .unwrap() + .as_ref() + .unwrap() + .downcast_ref::<Program<C, G>>() + .unwrap(); + f(program) + } + + /// Run the command line program + pub fn exec_without_render(mut self) -> Result<RenderResult, ProgramExecuteError> + where + C: 'static + Send + Sync, + G: 'static + Send + Sync, + { + self.args = self.args.iter().skip(1).cloned().collect(); + self.set_instance_and_run(|p| crate::exec::exec(p).map_err(|e| e.into())) + } + + /// Run the command line program + pub fn exec(self) + where + C: 'static + Send + Sync, + G: 'static + Send + Sync, + { + let stdout_setting = self.stdout_setting.clone(); + let result = match self.exec_without_render() { + Ok(r) => r, + Err(e) => match e { + ProgramExecuteError::DispatcherNotFound => { + eprintln!("Dispatcher not found"); + return; + } + ProgramExecuteError::RendererNotFound(renderer_name) => { + eprintln!("Renderer `{}` not found", renderer_name); + return; + } + ProgramExecuteError::Other(e) => { + eprintln!("{}", e); + return; + } + }, + }; + + // Render result + if stdout_setting.render_output && !result.is_empty() { + print!("{}", result); + if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) + && stdout_setting.error_output + { + eprintln!("{}", e); + } + } } } @@ -200,10 +282,15 @@ pub trait ProgramCollect { fn render(any: AnyOutput<Self::Enum>, r: &mut RenderResult); /// Find a matching chain to continue execution based on the input [AnyOutput](./struct.AnyOutput.html), returning a new [AnyOutput](./struct.AnyOutput.html) + #[cfg(feature = "async")] fn do_chain( any: AnyOutput<Self::Enum>, ) -> Pin<Box<dyn Future<Output = ChainProcess<Self::Enum>> + Send>>; + /// Find a matching chain to continue execution based on the input [AnyOutput](./struct.AnyOutput.html), returning a new [AnyOutput](./struct.AnyOutput.html) + #[cfg(not(feature = "async"))] + fn do_chain(any: AnyOutput<Self::Enum>) -> ChainProcess<Self::Enum>; + /// Match and execute specific completion logic based on any Entry #[cfg(feature = "comp")] fn do_comp(any: &AnyOutput<Self::Enum>, ctx: &ShellContext) -> Suggest; @@ -246,6 +333,7 @@ macro_rules! __dispatch_program_renderers { #[macro_export] #[doc(hidden)] +#[cfg(feature = "async")] macro_rules! __dispatch_program_chains { ( $( $chain_ty:ty => $chain_prev:ident, )* @@ -269,6 +357,31 @@ macro_rules! __dispatch_program_chains { }; } +#[macro_export] +#[doc(hidden)] +#[cfg(not(feature = "async"))] +macro_rules! __dispatch_program_chains { + ( + $( $chain_ty:ty => $chain_prev:ident, )* + ) => { + fn do_chain( + any: mingling::AnyOutput<Self::Enum>, + ) -> mingling::ChainProcess<Self::Enum> { + match any.member_id { + $( + Self::$chain_prev => { + // SAFETY: The `type_id` check ensures that `any` contains a value of type `$chain_prev`, + // so downcasting to `$chain_prev` is safe. + let value = unsafe { any.downcast::<$chain_prev>().unwrap_unchecked() }; + <$chain_ty as mingling::Chain<Self::Enum>>::proc(value) + } + )* + _ => panic!("No chain found for type id: {:?}", any.type_id), + } + } + }; +} + /// Get all registered dispatcher names from the program pub fn get_nodes<C: ProgramCollect<Enum = G>, G: Display>( program: &Program<C, G>, diff --git a/mingling_core/src/program/exec.rs b/mingling_core/src/program/exec.rs index ff7e92b..8ab2036 100644 --- a/mingling_core/src/program/exec.rs +++ b/mingling_core/src/program/exec.rs @@ -10,6 +10,7 @@ use crate::{ #[doc(hidden)] pub mod error; +#[cfg(feature = "async")] pub async fn exec<C, G>( program: &Program<C, G>, ) -> Result<RenderResult, ProgramInternalExecuteError> @@ -71,6 +72,66 @@ where Ok(RenderResult::default()) } +#[cfg(not(feature = "async"))] +pub fn exec<C, G>(program: &Program<C, G>) -> Result<RenderResult, ProgramInternalExecuteError> +where + C: ProgramCollect<Enum = G>, + G: Display, +{ + let mut current; + let mut stop_next = false; + + // Match user input + match match_user_input(program, program.args.clone()) { + Ok((dispatcher, args)) => { + // Entry point + current = match dispatcher.begin(args) { + ChainProcess::Ok((any, Next::Renderer)) => { + return Ok(render::<C, G>(program, any)); + } + ChainProcess::Ok((any, Next::Chain)) => any, + ChainProcess::Err(e) => return Err(e.into()), + }; + } + Err(ProgramInternalExecuteError::DispatcherNotFound) => { + // No matching Dispatcher is found + current = C::build_dispatcher_not_found(program.args.clone()); + } + Err(e) => return Err(e), + }; + + loop { + let final_exec = stop_next; + + current = { + // If a chain exists, execute as a chain + if C::has_chain(¤t) { + match C::do_chain(current) { + ChainProcess::Ok((any, Next::Renderer)) => { + return Ok(render::<C, G>(program, any)); + } + ChainProcess::Ok((any, Next::Chain)) => any, + ChainProcess::Err(e) => return Err(e.into()), + } + } + // If no chain exists, attempt to render + else if C::has_renderer(¤t) { + return Ok(render::<C, G>(program, current)); + } + // No renderer exists + else { + stop_next = true; + C::build_renderer_not_found(current.member_id) + } + }; + + if final_exec && stop_next { + break; + } + } + Ok(RenderResult::default()) +} + /// Match user input against registered dispatchers and return the matched dispatcher and remaining arguments. #[allow(clippy::type_complexity)] pub fn match_user_input<C, G>( diff --git a/mingling_macros/Cargo.toml b/mingling_macros/Cargo.toml index 1154c3d..1875cb0 100644 --- a/mingling_macros/Cargo.toml +++ b/mingling_macros/Cargo.toml @@ -11,6 +11,7 @@ proc-macro = true [features] default = [] +async = [] full = ["comp", "general_renderer"] comp = [] general_renderer = [] diff --git a/mingling_macros/src/chain.rs b/mingling_macros/src/chain.rs index 14e62ec..ed36e10 100644 --- a/mingling_macros/src/chain.rs +++ b/mingling_macros/src/chain.rs @@ -1,7 +1,10 @@ //! Chain Attribute Macro Implementation //! //! This module provides the `#[chain(Group)]` attribute macro for automatically -//! generating structs that implement the `Chain` trait from async functions. +//! generating structs that implement the `Chain` trait from functions. +//! +//! When the `async` feature is enabled, chain functions must be async functions. +//! When the `async` feature is disabled, chain functions can be regular functions. use proc_macro::TokenStream; use quote::{ToTokens, quote}; @@ -74,11 +77,30 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { // Parse the function item let input_fn = parse_macro_input!(item as ItemFn); - // Validate the function - if input_fn.sig.asyncness.is_none() { - return syn::Error::new(input_fn.sig.span(), "Chain function must be async") + // Validate the chain functions is a async function + #[cfg(feature = "async")] + { + if input_fn.sig.asyncness.is_none() { + return syn::Error::new( + input_fn.sig.span(), + "Chain function must be async when async feature is enabled", + ) + .to_compile_error() + .into(); + } + } + + // Validate the chain functions is a regular function + #[cfg(not(feature = "async"))] + { + if input_fn.sig.asyncness.is_some() { + return syn::Error::new( + input_fn.sig.span(), + "Chain function cannot be async when async feature is disabled", + ) .to_compile_error() .into(); + } } // Extract the previous type and parameter name from function arguments @@ -122,6 +144,48 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { let pascal_case_name = just_fmt::pascal_case!(fn_name.to_string()); let struct_name = Ident::new(&pascal_case_name, fn_name.span()); + #[cfg(feature = "async")] + let proc_fn = quote! { + async fn proc(#prev_param: Self::Previous) -> + ::mingling::ChainProcess<ThisProgram> + { + let _ = NextProcess; + // Call the original function + #fn_name(#prev_param).await + } + }; + + #[cfg(feature = "async")] + let origin_proc_fn = quote! { + #(#fn_attrs)* + #vis async fn #fn_name(#prev_param: #previous_type) + -> ::mingling::ChainProcess<#group_name> + { + #fn_body + } + }; + + #[cfg(not(feature = "async"))] + let proc_fn = quote! { + fn proc(#prev_param: Self::Previous) -> + ::mingling::ChainProcess<ThisProgram> + { + let _ = NextProcess; + // Call the original function + #fn_name(#prev_param) + } + }; + + #[cfg(not(feature = "async"))] + let origin_proc_fn = quote! { + #(#fn_attrs)* + #vis fn #fn_name(#prev_param: #previous_type) + -> ::mingling::ChainProcess<#group_name> + { + #fn_body + } + }; + // Generate the struct and implementation let expanded = if use_crate_prefix { quote! { @@ -134,22 +198,11 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { impl ::mingling::Chain<ThisProgram> for #struct_name { type Previous = #previous_type; - async fn proc(#prev_param: Self::Previous) -> - ::mingling::ChainProcess<ThisProgram> - { - let _ = NextProcess; - // Call the original function - #fn_name(#prev_param).await - } + #proc_fn } // Keep the original function for internal use - #(#fn_attrs)* - #vis async fn #fn_name(#prev_param: #previous_type) - -> ::mingling::ChainProcess<ThisProgram> - { - #fn_body - } + #origin_proc_fn } } else { quote! { @@ -161,22 +214,11 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { impl ::mingling::Chain<#group_name> for #struct_name { type Previous = #previous_type; - async fn proc(#prev_param: Self::Previous) -> - ::mingling::ChainProcess<#group_name> - { - let _ = NextProcess; - // Call the original function - #fn_name(#prev_param).await - } + #proc_fn } // Keep the original function for internal use - #(#fn_attrs)* - #vis async fn #fn_name(#prev_param: #previous_type) - -> ::mingling::ChainProcess<#group_name> - { - #fn_body - } + #origin_proc_fn } }; diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs index 5391dfd..4ae9e43 100644 --- a/mingling_macros/src/lib.rs +++ b/mingling_macros/src/lib.rs @@ -132,6 +132,36 @@ pub fn gen_program(input: TokenStream) -> TokenStream { pub fn program_gen_completion(input: TokenStream) -> TokenStream { let name = read_name(&input); + #[cfg(feature = "async")] + let fn_exec_comp = quote! { + #[::mingling::macros::chain(#name)] + pub async fn __exec_completion(prev: CompletionContext) -> NextProcess { + let read_ctx = ::mingling::ShellContext::try_from(prev.inner); + match read_ctx { + Ok(ctx) => { + let suggest = ::mingling::CompletionHelper::exec_completion::<#name>(&ctx); + CompletionSuggest::new((ctx, suggest)).to_render() + } + Err(_) => std::process::exit(1), + } + } + }; + + #[cfg(not(feature = "async"))] + let fn_exec_comp = quote! { + #[::mingling::macros::chain(#name)] + pub fn __exec_completion(prev: CompletionContext) -> NextProcess { + let read_ctx = ::mingling::ShellContext::try_from(prev.inner); + match read_ctx { + Ok(ctx) => { + let suggest = ::mingling::CompletionHelper::exec_completion::<#name>(&ctx); + CompletionSuggest::new((ctx, suggest)).to_render() + } + Err(_) => std::process::exit(1), + } + } + }; + let comp_dispatcher = quote! { #[allow(unused)] use __completion_gen::*; @@ -144,17 +174,7 @@ pub fn program_gen_completion(input: TokenStream) -> TokenStream { CompletionSuggest = (::mingling::ShellContext, ::mingling::Suggest) ); - #[::mingling::macros::chain(#name)] - pub async fn __exec_completion(prev: CompletionContext) -> NextProcess { - let read_ctx = ::mingling::ShellContext::try_from(prev.inner); - match read_ctx { - Ok(ctx) => { - let suggest = ::mingling::CompletionHelper::exec_completion::<#name>(&ctx); - CompletionSuggest::new((ctx, suggest)).to_render() - } - Err(_) => std::process::exit(1), - } - } + #fn_exec_comp ::mingling::macros::register_type!(CompletionContext); |
