aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-06-28 06:21:56 +0800
committer魏曹先生 <1992414357@qq.com>2026-06-28 06:21:56 +0800
commit0f84a88d0b2c9205ec1b3cbfa18ffe05478e5a64 (patch)
tree0d5d3ddff999937599f82e7c0fd1b585d826c9e7
parentda5e1a21fce9a303767af4a6d3cab8f0d66e5c87 (diff)
feat(mingling_pathf): add pattern analyzer module for struct detection
Add a `PatternAnalyzer` with an `AnalyzePattern` trait to detect and extract struct declarations from Rust source files, supporting nested inline modules.
-rw-r--r--Cargo.toml1
-rw-r--r--mingling_pathf/src/lib.rs1
-rw-r--r--mingling_pathf/src/pattern_analyzer.rs140
-rw-r--r--mingling_pathf/src/patterns.rs5
-rw-r--r--mingling_pathf/src/patterns/basic_struct.rs51
-rw-r--r--mingling_pathf/test/src/lib.rs11
6 files changed, 206 insertions, 3 deletions
diff --git a/Cargo.toml b/Cargo.toml
index fedb095..2954f69 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,6 +13,7 @@ exclude = [
# Tests
"mingling_core/tests/*",
+ "mingling_pathf/test"
]
[workspace.dependencies]
diff --git a/mingling_pathf/src/lib.rs b/mingling_pathf/src/lib.rs
index 5e4921b..d8d81db 100644
--- a/mingling_pathf/src/lib.rs
+++ b/mingling_pathf/src/lib.rs
@@ -1,3 +1,4 @@
pub mod module_pathf;
+pub mod pattern_analyzer;
pub mod error;
pub mod patterns;
diff --git a/mingling_pathf/src/pattern_analyzer.rs b/mingling_pathf/src/pattern_analyzer.rs
new file mode 100644
index 0000000..cb98a5f
--- /dev/null
+++ b/mingling_pathf/src/pattern_analyzer.rs
@@ -0,0 +1,140 @@
+use std::collections::HashSet;
+use std::path::Path;
+
+use crate::error::MinglingPathfinderError;
+
+/// Top-level convenience function: creates a default PatternAnalyzer with all built-in patterns pre-registered
+pub fn init() -> PatternAnalyzer {
+ let mut analyzer = PatternAnalyzer::new();
+ macro_rules! __register {
+ ( $($pattern:ident),* $(,)? ) => {
+ $(
+ analyzer.add_pattern(crate::patterns::$pattern);
+ )*
+ };
+ }
+
+ __register![
+ BasicStructPattern,
+ ];
+
+ analyzer
+}
+
+/// A single analysis item representing a parseable importable/referenceable item from code
+#[derive(Debug, Clone)]
+pub struct AnalyzeItem {
+ /// The module path to which the item belongs, e.g. `"std::collections"`; empty string `""` if the item is in the root module
+ pub module: String,
+ /// The name of the item itself, e.g. `"HashMap"`, `"AnalyzeResult"`, etc.
+ pub item_name: String,
+}
+
+/// Collection of analysis results
+#[derive(Debug)]
+pub struct AnalyzeResult {
+ items: Vec<AnalyzeItem>,
+}
+
+impl AnalyzeResult {
+ /// Creates an empty `AnalyzeResult` instance
+ pub fn new() -> Self {
+ Self { items: Vec::new() }
+ }
+
+ /// Formats all items into a set of strings in the format `"::{module_path}::{item_name}"`
+ pub fn into_formatted(self) -> HashSet<String> {
+ self.items
+ .into_iter()
+ .map(|item| {
+ if item.module.is_empty() {
+ format!("::{}", item.item_name)
+ } else {
+ format!("::{}::{}", item.module, item.item_name)
+ }
+ })
+ .collect()
+ }
+}
+
+impl Default for AnalyzeResult {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+/// Extension point trait — one independent implementation per syntax kind
+pub trait AnalyzePattern {
+ /// Quickly determine whether the file content contains an analyzable item
+ fn contains(&self, content: &str) -> bool;
+
+ /// Analyze the content and return all found AnalyzeItems
+ fn analyze(&self, content: &str) -> Vec<AnalyzeItem>;
+}
+
+/// A pattern analyzer that registers and runs multiple `AnalyzePattern` instances to parse
+/// referenceable items from code.
+///
+/// Internally maintains a `Vec<Box<dyn AnalyzePattern>>` for dynamic dispatch, supporting
+/// runtime registration of custom patterns. The `Default` derive provides an empty analyzer
+/// instance.
+///
+/// # Fields
+/// - `patterns`: A list of registered analysis patterns, each responsible for detecting and
+/// extracting a specific type of analyzable item (e.g., structs, functions).
+#[derive(Default)]
+pub struct PatternAnalyzer {
+ /// A list of registered analysis patterns, each responsible for detecting and extracting
+ /// a specific type of analyzable item.
+ patterns: Vec<Box<dyn AnalyzePattern>>,
+}
+
+impl PatternAnalyzer {
+ /// Creates a new `PatternAnalyzer` instance.
+ ///
+ /// Internally calls `Default::default()` directly, initially containing no registered patterns.
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Registers an analysis pattern with the current analyzer.
+ ///
+ /// The pattern is wrapped in a `Box` and stored in the internal pattern list,
+ /// and will be used to scan and analyze file content in subsequent calls to `analyze_file`.
+ ///
+ /// # Parameters
+ /// - `pattern` —— A type instance implementing the `AnalyzePattern` trait,
+ /// responsible for detecting and extracting a specific syntactic structure (e.g., structs, functions).
+ pub fn add_pattern(&mut self, pattern: impl AnalyzePattern + 'static) {
+ self.patterns.push(Box::new(pattern));
+ }
+
+ /// Analyzes a single file and returns a formatted set of strings.
+ ///
+ /// This method reads the content of the file at the specified path, then uses each registered
+ /// pattern to analyze the content in turn. Only patterns whose `contains` method returns `true`
+ /// will trigger the subsequent `analyze` call. All extracted `AnalyzeItem`s are merged and
+ /// converted into a formatted `HashSet<String>`.
+ ///
+ /// # Parameters
+ /// - `path` —— The path to the file to analyze, supporting any type that implements `AsRef<Path>`.
+ ///
+ /// # Returns
+ /// - `Ok(HashSet<String>)` —— On success, returns a formatted set of strings, each in the form `"::module_path::item_name"`.
+ /// - `Err(MinglingPathfinderError)` —— If the file cannot be read, returns the corresponding I/O error wrapper.
+ pub fn analyze_file(&self, path: impl AsRef<Path>) -> Result<HashSet<String>, MinglingPathfinderError> {
+ let path = path.as_ref();
+ let content = std::fs::read_to_string(path)?;
+
+ let mut all_items = Vec::new();
+ for pattern in &self.patterns {
+ if pattern.contains(&content) {
+ let items = pattern.analyze(&content);
+ all_items.extend(items);
+ }
+ }
+
+ let result = AnalyzeResult { items: all_items };
+ Ok(result.into_formatted())
+ }
+}
diff --git a/mingling_pathf/src/patterns.rs b/mingling_pathf/src/patterns.rs
index 9aa5502..33d3503 100644
--- a/mingling_pathf/src/patterns.rs
+++ b/mingling_pathf/src/patterns.rs
@@ -1,3 +1,2 @@
-pub trait AnalyzePattern {
-
-}
+mod basic_struct;
+pub use basic_struct::*;
diff --git a/mingling_pathf/src/patterns/basic_struct.rs b/mingling_pathf/src/patterns/basic_struct.rs
new file mode 100644
index 0000000..eeb665a
--- /dev/null
+++ b/mingling_pathf/src/patterns/basic_struct.rs
@@ -0,0 +1,51 @@
+use syn::Item;
+
+use crate::pattern_analyzer::{AnalyzeItem, AnalyzePattern};
+
+/// Basic struct pattern analyzer.
+///
+/// Used to identify and analyze struct definitions (`struct`) in Rust source code.
+/// Supports analyzing root-level structs as well as structs within inline modules.
+pub struct BasicStructPattern;
+
+impl AnalyzePattern for BasicStructPattern {
+ fn contains(&self, content: &str) -> bool {
+ content.contains("struct")
+ }
+
+ 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 {
+ match item {
+ // Root-level struct
+ Item::Struct(s) => {
+ items.push(AnalyzeItem {
+ module: String::new(),
+ item_name: s.ident.to_string(),
+ });
+ }
+ // Struct within inline modules
+ Item::Mod(item_mod) => {
+ if let Some((_, nested)) = &item_mod.content {
+ for n in nested {
+ if let syn::Item::Struct(s) = n {
+ items.push(AnalyzeItem {
+ module: item_mod.ident.to_string(),
+ item_name: s.ident.to_string(),
+ });
+ }
+ }
+ }
+ }
+ _ => {}
+ }
+ }
+
+ items
+ }
+}
diff --git a/mingling_pathf/test/src/lib.rs b/mingling_pathf/test/src/lib.rs
index 742e9d0..dbbd76d 100644
--- a/mingling_pathf/test/src/lib.rs
+++ b/mingling_pathf/test/src/lib.rs
@@ -37,3 +37,14 @@ fn test_module_pathf() {
assert_eq!(mapping.get("src/use_all.rs").unwrap(), "crate");
assert_eq!(mapping.get("src/main.rs").unwrap(), "crate");
}
+
+#[test]
+fn test_pattern_analyzer() {
+ let dir = current_dir().unwrap().join("test_proj");
+ let mut analyzer = mingling_pathf::pattern_analyzer::init();
+ analyzer.add_pattern(mingling_pathf::patterns::BasicStructPattern);
+
+ let result = analyzer.analyze_file(dir.join("src/has_sub_mod.rs")).unwrap();
+
+ assert!(result.contains("::directly_sub_mod::DirectlySubModStruct"));
+}