diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-07-28 19:11:14 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-07-28 19:11:14 +0800 |
| commit | 2dff21cc78fd2d6a4fa4f227f6ad94e3f7baa943 (patch) | |
| tree | de694a2d34a6dd60b08d6da31c4f27fcd5eec416 /mingling_pathf/src/patterns/command.rs | |
| parent | bf35af6bbf30492bcd23eb6103fdac3382dd6d33 (diff) | |
feat(mingling_macros, mingling_pathf): add `#[command]` attribute macro
Add a new `#[command]` attribute macro (feature-gated behind
`extra_macros`) that converts a plain function with a `Vec<String>`
parameter into a fully wired Mingling command. The macro generates a
`dispatcher!` call, a `#[chain]` wrapper, and a hidden module
re-exporting
all generated types.
Also add `CommandPattern` to the pathf pattern analyzer to recognize
`#[command]`-annotated functions and track their generated hidden
modules.
Diffstat (limited to 'mingling_pathf/src/patterns/command.rs')
| -rw-r--r-- | mingling_pathf/src/patterns/command.rs | 81 |
1 files changed, 81 insertions, 0 deletions
diff --git a/mingling_pathf/src/patterns/command.rs b/mingling_pathf/src/patterns/command.rs new file mode 100644 index 0000000..fac70af --- /dev/null +++ b/mingling_pathf/src/patterns/command.rs @@ -0,0 +1,81 @@ +//! The `CommandPattern` matches functions annotated with `#[command]` and +//! extracts the generated hidden module name (`__command_<fn>_module`). +//! +//! Supported forms: +//! ```rust,ignore +//! #[command] +//! fn greet(args: Vec<String>) -> Next { ... } +//! +//! #[command(node = "foo.foo-bar")] +//! fn greet(args: Vec<String>) -> Next { ... } +//! +//! #[command(name = CMDGreet, entry = EntryGreet)] +//! fn greet(args: Vec<String>) -> Next { ... } +//! +//! #[command(node = "greet", name = CMDGreet, entry = EntryGreet, buffer)] +//! fn greet(args: Vec<String>) { ... } +//! ``` + +use syn::Item; + +use crate::pattern_analyzer::{AnalyzeItem, AnalyzePattern}; + +/// Match `#[command]` functions. +/// +/// The `#[command]` macro generates a hidden `__command_<fn>_module` that +/// re-exports all internal types (`Entry*`, `CMD*`, chain struct, dispatcher +/// static). This pattern tracks that module; the build system generates a +/// glob re-export `use path::__command_<fn>_module::*;` to bring everything +/// into scope. +pub struct CommandPattern; + +impl AnalyzePattern for CommandPattern { + fn contains(&self, content: &str) -> bool { + content.contains("command") + } + + fn analyze(&self, content: &str) -> Vec<AnalyzeItem> { + let Ok(syntax) = syn::parse_file(content) else { + return Vec::new(); + }; + + let mut items = Vec::new(); + for item in &syntax.items { + collect_from_item(item, "", &mut items); + } + items + } +} + +fn collect_from_item(item: &Item, current_mod: &str, items: &mut Vec<AnalyzeItem>) { + match item { + Item::Fn(f) if has_command_attr(&f.attrs) => { + let fn_name = f.sig.ident.to_string(); + let mod_name = format!("__command_{}_module", &fn_name); + items.push(AnalyzeItem::local_module(current_mod.to_string(), mod_name)); + } + Item::Mod(item_mod) => { + if let Some((_, nested)) = &item_mod.content { + let mod_name = &item_mod.ident.to_string(); + let nested_mod = if current_mod.is_empty() { + mod_name.clone() + } else { + format!("{current_mod}::{mod_name}") + }; + for n in nested { + collect_from_item(n, &nested_mod, items); + } + } + } + _ => {} + } +} + +fn has_command_attr(attrs: &[syn::Attribute]) -> bool { + attrs.iter().any(|a| { + a.path() + .segments + .last() + .is_some_and(|s| s.ident == "command") + }) +} |
