use crate::config::TemplateManager;
use anyhow::Result;
use std::collections::HashSet;
use std::path::Path;
#[derive(Debug)]
pub struct ExclusionManager {
template_manager: TemplateManager,
enabled_templates: HashSet<String>,
}
impl ExclusionManager {
pub async fn new() -> Result<Self> {
let mut template_manager = TemplateManager::new()?;
template_manager.update_if_needed().await?;
Ok(Self {
template_manager,
enabled_templates: HashSet::new(),
})
}
pub async fn enable_templates_for_project(&mut self, project_path: &Path) -> Result<()> {
let detection_map = Self::get_detection_map();
for (template_key, file_indicators) in detection_map {
for indicator in file_indicators {
if project_path.join(indicator).exists() {
self.enabled_templates.insert(template_key.to_string());
break;
}
}
}
Ok(())
}
fn get_detection_map() -> Vec<(&'static str, Vec<&'static str>)> {
vec![
("rust", vec!["Cargo.toml"]),
("node", vec!["package.json"]),
("python", vec!["requirements.txt", "pyproject.toml"]),
("java", vec!["pom.xml", "build.gradle"]),
("go", vec!["go.mod"]),
("ruby", vec!["Gemfile"]),
("php", vec!["composer.json"]),
]
}
pub fn get_all_patterns(&self) -> Vec<String> {
let mut patterns = Vec::new();
for key in &self.enabled_templates {
if let Some(contents) = self.template_manager.get_template_contents(key) {
patterns.extend(Self::parse_ignore_patterns(contents));
}
}
patterns
}
fn parse_ignore_patterns(content: &str) -> Vec<String> {
content
.lines()
.map(|s| s.trim())
.filter(|s| !s.is_empty() && !s.starts_with('#'))
.map(|s| s.to_string())
.collect()
}
pub async fn get_folder_patterns(&self) -> HashSet<String> {
self.get_all_patterns()
.iter()
.filter_map(|p| Self::extract_folder_name(p))
.collect()
}
pub async fn get_extension_patterns(&self) -> HashSet<String> {
self.get_all_patterns()
.iter()
.filter_map(|p| Self::extract_extension(p))
.collect()
}
fn extract_folder_name(pattern: &str) -> Option<String> {
let p = pattern.trim_end_matches('/');
if !p.contains('*') && !p.contains('.') {
return Some(p.to_string());
}
None
}
fn extract_extension(pattern: &str) -> Option<String> {
if pattern.starts_with("*.") {
return Some(pattern.trim_start_matches("*.").to_string());
}
None
}
pub fn get_enabled_templates(&self) -> Vec<&str> {
self.enabled_templates.iter().map(|s| s.as_str()).collect()
}
pub fn enable_template(&mut self, template_key: String) {
self.enabled_templates.insert(template_key);
}
pub fn disable_template(&mut self, template_key: &str) {
self.enabled_templates.remove(template_key);
}
pub async fn force_update_templates(&mut self) -> Result<()> {
self.template_manager.force_update().await
}
pub async fn get_available_templates(&self) -> Vec<String> {
self.template_manager.get_available_templates()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_ignore_patterns() {
let content = "
# Comment
target/
*.log
file.txt
";
let patterns = ExclusionManager::parse_ignore_patterns(content);
assert_eq!(patterns, vec!["target/", "*.log", "file.txt"]);
}
#[test]
fn test_extract_folder_name() {
assert_eq!(ExclusionManager::extract_folder_name("target/"), Some("target".to_string()));
assert_eq!(ExclusionManager::extract_folder_name("node_modules"), Some("node_modules".to_string()));
assert_eq!(ExclusionManager::extract_folder_name("*.log"), None);
assert_eq!(ExclusionManager::extract_folder_name("file.txt"), None);
}
#[test]
fn test_extract_extension() {
assert_eq!(ExclusionManager::extract_extension("*.log"), Some("log".to_string()));
assert_eq!(ExclusionManager::extract_extension("*.pyc"), Some("pyc".to_string()));
assert_eq!(ExclusionManager::extract_extension("target/"), None);
assert_eq!(ExclusionManager::extract_extension("file.txt"), None);
}
}