diff options
| -rw-r--r-- | examples/example-async/src/main.rs | 2 | ||||
| -rw-r--r-- | examples/example-basic/src/main.rs | 2 | ||||
| -rw-r--r-- | examples/example-completion/src/main.rs | 2 | ||||
| -rw-r--r-- | examples/example-general-renderer/src/main.rs | 2 | ||||
| -rw-r--r-- | examples/example-picker/src/main.rs | 2 | ||||
| -rw-r--r-- | mingling/src/example_docs.rs | 119 | ||||
| -rw-r--r-- | mingling_macros/src/chain.rs | 38 | ||||
| -rw-r--r-- | mingling_macros/src/lib.rs | 14 |
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) => { |
