aboutsummaryrefslogtreecommitdiff
path: root/mingling_pathf
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_pathf')
-rw-r--r--mingling_pathf/src/pattern_analyzer.rs15
-rw-r--r--mingling_pathf/src/patterns.rs2
-rw-r--r--mingling_pathf/src/patterns/command.rs81
-rw-r--r--mingling_pathf/src/type_mapping_builder.rs17
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)?;