git_iris/file_analyzers/
mod.rs1use regex::Regex;
2use std::path::Path;
3
4use crate::{
5 context::{ProjectMetadata, StagedFile},
6 log_debug,
7};
8
9pub trait FileAnalyzer: Send + Sync {
11 fn analyze(&self, file: &str, staged_file: &StagedFile) -> Vec<String>;
12 fn get_file_type(&self) -> &'static str;
13 fn extract_metadata(&self, file: &str, content: &str) -> ProjectMetadata;
14}
15
16mod c;
18mod cpp;
20mod gradle;
22mod java;
24mod javascript;
26mod json;
28mod kotlin;
30mod markdown;
32mod python;
34mod rust;
36mod yaml;
38
39pub fn get_analyzer(file: &str) -> Box<dyn FileAnalyzer + Send + Sync> {
41 let file_lower = file.to_lowercase();
42 let path = std::path::Path::new(&file_lower);
43
44 if file == "Makefile" {
46 return Box::new(c::CAnalyzer);
47 } else if file == "CMakeLists.txt" {
48 return Box::new(cpp::CppAnalyzer);
49 }
50
51 if file_lower.ends_with(".gradle") || file_lower.ends_with(".gradle.kts") {
53 return Box::new(gradle::GradleAnalyzer);
54 }
55
56 if let Some(ext) = path.extension() {
58 if let Some(ext_str) = ext.to_str() {
59 let ext_lower = ext_str.to_lowercase();
60 match ext_lower.as_str() {
61 "c" => return Box::new(c::CAnalyzer),
62 "cpp" | "cc" | "cxx" => return Box::new(cpp::CppAnalyzer),
63 "rs" => return Box::new(rust::RustAnalyzer),
64 "py" => return Box::new(python::PythonAnalyzer),
65 "js" | "jsx" | "ts" | "tsx" => return Box::new(javascript::JavaScriptAnalyzer),
66 "java" => return Box::new(java::JavaAnalyzer),
67 "kt" | "kts" => return Box::new(kotlin::KotlinAnalyzer),
68 "json" => return Box::new(json::JsonAnalyzer),
69 "md" | "markdown" => return Box::new(markdown::MarkdownAnalyzer),
70 "yaml" | "yml" => return Box::new(yaml::YamlAnalyzer),
71 _ => {}
72 }
73 }
74 }
75
76 Box::new(DefaultAnalyzer)
77}
78
79struct DefaultAnalyzer;
81
82impl FileAnalyzer for DefaultAnalyzer {
83 fn analyze(&self, _file: &str, _staged_file: &StagedFile) -> Vec<String> {
84 vec![]
85 }
86
87 fn get_file_type(&self) -> &'static str {
88 "Unknown file type"
89 }
90
91 fn extract_metadata(&self, _file: &str, _content: &str) -> ProjectMetadata {
92 ProjectMetadata {
93 language: Some("Unknown".to_string()),
94 ..Default::default()
95 }
96 }
97}
98
99pub fn should_exclude_file(path: &str) -> bool {
109 log_debug!("Checking if file should be excluded: {}", path);
110 let exclude_patterns = vec![
111 (String::from(r"\.git"), false),
112 (String::from(r"\.svn"), false),
113 (String::from(r"\.hg"), false),
114 (String::from(r"\.DS_Store"), false),
115 (String::from(r"node_modules"), false),
116 (String::from(r"target"), false),
117 (String::from(r"build"), false),
118 (String::from(r"dist"), false),
119 (String::from(r"\.vscode"), false),
120 (String::from(r"\.idea"), false),
121 (String::from(r"\.vs"), false),
122 (String::from(r"package-lock\.json$"), true),
123 (String::from(r"\.lock$"), true),
124 (String::from(r"\.log$"), true),
125 (String::from(r"\.tmp$"), true),
126 (String::from(r"\.temp$"), true),
127 (String::from(r"\.swp$"), true),
128 (String::from(r"\.min\.js$"), true),
129 ];
130
131 let path = Path::new(path);
132
133 for (pattern, is_extension) in exclude_patterns {
134 let re = match Regex::new(&pattern) {
135 Ok(re) => re,
136 Err(e) => {
137 log_debug!("Failed to compile regex '{}': {}", pattern, e);
138 continue;
139 }
140 };
141
142 if is_extension {
143 if let Some(file_name) = path.file_name() {
144 if let Some(file_name_str) = file_name.to_str() {
145 if re.is_match(file_name_str) {
146 log_debug!("File excluded: {}", path.display());
147 return true;
148 }
149 }
150 }
151 } else if let Some(path_str) = path.to_str() {
152 if re.is_match(path_str) {
153 log_debug!("File excluded: {}", path.display());
154 return true;
155 }
156 }
157 }
158 log_debug!("File not excluded: {}", path.display());
159 false
160}