aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-05-02 01:38:36 +0800
committer魏曹先生 <1992414357@qq.com>2026-05-02 01:40:11 +0800
commit81be96847833bd443ddb157cedb7939d8ffcc150 (patch)
treeb8f476d4430ed435f3ac93a1553219a349c0baca
parent1da9fc6103c06015942cf6c06f5fe015479c2706 (diff)
Enforce `NextProcess` return type in chain functions and update examples
-rw-r--r--examples/example-async/src/main.rs2
-rw-r--r--examples/example-basic/src/main.rs2
-rw-r--r--examples/example-completion/src/main.rs2
-rw-r--r--examples/example-general-renderer/src/main.rs2
-rw-r--r--examples/example-picker/src/main.rs2
-rw-r--r--mingling/src/example_docs.rs119
-rw-r--r--mingling_macros/src/chain.rs38
-rw-r--r--mingling_macros/src/lib.rs14
8 files changed, 107 insertions, 74 deletions
diff --git a/examples/example-async/src/main.rs b/examples/example-async/src/main.rs
index 21e27aa..15aba7d 100644
--- a/examples/example-async/src/main.rs
+++ b/examples/example-async/src/main.rs
@@ -36,7 +36,7 @@ pack!(Hello = String);
#[chain]
// fn parse_name(prev: HelloEntry) -> NextProcess {
-async fn parse_name(prev: HelloEntry) -> mingling::ChainProcess<ThisProgram> {
+async fn parse_name(prev: HelloEntry) -> NextProcess {
let name = prev.first().cloned().unwrap_or_else(|| "World".to_string());
Hello::new(name).to_render()
}
diff --git a/examples/example-basic/src/main.rs b/examples/example-basic/src/main.rs
index 944a914..79853ad 100644
--- a/examples/example-basic/src/main.rs
+++ b/examples/example-basic/src/main.rs
@@ -26,7 +26,7 @@ pack!(Hello = String);
// Register chain to `ThisProgram`, handling logic from `HelloEntry`
#[chain]
-fn parse_name(prev: HelloEntry) -> ChainProcess<ThisProgram> {
+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/src/main.rs b/examples/example-completion/src/main.rs
index 28e3be4..7834db7 100644
--- a/examples/example-completion/src/main.rs
+++ b/examples/example-completion/src/main.rs
@@ -96,7 +96,7 @@ enum FruitType {
impl PickableEnum for FruitType {}
#[chain]
-fn parse_fruit_info(prev: FruitEntry) -> ChainProcess<ThisProgram> {
+fn parse_fruit_info(prev: FruitEntry) -> NextProcess {
let picker = Picker::from(prev.inner);
let (fruit_name, fruit_type) = picker.pick("--name").pick("--type").unpack();
let info = FruitInfo {
diff --git a/examples/example-general-renderer/src/main.rs b/examples/example-general-renderer/src/main.rs
index 0f4fce9..23f1eab 100644
--- a/examples/example-general-renderer/src/main.rs
+++ b/examples/example-general-renderer/src/main.rs
@@ -60,7 +60,7 @@ struct Info {
}
#[chain]
-fn parse_render(prev: RenderCommandEntry) -> ChainProcess<ThisProgram> {
+fn parse_render(prev: RenderCommandEntry) -> NextProcess {
let (name, age) = Picker::new(prev.inner)
.pick::<String>(())
.pick::<i32>(())
diff --git a/examples/example-picker/src/main.rs b/examples/example-picker/src/main.rs
index 5653368..fa895fc 100644
--- a/examples/example-picker/src/main.rs
+++ b/examples/example-picker/src/main.rs
@@ -34,7 +34,7 @@ pack!(NoNameProvided = ());
pack!(ParsedPickInput = (i32, String));
#[chain]
-fn parse(prev: PickEntry) -> mingling::ChainProcess<ThisProgram> {
+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/src/example_docs.rs b/mingling/src/example_docs.rs
index 81eb2ef..c23db8d 100644
--- a/mingling/src/example_docs.rs
+++ b/mingling/src/example_docs.rs
@@ -1,136 +1,133 @@
// Auto generated
-/// `Mingling` Example - Async
-///
-/// After enabling the `async` feature:
-/// 1. The `chain!` macro will support using **async** functions,
-/// 2. The `exec` function of `Program` will return a `Future` for you to use with an async runtime
-///
-/// ## Enable Feature
-/// Enable the `async` feature for mingling in `Cargo.toml`
-/// ```toml
-/// [dependencies]
-/// mingling = { version = "...", features = ["async"] }
-/// ```
+/// `Mingling` Example - Basic
///
/// # How to Run
/// ```bash
-/// cargo run --manifest-path ./examples/example-async/Cargo.toml -- hello World
+/// cargo run --manifest-path ./examples/example-basic/Cargo.toml -- hello World
/// ```
///
/// Cargo.toml
/// ```ignore
/// [package]
-/// name = "example-async"
+/// name = "example-basic"
/// version = "0.0.1"
/// edition = "2024"
///
/// [dependencies]
-/// tokio = { version = "1", features = ["full"] }
-/// mingling = { path = "../../mingling", features = ["async"] }
+/// mingling = { path = "../../mingling" }
/// ```
///
/// main.rs
/// ```ignore
/// use mingling::macros::{chain, dispatcher, gen_program, pack, r_println, renderer};
///
+/// // Define dispatcher `HelloCommand`, directing subcommand "hello" to `HelloEntry`
/// dispatcher!("hello", HelloCommand => HelloEntry);
///
-/// // Use Tokio async runtime
-/// #[tokio::main]
-/// async fn main() {
+/// fn main() {
+/// // Create program
/// let mut program = ThisProgram::new();
+///
+/// // Add dispatcher `HelloCommand`
/// program.with_dispatcher(HelloCommand);
///
/// // Run program
-/// program.exec().await;
+/// program.exec();
/// }
///
+/// // Register wrapper type `Hello`, setting inner to `String`
/// pack!(Hello = String);
///
-/// // You can freely use async / non-async functions to declare your Chain
-///
+/// // Register chain to `ThisProgram`, handling logic from `HelloEntry`
/// #[chain]
-/// // fn parse_name(prev: HelloEntry) -> NextProcess {
-/// async fn parse_name(prev: HelloEntry) -> mingling::ChainProcess<ThisProgram> {
+/// fn parse_name(prev: HelloEntry) -> NextProcess {
+/// // Extract string from `HelloEntry` as argument
/// let name = prev.first().cloned().unwrap_or_else(|| "World".to_string());
+///
+/// // Build `Hello` type and route to renderer
/// Hello::new(name).to_render()
/// }
///
-/// // For renderers, you can still only use synchronous functions
+/// // Register renderer to `ThisProgram`, handling rendering of `Hello`
/// #[renderer]
/// fn render_hello_who(prev: Hello) {
+/// // Print message
/// r_println!("Hello, {}!", *prev);
+///
+/// // Program ends here
/// }
///
+/// // Generate program, default is `ThisProgram`
/// gen_program!();
/// ```
-pub mod example_async {}
-/// `Mingling` Example - Basic
+pub mod example_basic {}
+/// `Mingling` Example - Async
+///
+/// After enabling the `async` feature:
+/// 1. The `chain!` macro will support using **async** functions,
+/// 2. The `exec` function of `Program` will return a `Future` for you to use with an async runtime
+///
+/// ## Enable Feature
+/// Enable the `async` feature for mingling in `Cargo.toml`
+/// ```toml
+/// [dependencies]
+/// mingling = { version = "...", features = ["async"] }
+/// ```
///
/// # How to Run
/// ```bash
-/// cargo run --manifest-path ./examples/example-basic/Cargo.toml -- hello World
+/// cargo run --manifest-path ./examples/example-async/Cargo.toml -- hello World
/// ```
///
/// Cargo.toml
/// ```ignore
/// [package]
-/// name = "example-basic"
+/// name = "example-async"
/// version = "0.0.1"
/// edition = "2024"
///
/// [dependencies]
-/// mingling = { path = "../../mingling" }
+/// tokio = { version = "1", features = ["full"] }
+/// mingling = { path = "../../mingling", features = ["async"] }
/// ```
///
/// main.rs
/// ```ignore
-/// use mingling::{
-/// ChainProcess,
-/// macros::{chain, dispatcher, gen_program, pack, r_println, renderer},
-/// };
+/// use mingling::macros::{chain, dispatcher, gen_program, pack, r_println, renderer};
///
-/// // Define dispatcher `HelloCommand`, directing subcommand "hello" to `HelloEntry`
/// dispatcher!("hello", HelloCommand => HelloEntry);
///
-/// fn main() {
-/// // Create program
+/// // Use Tokio async runtime
+/// #[tokio::main]
+/// async fn main() {
/// let mut program = ThisProgram::new();
-///
-/// // Add dispatcher `HelloCommand`
/// program.with_dispatcher(HelloCommand);
///
/// // Run program
-/// program.exec();
+/// program.exec().await;
/// }
///
-/// // Register wrapper type `Hello`, setting inner to `String`
/// pack!(Hello = String);
///
-/// // Register chain to `ThisProgram`, handling logic from `HelloEntry`
+/// // You can freely use async / non-async functions to declare your Chain
+///
/// #[chain]
-/// fn parse_name(prev: HelloEntry) -> ChainProcess<ThisProgram> {
-/// // Extract string from `HelloEntry` as argument
+/// // fn parse_name(prev: HelloEntry) -> NextProcess {
+/// async fn parse_name(prev: HelloEntry) -> NextProcess {
/// let name = prev.first().cloned().unwrap_or_else(|| "World".to_string());
-///
-/// // Build `Hello` type and route to renderer
/// Hello::new(name).to_render()
/// }
///
-/// // Register renderer to `ThisProgram`, handling rendering of `Hello`
+/// // For renderers, you can still only use synchronous functions
/// #[renderer]
/// fn render_hello_who(prev: Hello) {
-/// // Print message
/// r_println!("Hello, {}!", *prev);
-///
-/// // Program ends here
/// }
///
-/// // Generate program, default is `ThisProgram`
/// gen_program!();
/// ```
-pub mod example_basic {}
+pub mod example_async {}
/// `Mingling` Example - Completion
///
/// # How to Deploy
@@ -171,7 +168,7 @@ pub mod example_basic {}
/// main.rs
/// ```ignore
/// use mingling::{
-/// ChainProcess, EnumTag, Groupped, ShellContext, Suggest,
+/// EnumTag, Groupped, ShellContext, Suggest,
/// macros::{
/// chain, completion, dispatcher, gen_program, r_println, renderer, suggest, suggest_enum,
/// },
@@ -242,9 +239,9 @@ pub mod example_basic {}
/// impl PickableEnum for FruitType {}
///
/// #[chain]
-/// fn parse_fruit_info(prev: FruitEntry) -> ChainProcess<ThisProgram> {
-/// let picker = Picker::<()>::from(prev.inner);
-/// let (fruit_name, fruit_type) = picker.pick("--name").pick("--type").unpack_directly();
+/// fn parse_fruit_info(prev: FruitEntry) -> NextProcess {
+/// let picker = Picker::from(prev.inner);
+/// let (fruit_name, fruit_type) = picker.pick("--name").pick("--type").unpack();
/// let info = FruitInfo {
/// name: fruit_name,
/// fruit_type,
@@ -325,7 +322,7 @@ pub mod example_completion {}
/// main.rs
/// ```ignore
/// use mingling::{
-/// ChainProcess, Groupped,
+/// Groupped,
/// macros::{chain, dispatcher, gen_program, r_println, renderer},
/// parser::Picker,
/// setup::GeneralRendererSetup,
@@ -352,11 +349,11 @@ pub mod example_completion {}
/// }
///
/// #[chain]
-/// fn parse_render(prev: RenderCommandEntry) -> ChainProcess<ThisProgram> {
-/// let (name, age) = Picker::<()>::new(prev.inner)
+/// fn parse_render(prev: RenderCommandEntry) -> NextProcess {
+/// let (name, age) = Picker::new(prev.inner)
/// .pick::<String>(())
/// .pick::<i32>(())
-/// .unpack_directly();
+/// .unpack();
/// Info { name, age }.to_render()
/// }
///
@@ -419,7 +416,7 @@ pub mod example_general_renderer {}
/// pack!(ParsedPickInput = (i32, String));
///
/// #[chain]
-/// fn parse(prev: PickEntry) -> mingling::ChainProcess<ThisProgram> {
+/// 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_macros/src/chain.rs b/mingling_macros/src/chain.rs
index a91949d..e7b2db2 100644
--- a/mingling_macros/src/chain.rs
+++ b/mingling_macros/src/chain.rs
@@ -1,7 +1,9 @@
use proc_macro::TokenStream;
use quote::{ToTokens, quote};
use syn::spanned::Spanned;
-use syn::{FnArg, Ident, ItemFn, Pat, PatType, Signature, Type, TypePath, parse_macro_input};
+use syn::{
+ FnArg, Ident, ItemFn, Pat, PatType, ReturnType, Signature, Type, TypePath, parse_macro_input,
+};
/// Extracts the previous type and parameter name from function arguments
fn extract_previous_info(sig: &Signature) -> syn::Result<(Pat, TypePath)> {
@@ -67,6 +69,40 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
}
}
+ // Check that return type is NextProcess
+ let return_type = &input_fn.sig.output;
+ match return_type {
+ ReturnType::Type(_, ty) => {
+ // Check if the return type is NextProcess
+ match &**ty {
+ Type::Path(type_path) => {
+ let last_segment = type_path.path.segments.last().unwrap();
+ if last_segment.ident.to_string() != "NextProcess" {
+ return syn::Error::new(
+ ty.span(),
+ "Chain function must return `NextProcess`",
+ )
+ .to_compile_error()
+ .into();
+ }
+ }
+ _ => {
+ return syn::Error::new(ty.span(), "Chain function must return `NextProcess`")
+ .to_compile_error()
+ .into();
+ }
+ }
+ }
+ ReturnType::Default => {
+ return syn::Error::new(
+ input_fn.sig.span(),
+ "Chain function must specify a return type (must be `NextProcess`)",
+ )
+ .to_compile_error()
+ .into();
+ }
+ }
+
// Extract the previous type and parameter name from function arguments
let (prev_param, previous_type) = match extract_previous_info(&input_fn.sig) {
Ok(info) => info,
diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs
index 70a5c3e..818cda6 100644
--- a/mingling_macros/src/lib.rs
+++ b/mingling_macros/src/lib.rs
@@ -316,14 +316,14 @@ pub fn r_println(input: TokenStream) -> TokenStream {
/// ```rust,ignore
/// // Default program (ThisProgram):
/// #[chain]
-/// fn my_step(prev: InputType) -> ChainProcess<ThisProgram> {
+/// fn my_step(prev: InputType) -> NextProcess {
/// // transform `prev`...
/// OutputType::new(result).to_render()
/// }
///
/// // Explicit program name:
/// #[chain(MyProgram)]
-/// fn my_step(prev: InputType) -> ChainProcess<MyProgram> {
+/// fn my_step(prev: InputType) -> NextProcess {
/// // ...
/// }
/// ```
@@ -336,7 +336,7 @@ pub fn r_println(input: TokenStream) -> TokenStream {
/// pack!(MyOutput = String);
///
/// #[chain]
-/// fn greet(prev: HelloEntry) -> ChainProcess<ThisProgram> {
+/// fn greet(prev: HelloEntry) -> NextProcess {
/// let name = prev.first().cloned().unwrap_or_else(|| "World".to_string());
/// MyOutput::new(name).to_render()
/// }
@@ -350,7 +350,7 @@ pub fn r_println(input: TokenStream) -> TokenStream {
/// pack!(MyOutput = String);
///
/// #[chain]
-/// async fn greet(prev: HelloEntry) -> ChainProcess<ThisProgram> {
+/// async fn greet(prev: HelloEntry) -> NextProcess {
/// let name = prev.first().cloned().unwrap_or_else(|| "World".to_string());
/// some_async_fn(&name).await;
/// MyOutput::new(name).to_render()
@@ -360,7 +360,7 @@ pub fn r_println(input: TokenStream) -> TokenStream {
/// # Requirements
///
/// - The function must have exactly **one** parameter (the previous type in the chain).
-/// - The function must return `ChainProcess<ProgramName>` (or `impl Into<ChainProcess<ProgramName>>`).
+/// - The function must return `NextProcess` (the type alias generated by `gen_program!`, which equals `ChainProcess<ProgramName>`).
/// - With the `async` feature, async functions are supported; without it, async functions are rejected.
#[proc_macro_attribute]
pub fn chain(attr: TokenStream, item: TokenStream) -> TokenStream {
@@ -857,7 +857,7 @@ pub fn program_comp_gen(input: TokenStream) -> TokenStream {
#[cfg(feature = "async")]
let fn_exec_comp = quote! {
#[::mingling::macros::chain(#name)]
- pub async fn __exec_completion(prev: CompletionContext) -> ::mingling::ChainProcess<#name> {
+ pub async fn __exec_completion(prev: CompletionContext) -> NextProcess {
let read_ctx = ::mingling::ShellContext::try_from(prev.inner);
match read_ctx {
Ok(ctx) => {
@@ -872,7 +872,7 @@ pub fn program_comp_gen(input: TokenStream) -> TokenStream {
#[cfg(not(feature = "async"))]
let fn_exec_comp = quote! {
#[::mingling::macros::chain(#name)]
- pub fn __exec_completion(prev: CompletionContext) -> ::mingling::ChainProcess<#name> {
+ pub fn __exec_completion(prev: CompletionContext) -> NextProcess {
let read_ctx = ::mingling::ShellContext::try_from(prev.inner);
match read_ctx {
Ok(ctx) => {