flatten_rust/
exclusions.rs

1//! Модуль для управления логикой исключений.
2//!
3//! `ExclusionManager` является центральным компонентом, который использует
4//! `TemplateManager` для получения шаблонов и применяет их для
5//! определения, какие файлы и папки следует исключить из обработки.
6
7use crate::config::TemplateManager;
8use anyhow::Result;
9use std::collections::HashSet;
10use std::path::Path;
11
12/// Управляет логикой исключения файлов и папок.
13///
14/// Содержит в себе `TemplateManager` для доступа к шаблонам,
15/// а также списки включенных шаблонов и пользовательских паттернов.
16#[derive(Debug)]
17pub struct ExclusionManager {
18    template_manager: TemplateManager,
19    enabled_templates: HashSet<String>,
20}
21
22impl ExclusionManager {
23    /// Создает новый `ExclusionManager` и инициализирует `TemplateManager`.
24    ///
25    /// # Ошибки
26    /// Возвращает ошибку, если не удается инициализировать `TemplateManager`.
27    ///
28    /// # Examples
29    /// ```no_run
30    /// # use flatten_rust::exclusions::ExclusionManager;
31    /// # use anyhow::Result;
32    /// # #[tokio::main]
33    /// # async fn main() -> Result<()> {
34    /// let manager = ExclusionManager::new().await?;
35    /// # Ok(())
36    /// # }
37    /// ```
38    pub async fn new() -> Result<Self> {
39        let mut template_manager = TemplateManager::new()?;
40        template_manager.update_if_needed().await?;
41
42        Ok(Self {
43            template_manager,
44            enabled_templates: HashSet::new(),
45        })
46    }
47    
48    /// Автоматически включает шаблоны, релевантные для указанного проекта.
49    ///
50    /// Определяет тип проекта по наличию характерных файлов (например, `Cargo.toml`).
51    pub async fn enable_templates_for_project(&mut self, project_path: &Path) -> Result<()> {
52        let detection_map = Self::get_detection_map();
53        for (template_key, file_indicators) in detection_map {
54            for indicator in file_indicators {
55                if project_path.join(indicator).exists() {
56                    self.enabled_templates.insert(template_key.to_string());
57                    break;
58                }
59            }
60        }
61        Ok(())
62    }
63
64    /// Возвращает карту для определения типов проектов.
65    fn get_detection_map() -> Vec<(&'static str, Vec<&'static str>)> {
66        vec![
67            ("rust", vec!["Cargo.toml"]),
68            ("node", vec!["package.json"]),
69            ("python", vec!["requirements.txt", "pyproject.toml"]),
70            ("java", vec!["pom.xml", "build.gradle"]),
71            ("go", vec!["go.mod"]),
72            ("ruby", vec!["Gemfile"]),
73            ("php", vec!["composer.json"]),
74        ]
75    }
76    
77    /// Возвращает все паттерны из включенных шаблонов.
78    pub fn get_all_patterns(&self) -> Vec<String> {
79        let mut patterns = Vec::new();
80        for key in &self.enabled_templates {
81            if let Some(contents) = self.template_manager.get_template_contents(key) {
82                patterns.extend(Self::parse_ignore_patterns(contents));
83            }
84        }
85        patterns
86    }
87
88    /// Парсит содержимое шаблона, возвращая список паттернов.
89    fn parse_ignore_patterns(content: &str) -> Vec<String> {
90        content
91            .lines()
92            .map(|s| s.trim())
93            .filter(|s| !s.is_empty() && !s.starts_with('#'))
94            .map(|s| s.to_string())
95            .collect()
96    }
97    
98    /// Возвращает набор паттернов для исключения папок.
99    pub async fn get_folder_patterns(&self) -> HashSet<String> {
100        self.get_all_patterns()
101            .iter()
102            .filter_map(|p| Self::extract_folder_name(p))
103            .collect()
104    }
105
106    /// Возвращает набор паттернов для исключения файлов по расширению.
107    pub async fn get_extension_patterns(&self) -> HashSet<String> {
108        self.get_all_patterns()
109            .iter()
110            .filter_map(|p| Self::extract_extension(p))
111            .collect()
112    }
113
114    /// Извлекает имя папки из паттерна.
115    fn extract_folder_name(pattern: &str) -> Option<String> {
116        let p = pattern.trim_end_matches('/');
117        if !p.contains('*') && !p.contains('.') {
118            return Some(p.to_string());
119        }
120        None
121    }
122
123    /// Извлекает расширение файла из паттерна.
124    fn extract_extension(pattern: &str) -> Option<String> {
125        if pattern.starts_with("*.") {
126            return Some(pattern.trim_start_matches("*.").to_string());
127        }
128        None
129    }
130
131    /// Возвращает список включенных шаблонов.
132    pub fn get_enabled_templates(&self) -> Vec<&str> {
133        self.enabled_templates.iter().map(|s| s.as_str()).collect()
134    }
135
136    /// Включает шаблон по ключу.
137    pub fn enable_template(&mut self, template_key: String) {
138        self.enabled_templates.insert(template_key);
139    }
140
141    /// Отключает шаблон по ключу.
142    pub fn disable_template(&mut self, template_key: &str) {
143        self.enabled_templates.remove(template_key);
144    }
145
146    /// Принудительно обновляет шаблоны через `TemplateManager`.
147    pub async fn force_update_templates(&mut self) -> Result<()> {
148        self.template_manager.force_update().await
149    }
150
151    /// Возвращает список всех доступных шаблонов.
152    pub async fn get_available_templates(&self) -> Vec<String> {
153        self.template_manager.get_available_templates()
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_parse_ignore_patterns() {
163        let content = "
164# Comment
165target/
166*.log
167file.txt
168        ";
169        let patterns = ExclusionManager::parse_ignore_patterns(content);
170        assert_eq!(patterns, vec!["target/", "*.log", "file.txt"]);
171    }
172
173    #[test]
174    fn test_extract_folder_name() {
175        assert_eq!(ExclusionManager::extract_folder_name("target/"), Some("target".to_string()));
176        assert_eq!(ExclusionManager::extract_folder_name("node_modules"), Some("node_modules".to_string()));
177        assert_eq!(ExclusionManager::extract_folder_name("*.log"), None);
178        assert_eq!(ExclusionManager::extract_folder_name("file.txt"), None);
179    }
180
181    #[test]
182    fn test_extract_extension() {
183        assert_eq!(ExclusionManager::extract_extension("*.log"), Some("log".to_string()));
184        assert_eq!(ExclusionManager::extract_extension("*.pyc"), Some("pyc".to_string()));
185        assert_eq!(ExclusionManager::extract_extension("target/"), None);
186        assert_eq!(ExclusionManager::extract_extension("file.txt"), None);
187    }
188}