aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-04-19 00:31:05 +0800
committer魏曹先生 <1992414357@qq.com>2026-04-19 00:31:05 +0800
commit532f4ceba2bddb1c84d2e0bdd69808a3ebd5ca4a (patch)
tree043514a2aca670d8b2398726b17aab1066938ba1
parentecc1329bbd31cd98fa9b4c2f25a3114f133987ae (diff)
Make async an optional feature
-rw-r--r--examples/example-basic/Cargo.lock36
-rw-r--r--examples/example-basic/Cargo.toml1
-rw-r--r--examples/example-basic/src/main.rs7
-rw-r--r--examples/example-completion/Cargo.lock36
-rw-r--r--examples/example-completion/Cargo.toml1
-rw-r--r--examples/example-completion/src/main.rs7
-rw-r--r--examples/example-general-renderer/Cargo.lock36
-rw-r--r--examples/example-general-renderer/Cargo.toml1
-rw-r--r--examples/example-general-renderer/src/main.rs7
-rw-r--r--examples/example-picker/Cargo.lock8
-rw-r--r--examples/example-picker/src/main.rs7
-rw-r--r--mingling/Cargo.lock1
-rw-r--r--mingling/Cargo.toml1
-rw-r--r--mingling_core/Cargo.lock23
-rw-r--r--mingling_core/Cargo.toml2
-rw-r--r--mingling_core/src/asset/chain.rs4
-rw-r--r--mingling_core/src/program.rs127
-rw-r--r--mingling_core/src/program/exec.rs61
-rw-r--r--mingling_macros/Cargo.toml1
-rw-r--r--mingling_macros/src/chain.rs102
-rw-r--r--mingling_macros/src/lib.rs42
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(&current) {
+ 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(&current) {
+ 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);