aboutsummaryrefslogtreecommitdiff
path: root/mingling_pathf/src
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_pathf/src')
-rw-r--r--mingling_pathf/src/pattern_analyzer.rs1
-rw-r--r--mingling_pathf/src/patterns.rs2
-rw-r--r--mingling_pathf/src/patterns/metadata.rs165
3 files changed, 168 insertions, 0 deletions
diff --git a/mingling_pathf/src/pattern_analyzer.rs b/mingling_pathf/src/pattern_analyzer.rs
index 79b1c5a..4675dd1 100644
--- a/mingling_pathf/src/pattern_analyzer.rs
+++ b/mingling_pathf/src/pattern_analyzer.rs
@@ -36,6 +36,7 @@ pub fn init_with_config(config: PathfinderConfig) -> PatternAnalyzer {
analyzer.add_pattern(CommandPattern);
analyzer.add_pattern(RendererPattern);
analyzer.add_pattern(HelpPattern);
+ analyzer.add_pattern(MetadataPattern);
analyzer.add_pattern(CompletionPattern);
analyzer.add_pattern(DispatcherPattern::new(config.use_dispatch_tree));
analyzer.add_pattern(DispatcherClapPattern::new(config.use_dispatch_tree));
diff --git a/mingling_pathf/src/patterns.rs b/mingling_pathf/src/patterns.rs
index 964fe7c..fcc50c3 100644
--- a/mingling_pathf/src/patterns.rs
+++ b/mingling_pathf/src/patterns.rs
@@ -9,6 +9,7 @@ pub use dispatcher_clap::*;
pub use group::*;
pub use grouped_derive::*;
pub use help::*;
+pub use metadata::*;
pub use pack::*;
pub use renderer::*;
@@ -21,5 +22,6 @@ mod dispatcher_clap;
mod group;
mod grouped_derive;
mod help;
+mod metadata;
mod pack;
mod renderer;
diff --git a/mingling_pathf/src/patterns/metadata.rs b/mingling_pathf/src/patterns/metadata.rs
new file mode 100644
index 0000000..24243bf
--- /dev/null
+++ b/mingling_pathf/src/patterns/metadata.rs
@@ -0,0 +1,165 @@
+//! The `MetadataPattern` matches functions annotated with `#[metadata(BindType)]`
+//! and extracts two types referenced by the metadata system:
+//! - `BindType` — the entry enum variant the metadata is bound to (attribute argument)
+//! - `DataType` — the function's return type, i.e. the metadata type
+//!
+//! Both types are tracked so that `pathf` can emit the `use` statements needed to
+//! bring them into scope for `gen_program!` generated code.
+//!
+//! Example:
+//! ```ignore
+//! #[metadata(EntryGreet)] // BindType = EntryGreet
+//! pub fn get_desc() -> Description { ... } // DataType = Description
+//! ```
+
+use std::collections::HashMap;
+
+use syn::Item;
+use syn::UseTree;
+
+use crate::pattern_analyzer::{AnalyzeItem, AnalyzePattern};
+
+/// Matches `#[metadata(BindType)]` functions, extracting the bound entry type
+/// and the metadata (return) type.
+pub struct MetadataPattern;
+
+impl AnalyzePattern for MetadataPattern {
+ fn contains(&self, content: &str) -> bool {
+ content.contains("[metadata(") || content.contains("[metadata]")
+ }
+
+ fn analyze(&self, content: &str) -> Vec<AnalyzeItem> {
+ let Ok(syntax) = syn::parse_file(content) else {
+ return Vec::new();
+ };
+
+ let imports = collect_use_imports(&syntax.items);
+
+ let mut items = Vec::new();
+ for item in &syntax.items {
+ collect_from_item(item, "", &imports, &mut items);
+ }
+ items
+ }
+}
+
+fn collect_from_item(
+ item: &Item,
+ current_mod: &str,
+ imports: &HashMap<String, (String, String)>,
+ items: &mut Vec<AnalyzeItem>,
+) {
+ match item {
+ Item::Fn(f) => {
+ let Some(bind_type) = extract_bind_type(&f.attrs) else {
+ return;
+ };
+ let data_type = extract_data_type(f);
+ let Some(data_type) = data_type else {
+ return;
+ };
+
+ // BindType — always an in-crate entry type generated by dispatcher!/pack!.
+ items.push(AnalyzeItem::local(current_mod.to_string(), bind_type));
+
+ // DataType — may be a local type or a `use`-imported foreign type.
+ if let Some((module, _)) = imports.get(&data_type) {
+ items.push(AnalyzeItem::foreign(module.clone(), data_type));
+ } else {
+ items.push(AnalyzeItem::local(current_mod.to_string(), data_type));
+ }
+ }
+ 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}")
+ };
+ let inner_imports = collect_use_imports(nested);
+ for n in nested {
+ collect_from_item(n, &nested_mod, &inner_imports, items);
+ }
+ }
+ }
+ _ => {}
+ }
+}
+
+/// Extracts the `BindType` (the ident argument of `#[metadata(...)]`).
+fn extract_bind_type(attrs: &[syn::Attribute]) -> Option<String> {
+ for attr in attrs {
+ let path_ident = attr.path().segments.last()?.ident.to_string();
+ if path_ident != "metadata" {
+ continue;
+ }
+ if let syn::Meta::List(meta_list) = &attr.meta {
+ for token in meta_list.tokens.clone().into_iter() {
+ if let proc_macro2::TokenTree::Ident(ident) = token {
+ return Some(ident.to_string());
+ }
+ }
+ }
+ }
+ None
+}
+
+/// Extracts the `DataType` (the function's return type path last segment).
+fn extract_data_type(f: &syn::ItemFn) -> Option<String> {
+ let syn::ReturnType::Type(_, ty) = &f.sig.output else {
+ return None;
+ };
+ match ty.as_ref() {
+ syn::Type::Path(type_path) => type_path.path.segments.last().map(|s| s.ident.to_string()),
+ _ => None,
+ }
+}
+
+/// Collect `use` imports from a list of top-level items.
+///
+/// Returns a map of `short_name → (module_path, short_name)`.
+fn collect_use_imports(items: &[syn::Item]) -> HashMap<String, (String, String)> {
+ let mut map = HashMap::new();
+ for item in items {
+ if let Item::Use(use_item) = item {
+ collect_from_use_tree(&use_item.tree, "", &mut map);
+ }
+ }
+ map
+}
+
+/// Recursively traverse a `UseTree` and collect named imports.
+fn collect_from_use_tree(
+ tree: &UseTree,
+ prefix: &str,
+ map: &mut HashMap<String, (String, String)>,
+) {
+ match tree {
+ UseTree::Name(name) => {
+ let module = prefix.to_string();
+ let alias = name.ident.to_string();
+ map.entry(alias).or_insert((module, name.ident.to_string()));
+ }
+ UseTree::Path(use_path) => {
+ let new_prefix = if prefix.is_empty() {
+ use_path.ident.to_string()
+ } else {
+ format!("{}::{}", prefix, use_path.ident)
+ };
+ collect_from_use_tree(&use_path.tree, &new_prefix, map);
+ }
+ UseTree::Rename(rename) => {
+ let module = prefix.to_string();
+ let alias = rename.ident.to_string();
+ map.entry(alias)
+ .or_insert((module, rename.ident.to_string()));
+ }
+ UseTree::Glob(_) => {}
+ UseTree::Group(group) => {
+ for item in &group.items {
+ collect_from_use_tree(item, prefix, map);
+ }
+ }
+ }
+}