diff options
Diffstat (limited to 'mingling_pathf')
| -rw-r--r-- | mingling_pathf/src/pattern_analyzer.rs | 15 | ||||
| -rw-r--r-- | mingling_pathf/src/patterns.rs | 2 | ||||
| -rw-r--r-- | mingling_pathf/src/patterns/command.rs | 81 | ||||
| -rw-r--r-- | mingling_pathf/src/type_mapping_builder.rs | 17 |
4 files changed, 108 insertions, 7 deletions
diff --git a/mingling_pathf/src/pattern_analyzer.rs b/mingling_pathf/src/pattern_analyzer.rs index 5b25c15..79b1c5a 100644 --- a/mingling_pathf/src/pattern_analyzer.rs +++ b/mingling_pathf/src/pattern_analyzer.rs @@ -33,6 +33,7 @@ pub fn init_with_config(config: PathfinderConfig) -> PatternAnalyzer { analyzer.add_pattern(GroupPattern); analyzer.add_pattern(GroupedDerivePattern); analyzer.add_pattern(ChainPattern); + analyzer.add_pattern(CommandPattern); analyzer.add_pattern(RendererPattern); analyzer.add_pattern(HelpPattern); analyzer.add_pattern(CompletionPattern); @@ -50,6 +51,8 @@ pub struct AnalyzeItem { pub item_name: String, /// Whether the item is from an external crate (resolved via `use`), bypassing the file's own module path. pub is_foreign: bool, + /// When `true`, this item is a module whose contents should be glob-imported (`::*`). + pub is_module: bool, } impl AnalyzeItem { @@ -59,6 +62,17 @@ impl AnalyzeItem { module, item_name, is_foreign: false, + is_module: false, + } + } + + /// Creates a local module item — generates a `use path::item_name::*;` glob import. + pub fn local_module(module: String, item_name: String) -> Self { + Self { + module, + item_name, + is_foreign: false, + is_module: true, } } @@ -68,6 +82,7 @@ impl AnalyzeItem { module, item_name, is_foreign: true, + is_module: false, } } } diff --git a/mingling_pathf/src/patterns.rs b/mingling_pathf/src/patterns.rs index 9845b73..964fe7c 100644 --- a/mingling_pathf/src/patterns.rs +++ b/mingling_pathf/src/patterns.rs @@ -2,6 +2,7 @@ pub use basic_struct::*; pub use chain::*; +pub use command::*; pub use completion::*; pub use dispatcher::*; pub use dispatcher_clap::*; @@ -13,6 +14,7 @@ pub use renderer::*; mod basic_struct; mod chain; +mod command; mod completion; mod dispatcher; mod dispatcher_clap; 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") + }) +} diff --git a/mingling_pathf/src/type_mapping_builder.rs b/mingling_pathf/src/type_mapping_builder.rs index f6f57a5..fb59b43 100644 --- a/mingling_pathf/src/type_mapping_builder.rs +++ b/mingling_pathf/src/type_mapping_builder.rs @@ -25,7 +25,7 @@ pub fn analyze_and_build_type_mapping_for( let module_mapping = module_pathf::analyze(crate_dir)?; let analyzer = pattern_analyzer::init_with_config(config.clone()); - let mut type_mappings: Vec<(String, String)> = Vec::new(); + let mut type_mappings: Vec<(String, String, bool)> = Vec::new(); for item in module_mapping { let file_abs = crate_dir.join(item.file_path()); @@ -40,14 +40,13 @@ pub fn analyze_and_build_type_mapping_for( for ai in analyze_items { let full_path = if ai.is_foreign { - // Foreign item — use its own module path as-is format!("{}::{}", ai.module, ai.item_name) } else if ai.module.is_empty() { format!("{}::{}", module_path, ai.item_name) } else { format!("{}::{}::{}", module_path, ai.module, ai.item_name) }; - type_mappings.push((ai.item_name, full_path)); + type_mappings.push((ai.item_name, full_path, ai.is_module)); } } @@ -56,7 +55,7 @@ pub fn analyze_and_build_type_mapping_for( // Deduplicate by type name, keeping the first occurrence let mut seen = HashSet::new(); - type_mappings.retain(|(name, _)| seen.insert(name.clone())); + type_mappings.retain(|(name, _, _)| seen.insert(name.clone())); // Create output directory std::fs::create_dir_all(output_dir)?; @@ -66,14 +65,18 @@ pub fn analyze_and_build_type_mapping_for( let type_using_path = output_dir.join("type_using.rs"); let mut content_mapping = String::new(); - for (name, path) in &type_mappings { + for (name, path, _) in &type_mappings { content_mapping.push_str(&format!("{name} = {path}\n")); } std::fs::write(&output_path, content_mapping)?; let mut content_using = String::new(); - for (_, path) in &type_mappings { - content_using.push_str(&format!("use {path};\n")); + for (_, path, is_module) in &type_mappings { + if *is_module { + content_using.push_str(&format!("use {path}::*;\n")); + } else { + content_using.push_str(&format!("use {path};\n")); + } } std::fs::write(&type_using_path, content_using)?; |
