//! Proc-macro engine of the Mingling CLI framework. //! //! This crate is the **macro layer** of Mingling. Each `#[attribute]` or `!`-callable //! macro collects metadata into **compile-time global registries** (`OnceLock>`). //! At the end, [`gen_program!`] reads all registries and generates the final program struct //! with all dispatchers, chains, renderers, and completions wired together. //! //! # How Macros Work Together //! //! The Mingling macro pipeline has three phases: //! //! ```text //! ┌──────────────────────────────────────────────────────────────────┐ //! │ Phase 1: Declaration │ //! │ │ //! │ dispatcher! pack! node! #[derive(Groupped)] │ //! │ │ │ │ │ │ //! │ V V V V │ //! │ Declares Wraps a Builds Makes a type │ //! │ a command type in a command recognizable │ //! │ entry a new path Node by the │ //! │ type framework │ //! ├──────────────────────────────────────────────────────────────────┤ //! │ Phase 2: Registration (at compile time, in statics) │ //! │ │ //! │ #[chain] #[renderer] #[help] #[completion] │ //! │ │ │ │ │ │ //! │ V V V V │ //! │ Registers Registers Registers Registers │ //! │ type → chain type → renderer type → help completion logic │ //! ├──────────────────────────────────────────────────────────────────┤ //! │ Phase 3: Code Generation │ //! │ │ //! │ gen_program!() │ //! │ │ │ //! │ V │ //! │ Reads all registries → generates ThisProgram with: │ //! │ • ProgramCollect impl (dispatch/render/chain dispatch tree) │ //! │ • Fallback types (ErrorDispatcherNotFound, etc.) │ //! │ • Completion logic (if `comp` feature enabled) │ //! └──────────────────────────────────────────────────────────────────┘ //! ``` //! //! # Macro Categories //! //! ## Phase 1: Command & Type Declaration //! //! | Macro | What it does | //! |-------|-------------| //! | [`dispatcher!`] | Declares a command entry point and its argument type | //! | [`macro@dispatcher_clap`] | Like `dispatcher!` but powered by `clap::Parser` | //! | [`node!`] | Builds a [`Node`](https://docs.rs/mingling/latest/mingling/struct.Node.html) from a dot-separated path string | //! | [`pack!`] | Creates a newtype wrapper around an inner type for use in Chain/Renderer | //! | [`entry!`] | Creates a packed entry from string literals | //! | [`#[derive(Groupped)]`](derive@Groupped) | Makes a type recognizable by the framework's type registry | //! | [`#[derive(EnumTag)]`](derive@EnumTag) | Adds enum variant metadata (name, description) | //! //! ## Phase 2: Processing & Rendering Registration //! //! | Macro | What it does | //! |-------|-------------| //! | [`#[chain]`](attr.chain.html) | Transforms a function into a chain processing step | //! | [`#[renderer]`](attr.renderer.html) | Transforms a function into a renderer for a type | //! | [`#[help]`](attr.help.html) | Defines help output for a command entry type | //! | [`route!`] | Routes execution depending on a condition | //! | [`empty_result!`] | Returns an empty result for early termination | //! | [`r_print!`] / [`r_println!`] | Print to the `RenderResult` buffer inside a renderer | //! | [`#[completion]`](attr.completion.html) | Registers a shell completion handler | //! //! ## Phase 3: Program Generation //! //! | Macro | What it does | //! |-------|-------------| //! | [`gen_program!`] | **Final step**: reads all registries and generates the full program | //! | [`suggest!`] | Generates suggestion logic for a dispatcher | //! | [`suggest_enum!`] | Generates suggestion logic for an enum dispatcher | //! //! ## Internal (used by the macros above) //! //! | Macro | What it does | //! |-------|-------------| //! | [`register_type!`] | Registers a type in the packed-type registry | //! | [`register_chain!`] | Registers a chain mapping in the chain registry | //! | [`register_renderer!`] | Registers a renderer mapping in the renderer registry | //! | [`register_dispatcher!`] | Registers a dispatcher for the `dispatch_tree` feature | //! | [`register_help!`] | Registers a help request handler | //! | `program_fallback_gen!` | Generates fallback error types | //! | `program_final_gen!` | Generates the `ProgramCollect` impl and `ThisProgram` struct | //! | `program_comp_gen!` | Generates completion logic | //! | [`#[program_setup]`](attr.program_setup.html) | Declares a custom program setup step | //! //! # Feature Gates //! //! Some macros are only available when certain Cargo features are enabled: //! //! | Feature | Macros enabled | //! |---------|---------------| //! | `clap` | [`macro@dispatcher_clap`] | //! | `comp` | [`#[completion]`](attr.completion.html), [`suggest!`], [`suggest_enum!`] | //! | `extra_macros` | [`entry!`], [`empty_result!`], [`route!`], [`#[program_setup]`](attr.program_setup.html) | //! | `dispatch_tree` | `register_dispatcher!` (enables trie-based command dispatch) | //! | `general_renderer` | Enables JSON/YAML/TOML/RON serialization renderers | //! | `async` | Enables async `#[chain]` functions | //! | `repl` | Enables REPL execution loop | //! //! # The Compile-Time Registry System //! //! Macros in this crate do **not** generate all code immediately. Instead, they //! store entries into `OnceLock>>` statics. These string //! entries contain the **token-stream representation** of match arms, type mappings, //! and struct definitions. //! //! When [`gen_program!`] is called, it reads all registries, concatenates their //! entries, and emits the complete program: //! //! ```rust,ignore //! // Example of what gen_program! generates (simplified): //! impl ProgramCollect for ThisProgram { //! fn build_dispatcher_not_found(args: Vec) -> AnyOutput { //! AnyOutput::new(ErrorDispatcherNotFound::new(args)) //! } //! fn has_chain(any: &AnyOutput) -> bool { //! match any.member_id() { //! MyType => true, // ← collected from #[chain] macros //! _ => false, //! } //! } //! fn has_renderer(any: &AnyOutput) -> bool { //! match any.member_id() { //! MyType => true, // ← collected from #[renderer] macros //! _ => false, //! } //! } //! } //! ``` use proc_macro::TokenStream; use proc_macro2::Ident; use quote::quote; use std::collections::BTreeSet; use std::sync::Mutex; use std::sync::OnceLock; use syn::parse_macro_input; mod chain; #[cfg(feature = "comp")] mod completion; #[cfg(feature = "dispatch_tree")] mod dispatch_tree_gen; mod dispatcher; #[cfg(feature = "clap")] mod dispatcher_clap; #[cfg(feature = "extra_macros")] mod entry; mod enum_tag; mod groupped; mod help; mod node; mod pack; #[cfg(feature = "extra_macros")] mod program_setup; mod render; mod renderer; mod res_injection; #[cfg(feature = "comp")] mod suggest; pub(crate) const DEFAULT_PROGRAM_NAME: &str = "ThisProgram"; #[allow(dead_code)] pub(crate) fn default_program_ident() -> Ident { Ident::new(DEFAULT_PROGRAM_NAME, proc_macro2::Span::call_site()) } pub(crate) fn default_program_path() -> proc_macro2::TokenStream { quote::quote! { crate::ThisProgram } } // Helper to get or init a OnceLock>> pub(crate) fn get_global_set(lock: &OnceLock>>) -> &Mutex> { lock.get_or_init(|| Mutex::new(BTreeSet::new())) } pub(crate) type Registry = OnceLock>>; // Global variables #[cfg(feature = "general_renderer")] pub(crate) static GENERAL_RENDERERS: Registry = OnceLock::new(); #[cfg(feature = "comp")] pub(crate) static COMPLETIONS: Registry = OnceLock::new(); #[cfg(feature = "dispatch_tree")] pub(crate) static COMPILE_TIME_DISPATCHERS: Registry = OnceLock::new(); pub(crate) static PACKED_TYPES: Registry = OnceLock::new(); pub(crate) static CHAINS: Registry = OnceLock::new(); pub(crate) static RENDERERS: Registry = OnceLock::new(); pub(crate) static CHAINS_EXIST: Registry = OnceLock::new(); pub(crate) static RENDERERS_EXIST: Registry = OnceLock::new(); pub(crate) static HELP_REQUESTS: Registry = OnceLock::new(); /// Checks that a `TypePath` is a simple single-segment identifier (no `::` in the path). /// /// This is used by `#[renderer]`, `#[help]`, `#[chain]`, and `#[completion]` attribute macros /// to ensure that the type in the function signature is a bare identifier like `Empty`, /// not a qualified path like `other::Empty`. /// /// Returns `None` if the type is valid, or a `compile_error!` token stream if it contains `::`. pub(crate) fn check_single_segment_type( type_path: &syn::TypePath, attr_name: &str, ) -> Option { if type_path.path.segments.len() > 1 { let type_str = quote! { #type_path }; Some(quote! { compile_error!(concat!( "The type `", #type_str, "` in ", #attr_name, " function must be a simple single-segment type, ", "e.g. `Empty` instead of `other::Empty`. ", "Qualified paths with `::` are not allowed here." )); }) } else { None } } /// Creates a `Node` from a dot-separated path string. /// /// Each segment is converted to kebab-case (unless it starts with `_`). /// Segments are joined via `.join()` calls, building a path hierarchy for /// command matching. /// /// # Syntax /// /// ```rust,ignore /// node!("subcommand") /// node!("sub.subsub") /// node!("") // empty → Node::default() /// ``` /// /// # Example /// /// ```rust,ignore /// use mingling::macros::node; /// /// // Creates a single-level node for "hello" /// let n = node!("hello"); /// /// // Creates a two-level node for "remote control" /// let n = node!("remote.control"); /// ``` /// /// # Internals /// /// The generated code is equivalent to: /// ```rust,ignore /// Node::default().join("hello") /// Node::default().join("remote").join("control") /// ``` /// /// This macro is typically used internally by `dispatcher!` and should rarely /// need to be called directly. #[proc_macro] pub fn node(input: TokenStream) -> TokenStream { node::node(input) } /// Creates a type-safe wrapper struct around an inner type, with automatic /// trait implementations for use in the Mingling chain/render pipeline. /// /// The generated struct implements: `From`/`Into`, `AsRef`/`AsMut`, `Deref`/`DerefMut`, /// `Default` (conditional on inner type), and conversion into `AnyOutput` / /// `ChainProcess` for routing. /// /// # Syntax /// /// ```rust,ignore /// // Default program name (uses `ThisProgram`): /// pack!(TypeName = InnerType); /// /// // Explicit program name: /// pack!(MyProgram, TypeName = InnerType); /// ``` /// /// # Example /// /// ```rust,ignore /// use mingling::macros::pack; /// /// // Creates `Hello` wrapping `String`, registered under `ThisProgram`: /// pack!(Hello = String); /// /// // Creates `Greeting` wrapping `String`, registered under `MyApp`: /// pack!(MyApp, Greeting = String); /// ``` /// /// After expansion, `Hello` has: /// - `Hello::new(String)` — constructor /// - `Hello::to_chain()` — routes to the next chain processor /// - `Hello::to_render()` — routes to a renderer /// - `From for Hello`, `From for String` /// - `Deref`, `DerefMut` /// - `AsRef`, `AsMut` /// - `Default` if `String: Default` /// - `Into>`, `Into>` /// - Implements `Groupped` with `member_id()` returning the enum variant /// /// The struct is also registered via `register_type!` so that `gen_program!` /// can include it in the program enum. /// /// When the `general_renderer` feature is enabled, the struct also gets /// `#[derive(serde::Serialize)]`. #[proc_macro] pub fn pack(input: TokenStream) -> TokenStream { pack::pack(input) } /// Early-returns an error from a `Result`, converting the `Ok` branch to a /// `ChainProcess`. /// /// This macro is equivalent to: /// ```rust,ignore /// match expr { /// Ok(r) => r, /// Err(e) => return e, /// } /// ``` /// /// It is useful inside chain functions where you have a `Result, ChainProcess>` /// and want to propagate the error case as an early return. /// /// # Example /// /// ```rust,ignore /// use mingling::macros::{chain, route}; /// /// #[chain] /// fn process(prev: SomeEntry) -> ChainProcess { /// let value = route!(try_something().ok_or(ErrorEntry::new("failed".into()).to_render())); /// // value is the Ok(ChainProcess) from try_something() /// value /// } /// ``` #[cfg(feature = "extra_macros")] #[proc_macro] pub fn route(input: TokenStream) -> TokenStream { let expr = parse_macro_input!(input as syn::Expr); let expanded = quote! { match #expr { Ok(r) => r, Err(e) => return e, } }; TokenStream::from(expanded) } /// Creates an empty result value wrapped in `ChainProcess` for early return /// from a chain function. /// /// This macro is a shorthand for constructing a [`ResultEmpty`] and converting /// it into a [`ChainProcess`], which signals to the pipeline that there is /// no meaningful output to continue processing and the chain should terminate. /// /// This is useful in `#[chain]` functions where a condition determines that /// no further processing is needed (e.g., validation failures or early exits). /// /// # Syntax /// /// ```rust,ignore /// empty_result!() /// ``` /// /// # Example /// /// ```rust,ignore /// use mingling::macros::{chain, empty_result}; /// /// #[chain] /// fn maybe_skip(prev: SomeEntry) -> Next { /// if should_skip() { /// return empty_result!(); // Terminate chain gracefully /// } /// // ... continue processing /// NextEntry::new(result).to_chain() /// } /// ``` /// /// # Generated code /// /// The macro expands to: /// ```rust,ignore /// crate::ResultEmpty::new(()).to_chain() /// ``` /// /// This works because [`ResultEmpty`] is automatically generated by [`gen_program!`] /// and implements the necessary trait conversions into [`ChainProcess`]. /// /// # See also /// /// - [`ResultEmpty`] — The type that represents an empty result. /// - [`route!`] — For early-return from `Result` expressions. /// /// [`ResultEmpty`]: https://docs.rs/mingling/latest/mingling/type.ResultEmpty.html /// [`ChainProcess`]: https://docs.rs/mingling/latest/mingling/enum.ChainProcess.html #[cfg(feature = "extra_macros")] #[proc_macro] pub fn empty_result(_input: TokenStream) -> TokenStream { let expanded = quote! { crate::ResultEmpty::new(()).to_chain() }; TokenStream::from(expanded) } /// Creates a `Dispatcher` implementation for a subcommand. /// /// This is the primary way to define command-line subcommands in Mingling. /// It generates a dispatcher struct that, when matched against user input, /// converts the arguments into a [`ChainProcess`] via the specified entry type. /// /// The generated dispatcher implements [`Dispatcher`], which the /// framework uses to route user input to the correct chain processor. /// /// # Syntax /// /// ## Full syntax (recommended) /// /// ```rust,ignore /// // Default program name (uses `ThisProgram`): /// dispatcher!("command.path", CommandStruct => EntryStruct); /// /// // Explicit program name: /// dispatcher!(MyProgram, "command.path", CommandStruct => EntryStruct); /// ``` /// /// ## Abbreviated syntax (requires `extra_macros` feature) /// /// When the `extra_macros` feature is enabled, the `CommandStruct => EntryStruct` /// portion can be omitted. Struct names are auto-derived from the command path /// using `PascalCase` conversion: /// /// ```rust,ignore /// // "remote.add" → CMDRemoteAdd ⇒ EntryRemoteAdd /// dispatcher!("remote.add"); /// ``` /// /// ⚠️ **Note**: The abbreviated form generates structs with internal names. /// If you need to reference the dispatcher (e.g., `program.with_dispatcher(Dispatcher)`), /// use the full syntax instead. /// /// # Examples /// /// ```rust,ignore /// use mingling::macros::dispatcher; /// /// // Single-level command: "hello" /// dispatcher!("hello", HelloCommand => HelloEntry); /// /// // Nested command: "remote.control" creates a two-level path /// dispatcher!("remote.control", RemoteControlCommand => RemoteControlEntry); /// /// // With explicit program: /// dispatcher!(MyApp, "status", StatusCommand => StatusEntry); /// /// // Abbreviated form (extra_macros required): /// // dispatcher!("remote.add"); // → CMDRemoteAdd, EntryRemoteAdd /// ``` /// /// # How it works /// /// The macro generates: /// /// 1. **Entry struct** — A `pack!`-style wrapper around `Vec` (the raw args). /// Registered in the program enum via `register_type!`. /// 2. **Dispatcher struct** — A zero-sized struct implementing [`Dispatcher`]: /// - `node()` returns the [`Node`] hierarchy for the command path. /// - `begin(args)` wraps `args` into the entry type and routes to chain. /// - `clone_dispatcher()` returns a boxed clone. /// 3. **Registration** — If the `dispatch_tree` feature is enabled, also calls /// `register_dispatcher!` for compile-time trie construction. /// /// With the `comp` feature, the entry type also implements `CompletionEntry` /// for providing shell completion suggestions. /// /// # See also /// /// - [`macro@dispatcher_clap`] — For clap-powered argument parsing. /// - [`node!`] — For building custom [`Node`] paths. /// - [`#[chain]`](attr.chain.html) — For processing the dispatched entry. /// /// [`ChainProcess`]: https://docs.rs/mingling/latest/mingling/enum.ChainProcess.html /// [`Dispatcher`]: https://docs.rs/mingling/latest/mingling/trait.Dispatcher.html /// [`Node`]: https://docs.rs/mingling/latest/mingling/struct.Node.html #[proc_macro] pub fn dispatcher(input: TokenStream) -> TokenStream { dispatcher::dispatcher(input) } /// Prints formatted text to the current [`RenderResult`] buffer within a /// `#[renderer]` function. /// /// Unlike `print!`, this macro writes to the in-memory `RenderResult` buffer /// rather than directly to stdout. The buffered output is flushed automatically /// when the renderer returns, allowing the framework to control output timing /// and capture (e.g., for testing or general rendering to JSON/YAML). /// /// This macro requires a mutable reference to a [`RenderResult`] named /// `__renderer_inner_result` to be in scope, which is automatically provided /// inside `#[renderer]` and `#[help]` functions. /// /// # Syntax /// /// Same as [`format!`] / [`print!`]: /// /// ```rust,ignore /// r_print!("Hello, {}!", name); /// r_print!("Value: {value}"); /// ``` /// /// # Example /// /// ```rust,ignore /// use mingling::macros::{renderer, r_print}; /// /// #[renderer] /// fn show_greeting(prev: Greeting) { /// r_print!("Hello, {}!", *prev); /// } /// ``` /// /// # Difference from `r_println!` /// /// `r_print!` does **not** append a newline. Use [`r_println!`] for newline-terminated output. /// /// # Panics /// /// Does not panic under normal circumstances. The underlying `RenderResult` /// buffer operations are infallible. /// /// [`RenderResult`]: https://docs.rs/mingling/latest/mingling/struct.RenderResult.html #[proc_macro] pub fn r_print(input: TokenStream) -> TokenStream { render::r_print(input) } /// Prints formatted text followed by a newline to the current [`RenderResult`] buffer /// within a `#[renderer]` or `#[help]` function. /// /// Unlike `println!`, this macro writes to the in-memory `RenderResult` buffer /// rather than directly to stdout. The buffered output is flushed automatically /// when the renderer returns. /// /// This macro requires a mutable reference to a [`RenderResult`] named /// `__renderer_inner_result` to be in scope, which is automatically provided /// inside `#[renderer]` and `#[help]` functions. /// /// # Syntax /// /// Same as [`println!`]: /// /// ```rust,ignore /// r_println!("Hello, {}!", name); /// r_println!("Value: {value}"); /// r_println!(); // just a newline /// ``` /// /// # Example /// /// ```rust,ignore /// use mingling::macros::{renderer, r_println}; /// /// #[renderer] /// fn show_greeting(prev: Greeting) { /// r_println!("Hello, {}!", *prev); /// } /// ``` /// /// # See also /// /// - [`r_print!`] for output without a trailing newline. /// /// [`RenderResult`]: https://docs.rs/mingling/latest/mingling/struct.RenderResult.html #[proc_macro] pub fn r_println(input: TokenStream) -> TokenStream { render::r_println(input) } /// Declares a chain processing step that transforms one type into another /// within a Mingling pipeline. /// /// The `#[chain]` attribute converts an ordinary function (or async function /// with the `async` feature) into a chain step by: /// 1. Generating a hidden struct implementing the `Chain` trait. /// 2. Registering the chain mapping in the global chain registry. /// 3. Keeping the original function for direct calls. /// /// # Syntax /// /// ```rust,ignore /// // Default program (ThisProgram): /// #[chain] /// fn my_step(prev: InputType) -> Next { /// // transform `prev`... /// OutputType::new(result) /// } /// /// // Explicit program name: /// #[chain(MyProgram)] /// fn my_step(prev: InputType) -> Next { /// // ... /// } /// ``` /// /// # Resource Injection /// /// The `#[chain]` macro supports automatic injection of global resources /// via the 2nd to Nth parameters. You can read resources immutably with /// `&T` or mutate them with `&mut T`. /// /// ## Immutable Resource (`&T`) /// /// When you write `&SomeResource` as a parameter, the macro automatically /// resolves it from the global resource store: /// /// ```rust,ignore /// #[chain] /// fn process(prev: HelloEntry, age: &Age, name: &Name) -> Next { /// // `age` and `name` are automatically injected /// println!("Age: {}, Name: {}", age, name); /// NextStep::default() /// } /// ``` /// /// This expands to: /// /// ```rust,ignore /// let __age_binding = ::mingling::this::().res_or_default::(); /// let age: &Age = __age_binding.as_ref(); /// let __name_binding = ::mingling::this::().res_or_default::(); /// let name: &Name = __name_binding.as_ref(); /// ``` /// /// ## Mutable Resource (`&mut T`) /// /// When you write `&mut SomeResource` as a parameter, the macro wraps the /// function body in nested `__modify_res_and_return_any` calls: /// /// ```rust,ignore /// #[chain] /// fn process(prev: HelloEntry, count: &mut InvocationCount, name: &Name) -> Next { /// count.0 += 1; /// println!("Invocation #{} for {}", count.0, name); /// NextStep::default() /// } /// ``` /// /// This expands to: /// /// ```rust,ignore /// let __name_binding = ::mingling::this::().res_or_default::(); /// let name: &Name = __name_binding.as_ref(); /// /// ::mingling::this::().__modify_res_and_return_any(|count: &mut InvocationCount| { /// count.0 += 1; /// println!("Invocation #{} for {}", count.0, name); /// NextStep::default() /// }).into() /// ``` /// /// Multiple `&mut` parameters are supported with proper nesting. /// /// ## Restrictions /// /// - The first parameter (previous type) must be taken **by move**, not by reference. /// - Resource injection parameters **must** be references (`&T` or `&mut T`), /// owned values are not allowed. /// - When the `async` feature is enabled, `&mut T` cannot be used in async /// chain functions (only `&T` is supported for async). /// /// # Sync Example /// /// ```rust,ignore /// use mingling::macros::{chain, pack, gen_program}; /// /// pack!(MyOutput = String); /// /// #[chain] /// fn greet(prev: HelloEntry) -> Next { /// let name = prev.first().cloned().unwrap_or_else(|| "World".to_string()); /// MyOutput::new(name) /// } /// ``` /// /// # Sync Example with Resource Injection /// /// ```rust,ignore /// use mingling::macros::{chain, pack, gen_program, r_println}; /// /// #[derive(Default, Clone)] /// struct UserName(String); /// /// pack!(Greeting = String); /// pack!(DisplayCount = ()); /// /// #[chain] /// fn greet(prev: HelloEntry, user_name: &UserName, count: &mut u64) -> Next { /// r_println!("User: {:?}", user_name); /// *count += 1; /// Greeting::new(format!("Hello, {}!", user_name.0)) /// } /// ``` /// /// # Async Example (with `async` feature) /// /// ```rust,ignore /// use mingling::macros::{chain, pack, gen_program}; /// /// pack!(MyOutput = String); /// /// #[chain] /// async fn greet(prev: HelloEntry) -> Next { /// let name = prev.first().cloned().unwrap_or_else(|| "World".to_string()); /// some_async_fn(&name).await; /// MyOutput::new(name) /// } /// ``` /// /// # Async Example with Immutable Resource Injection /// /// ```rust,ignore /// use mingling::macros::{chain, pack, gen_program}; /// /// pack!(MyOutput = String); /// /// #[chain] /// async fn greet(prev: HelloEntry, prefix: &Prefix) -> Next { /// let name = prev.first().cloned().unwrap_or_else(|| "World".to_string()); /// some_async_fn(&name).await; /// MyOutput::new(format!("{}{}", prefix.0, name)) /// } /// ``` /// /// # Requirements /// /// - The function must have at least **one** parameter (the previous type in the chain). /// - The first parameter must be taken **by move**. /// - The function must return `Next` (the type alias generated by `gen_program!`, which equals `ChainProcess`). /// - 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 { chain::chain_attr(attr, item) } /// Declares a renderer step that renders the output of a chain to the terminal. /// /// The `#[renderer]` attribute converts a function into a renderer by: /// 1. Generating a hidden struct implementing the `Renderer` trait. /// 2. Registering the renderer mapping in the global renderer registry. /// 3. Keeping the original function for direct calls. When called directly, /// a new `RenderResult` is created and the renderer function writes its /// output directly to the current terminal output buffer. /// /// Inside a `#[renderer]` function, you can use `r_print!` and `r_println!` /// to write output to the `RenderResult` buffer. /// /// # Syntax /// /// ```rust,ignore /// // Default program (ThisProgram): /// #[renderer] /// fn render_my_type(prev: MyType) { /// r_println!("Output: {:?}", *prev); /// } /// ``` /// /// # Example /// /// ```rust,ignore /// use mingling::macros::{renderer, r_println, pack, gen_program}; /// /// pack!(Greeting = String); /// /// #[renderer] /// fn render_greeting(prev: Greeting) { /// r_println!("Hello, {}!", *prev); /// } /// ``` /// /// # Requirements /// /// - The function must have exactly **one** parameter (the type to render). /// - The function must return `()` (unit). /// - The function **cannot** be async. /// /// # Fallback Renderers /// /// The macros `gen_program!` automatically generates two fallback types that /// you can provide renderers for: /// - `ErrorRendererNotFound` — triggered when no matching renderer is found /// - `ErrorDispatcherNotFound` — triggered when no matching dispatcher is found /// /// ```rust,ignore /// #[renderer] /// fn fallback_dispatcher_not_found(prev: ErrorDispatcherNotFound) { /// r_println!("Unknown command: {}", prev.join(", ")); /// } /// /// #[renderer] /// fn fallback_renderer_not_found(prev: ErrorRendererNotFound) { /// r_println!("No renderer for `{}`", *prev); /// } /// ``` #[proc_macro_attribute] pub fn renderer(attr: TokenStream, item: TokenStream) -> TokenStream { renderer::renderer_attr(attr, item) } /// Declares a completion suggestion provider for a command entry type. /// /// **This macro is only available with the `comp` feature.** /// /// The `#[completion]` attribute converts a function into a completion provider by: /// 1. Generating a hidden struct implementing the `Completion` trait. /// 2. Registering the completion mapping for the specified entry type. /// 3. Keeping the original function for direct calls. /// /// # Syntax /// /// ```rust,ignore /// #[completion(EntryType)] /// fn complete_my_entry(ctx: &ShellContext) -> Suggest { /// // Return suggestions based on current input state... /// } /// ``` /// /// # Example /// /// ```rust,ignore /// use mingling::macros::{completion, suggest, suggest_enum}; /// use mingling::{ShellContext, Suggest}; /// /// #[completion(MyEntry)] /// fn complete_my_command(ctx: &ShellContext) -> Suggest { /// if ctx.filling_argument_first("--name") { /// return suggest!(); /// } /// if ctx.filling_argument_first("--type") { /// return suggest_enum!(MyEnum); /// } /// if ctx.typing_argument() { /// return suggest! { /// "--name": "Provide a name", /// "--type": "Select a type" /// }.strip_typed_argument(ctx); /// } /// suggest!() /// } /// ``` /// /// # Requirements /// /// - The `comp` feature must be enabled. /// - The function must have exactly one parameter of type `&ShellContext`. /// - The function must return `Suggest`. /// - The function cannot be async. #[cfg(feature = "comp")] #[proc_macro_attribute] pub fn completion(attr: TokenStream, item: TokenStream) -> TokenStream { completion::completion_attr(attr, item) } /// Declares a program setup function that initializes the program instance /// before execution. /// /// The `#[program_setup]` attribute converts a function into a setup step by: /// 1. Generating a struct implementing the `ProgramSetup` trait. /// 2. The setup function receives a mutable reference to `&mut Program`. /// /// # Syntax /// /// ```rust,ignore /// // Default program (ThisProgram): /// #[program_setup] /// fn setup_my_program(program: &mut Program) { /// program.stdout_setting.render_output = false; /// } /// /// // Explicit program name: /// #[program_setup(MyProgram)] /// fn setup_my_program(program: &mut Program) { /// // ... /// } /// ``` /// /// # Example /// /// ```rust,ignore /// use mingling::macros::program_setup; /// use mingling::Program; /// /// #[program_setup] /// fn configure(program: &mut Program) { /// program.with_setup(GeneralRendererSetup); /// program.user_context.some_flag = true; /// } /// ``` /// /// # Requirements /// /// - The function must have exactly one parameter of type `&mut Program`. /// - The function must return `()`. /// - The function cannot be async. #[cfg(feature = "extra_macros")] #[proc_macro_attribute] pub fn program_setup(attr: TokenStream, item: TokenStream) -> TokenStream { program_setup::setup_attr(attr, item) } /// Declares a `Dispatcher` that uses `clap::Parser` for argument parsing. /// /// **This macro is only available with the `clap` feature.** /// /// The `#[dispatcher_clap]` attribute: /// 1. Keeps the original struct definition (typically with `#[derive(clap::Parser)]`). /// 2. Generates a dispatcher struct that parses arguments using clap and routes /// to the chain pipeline. /// 3. Optionally generates a `#[help]` block for displaying clap-generated help. /// /// # Syntax /// /// ```rust,ignore /// // Default program (ThisProgram): /// #[derive(clap::Parser)] /// #[dispatcher_clap("command.name", DispatcherStruct)] /// struct MyEntry { /* ... */ } /// /// // With explicit error type and help: /// #[derive(clap::Parser)] /// #[dispatcher_clap("cmd", Disp, error = ParseError, help = true)] /// struct CmdEntry { /* ... */ } /// /// // With explicit program name: /// #[derive(clap::Parser)] /// #[dispatcher_clap(MyProgram, "cmd", Disp)] /// struct CmdEntry { /* ... */ } /// ``` /// /// # Example /// /// ```rust,ignore /// use clap::Parser; /// use mingling::macros::dispatcher_clap; /// /// #[derive(Parser)] /// #[dispatcher_clap("greet", GreetDispatcher, error = GreetParseError, help = true)] /// struct GreetArgs { /// #[arg(short, long)] /// name: String, /// } /// ``` /// /// # Options /// /// - `error = ErrorType` — Specifies an error wrapper type for clap parse failures. /// The error message is captured and routed to the renderer. /// - `help = true` — Generates a `#[help]` block that displays clap's help output /// when `--help` is passed. #[cfg(feature = "clap")] #[proc_macro_attribute] pub fn dispatcher_clap(attr: TokenStream, item: TokenStream) -> TokenStream { dispatcher_clap::dispatcher_clap_attr(attr, item) } /// Creates a packed entry value from a list of string literals. /// /// This is a convenience macro for constructing entry wrapper types (created /// via [`pack!`] or [`dispatcher!`]) with test data, typically used in unit tests /// or quick prototypes. /// /// # Syntax /// /// Two forms: /// /// ```rust,ignore /// // Named form — wraps into a specific pack type: /// entry!(MyEntry, ["a", "b", "c"]) /// // Expands to: MyEntry::new(vec!["a".to_string(), "b".to_string(), "c".to_string()]) /// /// // Bracket form — returns Vec.into() for type inference: /// entry!["a", "b", "c"] /// // Expands to: vec!["a".to_string(), ...].into() /// ``` /// /// # Example /// /// ```rust,ignore /// use mingling::macros::entry; /// /// // Named form (with a specific pack type): /// let args = entry!(MyEntry, ["--name", "Alice", "--count", "5"]); /// /// // Bracket form (type inference): /// let args: Vec = entry!["hello", "world"]; /// ``` /// /// # See also /// /// - [`pack!`] — For creating the wrapper types used with `entry!`. /// - [`dispatcher!`] — Which implicitly creates entry types via `pack!`. #[cfg(feature = "extra_macros")] #[proc_macro] pub fn entry(input: TokenStream) -> TokenStream { entry::entry(input) } /// Registers a help request mapping between an entry type and a help struct. /// /// This macro is used internally by the `#[help]`(macro.help.html) attribute /// and is also available for manual registration if needed. /// /// # Syntax /// /// ```rust,ignore /// register_help!(EntryType, HelpStruct); /// ``` /// /// This adds an entry to the global `HELP_REQUESTS` registry, mapping the /// enum variant for `EntryType` to the help rendering logic in `HelpStruct`. #[proc_macro] pub fn register_help(input: TokenStream) -> TokenStream { help::register_help(input) } /// Registers a dispatcher at compile time for the `dispatch_tree` feature. /// /// This macro is called internally by [`dispatcher!`] when the `dispatch_tree` /// feature is enabled. Each call stores the node name into the global /// `COMPILE_TIME_DISPATCHERS` registry and generates a static variable for the /// dispatcher instance. This data is later consumed by [`gen_program!`] to /// generate a character-level **Trie** for efficient command dispatch. /// /// The trie dispatch works by grouping commands by their character prefix, /// enabling O(n) lookup (where n is input length) instead of linear iteration /// over all registered commands. /// /// # Syntax /// /// ```rust,ignore /// register_dispatcher!("node.name", DispatcherType, EntryName); /// ``` /// /// This macro should not be called directly by user code. /// /// # See also /// /// - [`dispatcher!`] — The primary way to declare dispatchers (calls this internally). /// - `dispatch_tree_gen` module — The trie generation logic. #[proc_macro] pub fn register_dispatcher(input: TokenStream) -> TokenStream { dispatcher::register_dispatcher(input) } /// Declares a help rendering function for an entry type. /// /// The `#[help]` attribute converts a function into a help provider. Help /// functions are invoked when the user passes `--help` / `-h` for a command /// and `BasicProgramSetup` is registered on the program. /// /// When `program.user_context.help` is `true`, the command will **skip** the /// normal `#[chain]` and `#[renderer`] pipeline and instead route directly /// to the registered `#[help]` function for that entry type. /// /// The macro works by: /// 1. Generating a hidden struct implementing the `HelpRequest` trait. /// 2. Registering the help mapping in the global `HELP_REQUESTS` registry. /// 3. Keeping the original function for direct calls (with a dummy `RenderResult`). /// /// Inside a `#[help]` function, you can use [`r_print!`] and [`r_println!`] /// to write help text to the `RenderResult` buffer. /// /// # Syntax /// /// ```rust,ignore /// #[help] /// fn help_my_entry(prev: MyEntry) { /// r_println!("Usage: myapp myentry [options]"); /// r_println!(" Does something useful."); /// } /// ``` /// /// # Example /// /// ```rust,ignore /// use mingling::macros::{help, r_println, pack, gen_program}; /// use mingling::{prelude::*, setup::BasicProgramSetup}; /// /// pack!(MyEntry = Vec); /// /// #[help] /// fn help_my_entry(prev: MyEntry) { /// r_println!("Usage: myapp greet [name]"); /// r_println!("Greets the user."); /// } /// /// fn main() { /// let mut program = ThisProgram::new(); /// program.with_setup(BasicProgramSetup); // Required for --help /// program.with_dispatcher(CMDMyEntry); /// program.exec_and_exit(); /// } /// ``` /// /// # Requirements /// /// - The function must have exactly one parameter (the entry type to provide help for). /// - The parameter type must be a single-segment type path (e.g., `MyEntry`, not `other::MyEntry`). /// - The function must return `()`. /// - The function cannot be async. /// /// # See also /// /// - [`BasicProgramSetup`] — The setup that enables `--help` and `-h` flag processing. /// - [`r_print!`] / [`r_println!`] — For outputting help text. /// - [`#[chain]`](attr.chain.html) — For processing the dispatched entry after help. /// /// [`BasicProgramSetup`]: https://docs.rs/mingling/latest/mingling/setup/struct.BasicProgramSetup.html #[proc_macro_attribute] pub fn help(_attr: TokenStream, item: TokenStream) -> TokenStream { help::help_attr(item) } /// Derive macro for automatically implementing the `Groupped` trait on a struct. /// /// The `#[derive(Groupped)]` macro: /// 1. Implements `Groupped` where the group is specified via `#[group(GroupName)]`. /// 2. Registers the type via `register_type!` so it's included in the program enum. /// 3. Generates `Into>` and `Into>` conversions. /// 4. Adds `to_chain()` and `to_render()` methods to the struct. /// /// # Syntax /// /// ```rust,ignore /// #[derive(Groupped)] /// #[group(MyProgram)] // optional; defaults to `ThisProgram` /// struct MyStruct { /// field: String, /// } /// ``` /// /// # Example /// /// ```rust,ignore /// use mingling::{Groupped, macros::{chain, gen_program, renderer, r_println}}; /// /// #[derive(Groupped)] /// #[group(ThisProgram)] /// struct Greeting { /// name: String, /// } /// ``` /// /// This is equivalent to using `pack!` but works with custom structs that /// have named fields. For simple wrappers, prefer `pack!`. #[proc_macro_derive(Groupped, attributes(group))] pub fn derive_groupped(input: TokenStream) -> TokenStream { groupped::derive_groupped(input) } /// Derive macro for automatically implementing the `EnumTag` trait on an enum /// with unit-only variants. /// /// The `#[derive(EnumTag)]` macro generates: /// - `enum_info(&self) -> (&'static str, &'static str)` — returns (name, description) /// for the current variant. /// - `build_enum(name: String) -> Option` — constructs a variant from its /// display name (or `#[enum_rename]` value). /// - `enums() -> &'static [(&'static str, &'static str)]` — returns all (name, description) /// pairs. /// /// # Attributes /// /// - `#[enum_desc("description text")]` — Provides a description for the variant. /// - `#[enum_rename("display name")]` — Changes the display/build name of the variant. /// /// # Syntax /// /// ```rust,ignore /// #[derive(EnumTag)] /// enum Fruit { /// #[enum_desc("A sweet red fruit")] /// #[enum_rename("apple")] /// Apple, /// /// #[enum_desc("A yellow tropical fruit")] /// #[enum_rename("banana")] /// Banana, /// } /// ``` /// /// # Requirements /// /// - Can only be derived for **enums** (not structs or unions). /// - All variants must be **unit variants** (no fields). /// - Each variant is optional; variants without attributes get their Rust name as display name /// and an empty description. #[proc_macro_derive(EnumTag, attributes(enum_desc, enum_rename))] pub fn derive_enum_tag(input: TokenStream) -> TokenStream { enum_tag::derive_enum_tag(input) } /// Derive macro for implementing both `Groupped` and `serde::Serialize` on a struct. /// /// **This macro is only available with the `general_renderer` feature.** /// /// This is identical to `#[derive(Groupped)]` but also adds `#[derive(serde::Serialize)]` /// to the struct, which is required for the general renderer to serialize output /// to formats like JSON, YAML, TOML, or RON. /// /// # Syntax /// /// ```rust,ignore /// #[derive(GrouppedSerialize)] /// #[group(MyProgram)] /// struct Info { /// name: String, /// age: i32, /// } /// ``` /// /// # Example /// /// ```rust,ignore /// use mingling::GrouppedSerialize; /// use serde::Serialize; /// /// #[derive(GrouppedSerialize)] /// struct Info { /// name: String, /// age: i32, /// } /// ``` #[cfg(feature = "general_renderer")] #[proc_macro_derive(GrouppedSerialize, attributes(group))] pub fn derive_groupped_serialize(input: TokenStream) -> TokenStream { groupped::derive_groupped_serialize(input) } /// Generates the program enum and all collected types, chains, and renderers. /// /// This macro **must** be called at the end of your program module to collect /// all registered types, chains, renderers, and help requests into a single /// program enum that implements `ProgramCollect`. /// /// # Syntax /// /// ```rust,ignore /// // Default program name (uses `ThisProgram`): /// gen_program!(); /// /// // Explicit program name: /// gen_program!(MyProgram); /// ``` /// /// # What it generates /// /// The macro expands to: /// 1. **`pub type Next = ChainProcess`** — A convenience type alias /// for use in chain function return types. /// 2. **`program_comp_gen!(...)`** (with `comp` feature) — Generates completion infrastructure. /// 3. **`program_fallback_gen!(...)`** — Generates `ErrorRendererNotFound` and `ErrorDispatcherNotFound` types. /// 4. **`program_final_gen!(...)`** — Generates the program enum with: /// - An enum with all packed types as variants /// - `Display` implementation for the enum /// - `ProgramCollect` implementation dispatching to all registered renderers and chains /// - A `new()` constructor returning `Program` /// /// # Example /// /// ```rust,ignore /// use mingling::macros::{dispatcher, chain, renderer, gen_program}; /// /// dispatcher!("hello", HelloCommand => HelloEntry); /// /// #[chain] /// fn process(prev: HelloEntry) -> Next { /// // ... /// } /// /// #[renderer] /// fn render(prev: /* ... */) { /// r_println!("Done!"); /// } /// /// // Collect everything: /// gen_program!(); /// ``` #[proc_macro] pub fn gen_program(input: TokenStream) -> TokenStream { let name = read_name(&input); #[cfg(feature = "comp")] let comp_gen = quote! { ::mingling::macros::program_comp_gen!(#name); }; #[cfg(not(feature = "comp"))] let comp_gen = quote! {}; TokenStream::from(quote! { // Shit, this feature is unstable // TODO :: This logic will be implemented when Rust's Impl In Type Alias feature becomes stable // pub type Next = impl Into<::mingling::ChainProcess<#name>>; pub type Next = ::mingling::ChainProcess<#name>; #comp_gen ::mingling::macros::program_fallback_gen!(#name); ::mingling::macros::program_final_gen!(#name); }) } /// Internal macro used by `gen_program!` to generate completion infrastructure. /// /// **This macro is only available with the `comp` feature.** /// /// This is an internal macro and should not be called directly by user code. /// It generates a completion dispatcher, the `CompletionContext` type, and /// the execution/render logic for shell completion. /// /// The generated module `__completion_gen` contains: /// - A `__comp` dispatcher that routes completion requests /// - A `__exec_completion` chain that processes `CompletionContext` into `CompletionSuggest` /// - A `__render_completion` renderer that outputs completion suggestions #[proc_macro] #[cfg(feature = "comp")] pub fn program_comp_gen(input: TokenStream) -> TokenStream { let name = read_name(&input); #[cfg(feature = "async")] let fn_exec_comp = quote! { #[doc(hidden)] #[::mingling::macros::chain(#name)] pub async fn __exec_completion(prev: CompletionContext) -> Next { let read_ctx = ::mingling::ShellContext::try_from(prev.inner); match read_ctx { Ok(ctx) => { let suggest = ::mingling::CompletionHelper::exec_completion::<#name>(&ctx); crate::CompletionSuggest::new((ctx, suggest)).to_render() } Err(_) => std::process::exit(1), } } }; #[cfg(not(feature = "async"))] let fn_exec_comp = quote! { #[doc(hidden)] #[::mingling::macros::chain(#name)] pub fn __exec_completion(prev: CompletionContext) -> Next { let read_ctx = ::mingling::ShellContext::try_from(prev.inner); match read_ctx { Ok(ctx) => { let suggest = ::mingling::CompletionHelper::exec_completion::<#name>(&ctx); crate::CompletionSuggest::new((ctx, suggest)).to_render() } Err(_) => std::process::exit(1), } } }; #[cfg(feature = "dispatch_tree")] let internal_dispatcher_comp = quote! { use __internal_completion_mod::__internal_dispatcher___comp; }; #[cfg(not(feature = "dispatch_tree"))] let internal_dispatcher_comp = quote! {}; let comp_dispatcher = quote! { #[doc(hidden)] mod __internal_completion_mod { use super::#name; ::mingling::macros::dispatcher!(#name, "__comp", CMDCompletion => CompletionContext); ::mingling::macros::pack!( #name, CompletionSuggest = (::mingling::ShellContext, ::mingling::Suggest) ); } #internal_dispatcher_comp use __internal_completion_mod::CompletionContext; use __internal_completion_mod::CompletionSuggest; pub use __internal_completion_mod::CMDCompletion; #fn_exec_comp ::mingling::macros::register_type!(CompletionContext); #[allow(unused)] #[doc(hidden)] #[::mingling::macros::renderer(#name)] pub fn __render_completion(prev: CompletionSuggest) { let (ctx, suggest) = prev.inner; ::mingling::CompletionHelper::render_suggest::<#name>(ctx, suggest); } }; TokenStream::from(comp_dispatcher) } /// Registers a type into the global packed types registry for inclusion in /// the program enum generated by `gen_program!`. /// /// This macro is called internally by `pack!` and `#[derive(Groupped)]`(`macro.derive_groupped.html`) /// and is generally not needed in user code. However, it can be used for manual /// registration if you are implementing custom type registration outside of /// the standard macros. /// /// # Syntax /// /// ```rust,ignore /// register_type!(MyType); /// ``` /// /// Each call inserts the type's name into the `PACKED_TYPES` global set, which /// is later consumed by `program_final_gen!` to generate enum variants. /// /// # Panics /// /// Panics if the global `PACKED_TYPES` mutex is poisoned. #[proc_macro] pub fn register_type(input: TokenStream) -> TokenStream { let type_ident = parse_macro_input!(input as syn::Ident); let entry_str = type_ident.to_string(); get_global_set(&PACKED_TYPES) .lock() .unwrap() .insert(entry_str); TokenStream::new() } /// Registers a chain mapping from a previous type to a chain struct. /// /// This macro is called internally by `#[chain]`(macro.chain.html) and is /// generally not needed in user code. It inserts entries into the global /// `CHAINS` and `CHAINS_EXIST` registries. /// /// # Syntax /// /// ```rust,ignore /// register_chain!(PreviousType, ChainStruct); /// ``` /// /// The `PreviousType` is the input type of the chain step, and `ChainStruct` /// is the generated struct that implements the `Chain` trait. #[proc_macro] pub fn register_chain(input: TokenStream) -> TokenStream { chain::register_chain(input) } /// Registers a renderer mapping from a type to a renderer struct. /// /// This macro is called internally by `#[renderer]`(macro.renderer.html) and is /// generally not needed in user code. It inserts entries into the global /// `RENDERERS`, `RENDERERS_EXIST` and (with `general_renderer` feature) /// `GENERAL_RENDERERS` registries. /// /// # Syntax /// /// ```rust,ignore /// register_renderer!(PreviousType, RendererStruct); /// ``` /// /// The `PreviousType` is the input type of the renderer, and `RendererStruct` /// is the generated struct that implements the `Renderer` trait. #[proc_macro] pub fn register_renderer(input: TokenStream) -> TokenStream { renderer::register_renderer(input) } /// Internal macro used by `gen_program!` to generate fallback types. /// /// This macro generates the fallback wrapper types that are essential /// for error handling in the Mingling pipeline: /// /// - **`ErrorRendererNotFound`** — Wraps a `String` (the name of the missing renderer). /// Used when no matching renderer is found for a given output type. /// - **`ErrorDispatcherNotFound`** — Wraps `Vec` (the unrecognized command args). /// Used when no matching dispatcher is found for user input. /// - **`ResultEmpty`** — Wraps `()` (the unit type). /// Used when the chain returns an empty result. /// /// Users can (and should) write `#[renderer]` functions for these types /// to provide meaningful error messages. /// /// This macro is called automatically by `gen_program!` and should not /// be called directly by user code. /// /// # Syntax /// /// ```rust,ignore /// // Called internally by gen_program!: /// program_fallback_gen!(ThisProgram); /// program_fallback_gen!(MyProgram); /// ``` /// /// # Generated code equivalent /// /// ```rust,ignore /// pack!(ProgramName, ErrorRendererNotFound = String); /// pack!(ProgramName, ErrorDispatcherNotFound = Vec); /// pack!(ProgramName, ResultEmpty = ()); /// ``` #[proc_macro] pub fn program_fallback_gen(input: TokenStream) -> TokenStream { let name = read_name(&input); let expanded = quote! { ::mingling::macros::pack!(#name, ErrorRendererNotFound = String); ::mingling::macros::pack!(#name, ErrorDispatcherNotFound = Vec); ::mingling::macros::pack!(#name, ResultEmpty = ()); }; TokenStream::from(expanded) } /// Internal macro used by `gen_program!` to generate the final program enum /// and its `ProgramCollect` implementation. /// /// This is the core code generation macro that: /// 1. Collects all registered types (from `pack!`, `#[derive(Groupped)]`, etc.) and /// creates an enum with each type as a variant. /// 2. Generates the `Display` implementation for the enum. /// 3. Generates the `ProgramCollect` implementation that dispatches to all /// registered renderers, chains, help handlers, completions, and general renderers. /// 4. Adds a `new()` constructor on the enum returning `Program`. /// /// The generated enum's representation type (`#[repr(u8)]`, `#[repr(u16)]`, etc.) /// is automatically chosen based on the number of variants. /// /// This macro is called automatically by `gen_program!` and should not /// be called directly by user code. /// /// # Syntax /// /// ```rust,ignore /// program_final_gen!(ThisProgram); /// program_final_gen!(MyProgram); /// ``` /// /// # Generated code structure /// /// ```rust,ignore /// #[repr(u8)] /// pub enum MyProgram { /// TypeA, /// TypeB, /// // ... /// } /// /// impl ProgramCollect for MyProgram { /// type Enum = MyProgram; /// type ResultEmpty = ResultEmpty; /// fn render(any, r) { /* dispatches to all registered renderers */ } /// fn do_chain(any) -> ChainProcess { /* dispatches to all registered chain steps */ } /// fn render_help(any, r) { /* dispatches to all registered help handlers */ } /// fn has_renderer(any) -> bool { /* checks renderer registry */ } /// fn has_chain(any) -> bool { /* checks chain registry */ } /// // (with comp feature) fn do_comp(...) /// // (with general_renderer feature) fn general_render(...) /// } /// /// impl MyProgram { /// pub fn new() -> Program { Program::new() } /// } /// ``` /// /// # Panics /// /// Panics if any of the global registries (`PACKED_TYPES`, `RENDERERS`, `CHAINS`, etc.) /// are poisoned. #[proc_macro] #[allow(clippy::too_many_lines)] pub fn program_final_gen(input: TokenStream) -> TokenStream { let name = read_name(&input); let packed_types = get_global_set(&PACKED_TYPES).lock().unwrap().clone(); let renderers = get_global_set(&RENDERERS).lock().unwrap().clone(); let chains = get_global_set(&CHAINS).lock().unwrap().clone(); let renderer_exist = get_global_set(&RENDERERS_EXIST).lock().unwrap().clone(); let chain_exist = get_global_set(&CHAINS_EXIST).lock().unwrap().clone(); #[cfg(feature = "general_renderer")] let general_renderers = get_global_set(&GENERAL_RENDERERS).lock().unwrap().clone(); #[cfg(feature = "comp")] let completions = get_global_set(&COMPLETIONS).lock().unwrap().clone(); let packed_types: Vec = packed_types .iter() .map(|s| syn::parse_str::(s).unwrap()) .collect(); let renderer_tokens: Vec = renderers .iter() .map(|s| syn::parse_str::(s).unwrap()) .collect(); let chain_tokens: Vec = chains .iter() .map(|s| syn::parse_str::(s).unwrap()) .collect(); let renderer_exist_tokens: Vec = renderer_exist .iter() .map(|s| syn::parse_str::(s).unwrap()) .collect(); let chain_exist_tokens: Vec = chain_exist .iter() .map(|s| syn::parse_str::(s).unwrap()) .collect(); #[cfg(feature = "general_renderer")] let general_renderer_tokens: Vec = general_renderers .iter() .map(|s| syn::parse_str::(s).unwrap()) .collect(); #[cfg(feature = "general_renderer")] let general_render = quote! { fn general_render( any: ::mingling::AnyOutput, setting: &::mingling::GeneralRendererSetting, ) -> Result<::mingling::RenderResult, ::mingling::error::GeneralRendererSerializeError> { match any.member_id { #(#general_renderer_tokens)* _ => Ok(::mingling::RenderResult::default()), } } }; #[cfg(not(feature = "general_renderer"))] let general_render = quote! {}; #[cfg(feature = "dispatch_tree")] let compile_time_dispatchers: Vec = get_global_set(&COMPILE_TIME_DISPATCHERS) .lock() .unwrap() .clone() .iter() .cloned() .collect(); #[cfg(feature = "dispatch_tree")] let dispatch_tree_nodes = { let entries: Vec<(String, String, String)> = compile_time_dispatchers .iter() .filter_map(|entry| { let parts: Vec<&str> = entry.split(':').collect(); if parts.len() == 3 { Some(( parts[0].to_string(), parts[1].to_string(), parts[2].to_string(), )) } else { None } }) .collect(); let get_nodes_fn = dispatch_tree_gen::gen_get_nodes(&entries); let dispatch_trie_fn = dispatch_tree_gen::gen_dispatch_args_trie(&entries); quote! { #get_nodes_fn #dispatch_trie_fn } }; #[cfg(not(feature = "dispatch_tree"))] let dispatch_tree_nodes = quote! {}; #[cfg(feature = "comp")] let completion_tokens: Vec = completions .iter() .map(|s| syn::parse_str::(s).unwrap()) .collect(); #[cfg(feature = "comp")] let comp = quote! { fn do_comp(any: &::mingling::AnyOutput, ctx: &::mingling::ShellContext) -> ::mingling::Suggest { match any.member_id { #(#completion_tokens)* _ => ::mingling::Suggest::FileCompletion, } } }; #[cfg(not(feature = "comp"))] let comp = quote! {}; let help_tokens: Vec = get_global_set(&HELP_REQUESTS) .lock() .unwrap() .clone() .iter() .map(|s| syn::parse_str::(s).unwrap()) .collect(); let num_variants = packed_types.len(); let repr_type = if u8::try_from(num_variants).is_ok() { quote! { u8 } } else if u16::try_from(num_variants).is_ok() { quote! { u16 } } else if u32::try_from(num_variants).is_ok() { quote! { u32 } } else { quote! { u128 } }; let expanded = quote! { #[derive(Debug, PartialEq, Eq, Clone)] #[repr(#repr_type)] pub enum #name { #(#packed_types),* } impl ::std::fmt::Display for #name { fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { match self { #(#name::#packed_types => write!(f, stringify!(#packed_types)),)* } } } impl ::mingling::ProgramCollect for #name { type Enum = #name; type ErrorDispatcherNotFound = ErrorDispatcherNotFound; type ErrorRendererNotFound = ErrorRendererNotFound; type ResultEmpty = ResultEmpty; fn build_renderer_not_found(member_id: Self::Enum) -> ::mingling::AnyOutput { ::mingling::AnyOutput::new(ErrorRendererNotFound::new(member_id.to_string())) } fn build_dispatcher_not_found(args: Vec) -> ::mingling::AnyOutput { ::mingling::AnyOutput::new(ErrorDispatcherNotFound::new(args)) } fn build_empty_result() -> ::mingling::AnyOutput { ::mingling::AnyOutput::new(ResultEmpty::new(())) } ::mingling::__dispatch_program_renderers!( #(#renderer_tokens)* ); ::mingling::__dispatch_program_chains!( #(#chain_tokens)* ); fn render_help(any: ::mingling::AnyOutput, __renderer_inner_result: &mut ::mingling::RenderResult) { match any.member_id { #(#help_tokens)* _ => (), } } fn has_renderer(any: &::mingling::AnyOutput) -> bool { match any.member_id { #(#renderer_exist_tokens)* _ => false } } fn has_chain(any: &::mingling::AnyOutput) -> bool { match any.member_id { #(#chain_exist_tokens)* _ => false } } #dispatch_tree_nodes #general_render #comp } impl #name { /// Creates a new `Program<#name>` instance with default configuration. pub fn new() -> ::mingling::Program<#name> { ::mingling::Program::new() } /// Returns a static reference to the global `Program<#name>` singleton. pub fn this() -> &'static ::mingling::Program<#name> { &::mingling::this::<#name>() } } }; TokenStream::from(expanded) } /// Builds a `Suggest` instance with inline suggestion items. /// /// **This macro is only available with the `comp` feature.** /// /// The `suggest!` macro provides a concise syntax for creating shell completion /// suggestions. Each item can be either a simple flag or a flag with a description. /// /// # Syntax /// /// ```rust,ignore /// // Empty suggestions: /// suggest!() /// /// // Simple flags (no description): /// suggest! { "--flag1", "--flag2" } /// /// // Flags with descriptions: /// suggest! { /// "--name": "User's name", /// "--age": "User's age" /// } /// /// // Mixed: /// suggest! { /// "--name": "User's name", /// "--verbose" /// } /// ``` /// /// # Example /// /// ```rust,ignore /// use mingling::macros::{completion, suggest}; /// use mingling::{ShellContext, Suggest}; /// /// #[completion(MyEntry)] /// fn complete(ctx: &ShellContext) -> Suggest { /// if ctx.typing_argument() { /// return suggest! { /// "--name": "Provide a name", /// "--type": "Select a type" /// }.strip_typed_argument(ctx); /// } /// suggest!() /// } /// ``` /// /// # Related /// /// - `suggest_enum!`(`macro.suggest_enum.html`) — Build suggestions from an `EnumTag` enum. #[cfg(feature = "comp")] #[proc_macro] pub fn suggest(input: TokenStream) -> TokenStream { suggest::suggest(input) } /// Builds a `Suggest` instance from an `EnumTag` enum's variants. /// /// **This macro is only available with the `comp` feature.** /// /// The `suggest_enum!` macro iterates over all variants of an `EnumTag`-derived /// enum and creates suggestion items using each variant's display name /// (from `#[enum_rename]`) and description (from `#[enum_desc]`). /// /// # Syntax /// /// ```rust,ignore /// suggest_enum!(MyEnumType); /// ``` /// /// # Example /// /// ```rust,ignore /// use mingling::macros::{completion, suggest_enum}; /// use mingling::{ShellContext, Suggest, EnumTag}; /// /// #[derive(EnumTag)] /// enum Fruit { /// #[enum_desc("A sweet red fruit")] /// #[enum_rename("apple")] /// Apple, /// #[enum_desc("A yellow tropical fruit")] /// #[enum_rename("banana")] /// Banana, /// } /// /// #[completion(MyEntry)] /// fn complete(ctx: &ShellContext) -> Suggest { /// if ctx.filling_argument_first("--fruit") { /// return suggest_enum!(Fruit); /// } /// suggest!() /// } /// ``` /// /// # Generated code equivalent /// /// ```rust,ignore /// { /// let mut enum_suggest = Suggest::new(); /// for (name, desc) in ::enums() { /// if desc.is_empty() { /// enum_suggest.insert(SuggestItem::new(name.to_string())); /// } else { /// enum_suggest.insert(SuggestItem::new_with_desc(name.to_string(), desc.to_string())); /// } /// } /// enum_suggest /// } /// ``` /// /// # Related /// /// - `suggest!`(macro.suggest.html) — Build suggestions with inline syntax. /// - `EnumTag`(derive.EnumTag.html) — The derive macro required for the enum type. #[cfg(feature = "comp")] #[proc_macro] pub fn suggest_enum(input: TokenStream) -> TokenStream { suggest::suggest_enum(input) } fn read_name(input: &TokenStream) -> Ident { if input.is_empty() { Ident::new(DEFAULT_PROGRAM_NAME, proc_macro2::Span::call_site()) } else { syn::parse(input.clone()).unwrap() } }