aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_macros/src')
-rw-r--r--mingling_macros/src/attr/command.rs53
-rw-r--r--mingling_macros/src/attr/dispatcher_clap.rs39
-rw-r--r--mingling_macros/src/func.rs1
-rw-r--r--mingling_macros/src/func/dispatcher.rs123
-rw-r--r--mingling_macros/src/func/node.rs60
-rw-r--r--mingling_macros/src/func/program_comp_gen.rs4
-rw-r--r--mingling_macros/src/lib.rs49
7 files changed, 101 insertions, 228 deletions
diff --git a/mingling_macros/src/attr/command.rs b/mingling_macros/src/attr/command.rs
index 4542bd7..6f2409c 100644
--- a/mingling_macros/src/attr/command.rs
+++ b/mingling_macros/src/attr/command.rs
@@ -12,12 +12,10 @@ use syn::{FnArg, Ident, ItemFn, LitStr, PatType, Token, Type};
///
/// Supports:
/// - `node = "dot.separated.path"` — explicit command path
-/// - `name = CMDName` — explicit CMD struct name
/// - `entry = EntryName` — explicit Entry struct name
/// - bare paths like `routeify`, `::mingling::macros::routeify` — extension attrs for the original fn
struct CommandArgs {
node: Option<LitStr>,
- name: Option<Ident>,
entry: Option<Ident>,
exts: Vec<syn::Path>,
}
@@ -25,7 +23,6 @@ struct CommandArgs {
impl Parse for CommandArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut node = None;
- let mut name = None;
let mut entry = None;
let mut exts = Vec::new();
@@ -41,19 +38,18 @@ impl Parse for CommandArgs {
}
node = Some(input.parse()?);
} else if key == "name" {
- if name.is_some() {
- return Err(input.error("duplicate `name` argument"));
- }
- name = Some(input.parse()?);
+ return Err(input.error(
+ "`name = ...` was removed in 0.5.0; the dispatcher struct is generated internally",
+ ));
} else if key == "entry" {
if entry.is_some() {
return Err(input.error("duplicate `entry` argument"));
}
entry = Some(input.parse()?);
} else {
- return Err(input.error(format!(
- "unknown key `{key}`; expected `node`, `name`, or `entry`"
- )));
+ return Err(
+ input.error(format!("unknown key `{key}`; expected `node` or `entry`"))
+ );
}
} else {
// Extension path (e.g. `routeify` or `::mingling::macros::routeify`)
@@ -67,12 +63,7 @@ impl Parse for CommandArgs {
}
}
- Ok(Self {
- node,
- name,
- entry,
- exts,
- })
+ Ok(Self { node, entry, exts })
}
}
@@ -127,17 +118,15 @@ fn handle_async(f: &ItemFn) -> Result<(TokenStream2, TokenStream2), TokenStream2
struct ResolvedNames {
/// `node_str` as a string literal token
node_lit: LitStr,
- /// Whether the user supplied any explicit override (node/name/entry)
+ /// Whether the user supplied any explicit override (node/entry)
has_overrides: bool,
- /// CMD struct name (e.g. `CMDGreet`)
- cmd_name: Ident,
/// Entry struct name (e.g. `EntryGreet`)
entry_type: Ident,
/// Chain wrapper function name (e.g. `__command_chain_greet`)
chain_fn_name: Ident,
}
-/// Resolves `node`, `cmd_name`, `entry_type`, and `chain_fn_name` from
+/// Resolves `node`, `entry_type`, and `chain_fn_name` from
/// the attribute args and the original function name.
fn resolve_names(fn_name: &Ident, args: &CommandArgs) -> ResolvedNames {
let fn_name_str = fn_name.to_string();
@@ -148,12 +137,7 @@ fn resolve_names(fn_name: &Ident, args: &CommandArgs) -> ResolvedNames {
.map_or_else(|| default_node_from_fn(fn_name), syn::LitStr::value);
let node_lit = syn::LitStr::new(&node_str, fn_name.span());
- let has_overrides = args.node.is_some() || args.name.is_some() || args.entry.is_some();
-
- let cmd_name = args.name.clone().unwrap_or_else(|| {
- let pascal = just_fmt::pascal_case!(&node_str);
- Ident::new(&format!("CMD{pascal}"), fn_name.span())
- });
+ let has_overrides = args.node.is_some() || args.entry.is_some();
let entry_type = args.entry.clone().unwrap_or_else(|| {
let pascal = just_fmt::pascal_case!(&node_str);
@@ -165,7 +149,6 @@ fn resolve_names(fn_name: &Ident, args: &CommandArgs) -> ResolvedNames {
ResolvedNames {
node_lit,
has_overrides,
- cmd_name,
entry_type,
chain_fn_name,
}
@@ -247,13 +230,12 @@ fn build_call_args(sig: &syn::Signature) -> Vec<TokenStream2> {
/// Generates the `dispatcher!(...)` call.
///
/// - No overrides → abbreviated form: `dispatcher!("node")`
-/// - Any override → explicit form: `dispatcher!("node", CMDName => EntryName)`
+/// - Any override → explicit form: `dispatcher!("node", EntryType)`
fn build_dispatcher_invoke(names: &ResolvedNames) -> TokenStream2 {
let node_lit = &names.node_lit;
if names.has_overrides {
- let cmd_name = &names.cmd_name;
let entry_type = &names.entry_type;
- quote! { ::mingling::macros::dispatcher!(#node_lit, #cmd_name => #entry_type); }
+ quote! { ::mingling::macros::dispatcher!(#node_lit, #entry_type); }
} else {
quote! { ::mingling::macros::dispatcher!(#node_lit); }
}
@@ -271,7 +253,6 @@ pub(crate) fn command_attr(attr: TokenStream, item: TokenStream) -> TokenStream
let args: CommandArgs = if attr.is_empty() {
CommandArgs {
node: None,
- name: None,
entry: None,
exts: Vec::new(),
}
@@ -332,7 +313,13 @@ pub(crate) fn command_attr(attr: TokenStream, item: TokenStream) -> TokenStream
quote! { #vis use super::#ident; }
};
- let cmd_name = &names.cmd_name;
+ // hidden dispatcher struct generated by the internal `dispatcher!` call
+ let hidden_dispatcher = {
+ let node_str = names.node_lit.value();
+ let pascal = just_fmt::pascal_case!(&node_str);
+ Ident::new(&format!("__Dispatcher{pascal}"), fn_name.span())
+ };
+
let entry_type = &names.entry_type;
// assemble output
@@ -351,7 +338,7 @@ pub(crate) fn command_attr(attr: TokenStream, item: TokenStream) -> TokenStream
// hidden module gathering all generated types for pathf / external access
#[doc(hidden)]
#vis mod #mod_name {
- #vis use super::#cmd_name;
+ #vis use super::#hidden_dispatcher;
#vis use super::#entry_type;
#vis use super::#chain_internal;
#dispatcher_internal
diff --git a/mingling_macros/src/attr/dispatcher_clap.rs b/mingling_macros/src/attr/dispatcher_clap.rs
index 2f45b14..46238d6 100644
--- a/mingling_macros/src/attr/dispatcher_clap.rs
+++ b/mingling_macros/src/attr/dispatcher_clap.rs
@@ -30,7 +30,13 @@ impl Parse for ClapOptions {
}
let key: Ident = input.parse()?;
- input.parse::<Token![=]>()?;
+ if input.parse::<Token![=]>().is_err() {
+ return Err(syn::Error::new(
+ key.span(),
+ "expected `key = value`; note: the explicit CMD struct argument \
+ was removed in 0.5.0, use `dispatcher_clap!(\"name\", help = ..., error = ...)`",
+ ));
+ }
if key == "error" {
let value: Ident = input.parse()?;
@@ -63,18 +69,15 @@ impl Parse for ClapOptions {
/// Input for the `dispatcher_clap` attribute
struct DispatcherClapInput {
- /// `("cmd", Disp, ...)`
+ /// `("cmd", options...)`
command_name: LitStr,
- dispatcher_struct: Ident,
options: ClapOptions,
}
impl Parse for DispatcherClapInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
- // Format: "cmd", Disp, ...
+ // Format: "cmd", options...
let command_name: LitStr = input.parse()?;
- input.parse::<Token![,]>()?;
- let dispatcher_struct: Ident = input.parse()?;
let options = if input.is_empty() {
ClapOptions {
@@ -87,13 +90,13 @@ impl Parse for DispatcherClapInput {
Ok(Self {
command_name,
- dispatcher_struct,
options,
})
}
}
#[cfg(feature = "clap")]
+#[allow(clippy::too_many_lines)]
pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
let attr_input = parse_macro_input!(attr as DispatcherClapInput);
let input_struct = parse_macro_input!(item as ItemStruct);
@@ -102,7 +105,12 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke
let program_path = crate::default_program_path();
let command_name_str = attr_input.command_name.value();
- let dispatcher_struct = &attr_input.dispatcher_struct;
+
+ // The dispatcher struct is now generated internally.
+ let dispatcher_struct = Ident::new(
+ &format!("__Dispatcher{}", just_fmt::pascal_case!(&command_name_str)),
+ attr_input.command_name.span(),
+ );
let options = &attr_input.options;
// Generate the `begin` method body
@@ -141,8 +149,7 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke
// Generate the #[help] block if help = true
let help_gen = if options.help_enabled {
- let dispatcher_name_str = dispatcher_struct.to_string();
- let help_fn_name_str = format!("__{}_help", just_fmt::snake_case!(&dispatcher_name_str));
+ let help_fn_name_str = format!("__{}_help", just_fmt::snake_case!(&command_name_str));
let help_fn_name = Ident::new(&help_fn_name_str, proc_macro2::Span::call_site());
Some(quote! {
@@ -175,7 +182,7 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke
};
let compile_time_registration =
- get_compile_time_registration(&command_name_str, dispatcher_struct, struct_name);
+ get_compile_time_registration(&command_name_str, &dispatcher_struct, struct_name);
let expanded = quote! {
// Keep the original struct definition
@@ -196,10 +203,6 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke
pub(crate) struct #dispatcher_struct;
impl ::mingling::Dispatcher<#program_path> for #dispatcher_struct {
- fn node(&self) -> ::mingling::Node {
- ::mingling::macros::node!(#command_name_str)
- }
-
fn begin(
&self,
args: Vec<String>,
@@ -211,12 +214,6 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke
#begin_body
}
-
- fn clone_dispatcher(
- &self,
- ) -> Box<dyn ::mingling::Dispatcher<#program_path>> {
- Box::new(#dispatcher_struct)
- }
}
};
diff --git a/mingling_macros/src/func.rs b/mingling_macros/src/func.rs
index 834fa7a..57d0ec5 100644
--- a/mingling_macros/src/func.rs
+++ b/mingling_macros/src/func.rs
@@ -9,7 +9,6 @@ pub(crate) mod gen_program;
pub(crate) mod group;
#[cfg(all(feature = "structural_renderer", feature = "extras"))]
pub(crate) mod group_structural;
-pub(crate) mod node;
pub(crate) mod pack;
#[cfg(feature = "extras")]
pub(crate) mod pack_err;
diff --git a/mingling_macros/src/func/dispatcher.rs b/mingling_macros/src/func/dispatcher.rs
index 6183993..c4d67a9 100644
--- a/mingling_macros/src/func/dispatcher.rs
+++ b/mingling_macros/src/func/dispatcher.rs
@@ -6,13 +6,14 @@ use syn::parse::{Parse, ParseStream};
use syn::{Attribute, Ident, LitStr, Token};
enum DispatcherChainInput {
+ /// `dispatcher!("name", EntryType)` — explicit entry type
Default {
cmd_attrs: Vec<Attribute>,
entry_attrs: Vec<Attribute>,
command_name: syn::LitStr,
- command_struct: Ident,
pack: Ident,
},
+ /// `dispatcher!("name")` — entry type derived from the command name
#[cfg(feature = "extras")]
Auto {
cmd_attrs: Vec<Attribute>,
@@ -22,106 +23,107 @@ enum DispatcherChainInput {
impl Parse for DispatcherChainInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
- // Collect outer attributes for the CMD struct
+ // Collect outer attributes for the hidden dispatcher struct
let cmd_attrs = input.call(Attribute::parse_outer)?;
- if input.peek(syn::LitStr) {
- // Parse the command name string first
- let command_name: LitStr = input.parse()?;
-
- // Check if this is the abbreviated form: just "command_name" without ", CMD => Entry"
- if input.is_empty() {
- #[cfg(feature = "extras")]
- {
- return Ok(Self::Auto {
- cmd_attrs,
- command_name,
- });
- }
- #[cfg(not(feature = "extras"))]
- {
- return Err(syn::Error::new(
- command_name.span(),
- "expected `, CommandStruct => EntryStruct` after command name",
- ));
- }
+ let command_name: LitStr = input.parse()?;
+
+ if input.is_empty() {
+ // Abbreviated form: just "command_name"
+ #[cfg(feature = "extras")]
+ {
+ return Ok(Self::Auto {
+ cmd_attrs,
+ command_name,
+ });
+ }
+ #[cfg(not(feature = "extras"))]
+ {
+ return Err(syn::Error::new(
+ command_name.span(),
+ "expected `, EntryType` after command name",
+ ));
}
+ }
- // Default format: "command_name", CommandStruct => ChainStruct
- input.parse::<Token![,]>()?;
- let command_struct = input.parse()?;
- input.parse::<Token![=>]>()?;
- let entry_attrs = input.call(Attribute::parse_outer)?;
- let pack = input.parse()?;
-
- Ok(Self::Default {
- cmd_attrs,
- entry_attrs,
- command_name,
- command_struct,
- pack,
- })
- } else {
- Err(input.lookahead1().error())
+ // Explicit form: "command_name", EntryType
+ input.parse::<Token![,]>()?;
+ let entry_attrs = input.call(Attribute::parse_outer)?;
+ let pack: Ident = input.parse()?;
+
+ // The old `"name", CMD => Entry` form was removed in 0.5.0.
+ if input.peek(Token![=>]) {
+ return Err(syn::Error::new(
+ pack.span(),
+ "the `dispatcher!(\"name\", CMD => Entry)` form was removed in 0.5.0; \
+ use `dispatcher!(\"name\", Entry)` — the dispatcher struct is generated internally",
+ ));
}
+
+ Ok(Self::Default {
+ cmd_attrs,
+ entry_attrs,
+ command_name,
+ pack,
+ })
}
}
-// NOTICE: The token stream generation patterns in `dispatcher_chain` and `dispatcher_render`
-// are nearly identical and could benefit from refactoring into common helper functions.
-
-#[allow(clippy::too_many_lines)]
pub(crate) fn dispatcher(input: TokenStream) -> TokenStream {
- // Parse the input
let dispatcher_input = syn::parse_macro_input!(input as DispatcherChainInput);
#[cfg(not(feature = "extras"))]
- let (command_name, command_struct, pack, cmd_attrs, entry_attrs) = match dispatcher_input {
+ let (command_name, pack, cmd_attrs, entry_attrs) = match dispatcher_input {
DispatcherChainInput::Default {
cmd_attrs,
entry_attrs,
command_name,
- command_struct,
pack,
- } => (command_name, command_struct, pack, cmd_attrs, entry_attrs),
+ } => (command_name, pack, cmd_attrs, entry_attrs),
};
#[cfg(feature = "extras")]
- let (command_name, command_struct, pack, cmd_attrs, entry_attrs) = match dispatcher_input {
+ let (command_name, pack, cmd_attrs, entry_attrs) = match dispatcher_input {
DispatcherChainInput::Default {
cmd_attrs,
entry_attrs,
command_name,
- command_struct,
pack,
- } => (command_name, command_struct, pack, cmd_attrs, entry_attrs),
+ } => (command_name, pack, cmd_attrs, entry_attrs),
DispatcherChainInput::Auto {
cmd_attrs,
command_name,
} => {
let command_name_str = command_name.value();
let pascal = just_fmt::pascal_case!(&command_name_str);
- let command_struct = Ident::new(&format!("CMD{pascal}"), command_name.span());
let pack = Ident::new(&format!("Entry{pascal}"), command_name.span());
- (command_name, command_struct, pack, cmd_attrs, Vec::new())
+ (command_name, pack, cmd_attrs, Vec::new())
}
};
let command_name_str = command_name.value();
+ let hidden_dispatcher = Ident::new(
+ &format!("__Dispatcher{}", just_fmt::pascal_case!(&command_name_str)),
+ command_name.span(),
+ );
let comp_entry = get_comp_entry(&pack);
let compile_time_registration =
- get_compile_time_registration(&command_name_str, &command_struct, &pack);
+ get_compile_time_registration(&command_name_str, &hidden_dispatcher, &pack);
let program_type = crate::default_program_path();
let expanded = quote! {
+ ::mingling::macros::pack!(#(#entry_attrs)* #pack = Vec<String>);
+
#(#cmd_attrs)*
+ #[doc(hidden)]
#[derive(Debug, Default)]
- pub struct #command_struct;
+ #[allow(nonstandard_style)]
+ pub struct #hidden_dispatcher;
- ::mingling::macros::pack!(#(#entry_attrs)* #pack = Vec<String>);
+ #compile_time_registration
impl From<#pack> for crate::Entry {
fn from(value: #pack) -> Self {
@@ -130,19 +132,12 @@ pub(crate) fn dispatcher(input: TokenStream) -> TokenStream {
}
#comp_entry
- #compile_time_registration
- impl ::mingling::Dispatcher<#program_type> for #command_struct {
- fn node(&self) -> ::mingling::Node {
- ::mingling::macros::node!(#command_name_str)
- }
+ impl ::mingling::Dispatcher<#program_type> for #hidden_dispatcher {
fn begin(&self, args: Vec<String>) -> ::mingling::ChainProcess<#program_type> {
use ::mingling::Grouped;
::mingling::Routable::to_chain(#pack::new(args))
}
- fn clone_dispatcher(&self) -> Box<dyn ::mingling::Dispatcher<#program_type>> {
- Box::new(#command_struct)
- }
}
};
@@ -173,11 +168,11 @@ fn get_comp_entry(_entry_name: &Ident) -> TokenStream2 {
/// (trie vs. linear list) is generated later by `gen_program!`.
fn get_compile_time_registration(
command_name_str: &str,
- command_struct: &Ident,
+ dispatcher_struct: &Ident,
entry_name: &Ident,
) -> TokenStream2 {
let node_name_lit = syn::LitStr::new(command_name_str, proc_macro2::Span::call_site());
quote! {
- ::mingling::macros::register_dispatcher!(#node_name_lit, #command_struct, #entry_name);
+ ::mingling::macros::register_dispatcher!(#node_name_lit, #dispatcher_struct, #entry_name);
}
}
diff --git a/mingling_macros/src/func/node.rs b/mingling_macros/src/func/node.rs
deleted file mode 100644
index 9963037..0000000
--- a/mingling_macros/src/func/node.rs
+++ /dev/null
@@ -1,60 +0,0 @@
-// Doc Not Optimize
-use just_fmt::kebab_case;
-use proc_macro::TokenStream;
-use proc_macro2::TokenStream as TokenStream2;
-use quote::quote;
-use syn::parse::{Parse, ParseStream};
-use syn::{LitStr, Result as SynResult};
-
-/// Parses a string literal input for the node macro
-struct NodeInput {
- path: LitStr,
-}
-
-impl Parse for NodeInput {
- fn parse(input: ParseStream) -> SynResult<Self> {
- Ok(Self {
- path: input.parse()?,
- })
- }
-}
-
-pub(crate) fn node(input: TokenStream) -> TokenStream {
- // Parse the input as a string literal
- let input_parsed = syn::parse_macro_input!(input as NodeInput);
- let path_str = input_parsed.path.value();
-
- // If the input string is empty, return an empty Node
- if path_str.is_empty() {
- return quote! {
- mingling::Node::default()
- }
- .into();
- }
-
- // Split the path by dots
- let parts: Vec<String> = path_str
- .split('.')
- .map(|s| {
- if s.starts_with('_') {
- s.to_string()
- } else {
- kebab_case!(s)
- }
- })
- .collect();
-
- // Build the expression starting from Node::default()
- let mut expr: TokenStream2 = quote! {
- mingling::Node::default()
- };
-
- // Add .join() calls for each part of the path
- for part in parts {
- expr = quote! {
- #expr.join(#part)
- };
- }
-
- expr.into()
-}
diff --git a/mingling_macros/src/func/program_comp_gen.rs b/mingling_macros/src/func/program_comp_gen.rs
index 7f77d46..ed04379 100644
--- a/mingling_macros/src/func/program_comp_gen.rs
+++ b/mingling_macros/src/func/program_comp_gen.rs
@@ -42,13 +42,14 @@ pub(crate) fn program_comp_gen_impl(_input: TokenStream) -> TokenStream {
let internal_dispatcher_comp = quote! {
use __internal_completion_mod::__internal_dispatcher_comp;
+ use __internal_completion_mod::__DispatcherComp;
};
let comp_dispatcher = quote! {
#[doc(hidden)]
mod __internal_completion_mod {
use ::mingling::Grouped;
- ::mingling::macros::dispatcher!("__comp", CMDCompletion => CompletionContext);
+ ::mingling::macros::dispatcher!("__comp", CompletionContext);
::mingling::macros::pack!(
CompletionSuggest = (::mingling::ShellContext, ::mingling::Suggest)
);
@@ -56,7 +57,6 @@ pub(crate) fn program_comp_gen_impl(_input: TokenStream) -> TokenStream {
#internal_dispatcher_comp
use __internal_completion_mod::CompletionContext;
use __internal_completion_mod::CompletionSuggest;
- pub use __internal_completion_mod::CMDCompletion;
#fn_exec_comp
diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs
index ad84548..607ba9f 100644
--- a/mingling_macros/src/lib.rs
+++ b/mingling_macros/src/lib.rs
@@ -42,7 +42,7 @@ pub(crate) use func::group as group_impl;
use func::pack_err;
#[cfg(feature = "comp")]
use func::suggest;
-use func::{dispatcher, node, pack};
+use func::{dispatcher, pack};
use systems::res_injection;
pub(crate) fn default_program_path() -> proc_macro2::TokenStream {
quote::quote! { crate::ThisProgram }
@@ -183,47 +183,6 @@ pub fn group_structural(input: TokenStream) -> TokenStream {
func::group_structural::group_structural(input)
}
-/// 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.
///
@@ -598,10 +557,8 @@ pub fn empty_result(input: TokenStream) -> TokenStream {
///
/// 1. **Entry struct** — A `pack!`-style wrapper around `Vec<String>` (the raw args).
/// Registered in the program enum via `register_type!`.
-/// 2. **Dispatcher struct** — A zero-sized struct implementing [`Dispatcher<Program>`]:
-/// - `node()` returns the [`Node`] hierarchy for the command path.
+/// 2. **Dispatcher struct** — A hidden zero-sized struct implementing [`Dispatcher<Program>`]:
/// - `begin(args)` wraps `args` into the entry type and routes to chain.
-/// - `clone_dispatcher()` returns a boxed clone.
/// 3. **Registration** — Calls `register_dispatcher!` to collect the command
/// at compile time (the `dispatch_tree` feature only selects the matching
/// strategy generated later by `gen_program!`).
@@ -612,12 +569,10 @@ pub fn empty_result(input: TokenStream) -> TokenStream {
/// # See also
///
/// - `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<Program>`]: 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)