1mod analyzer;
6mod extractor;
7mod indexer;
8mod provider;
9mod scanner;
10
11pub use analyzer::PatternAnalyzer;
12pub use extractor::ConventionExtractor;
13pub use indexer::CodebaseIndexer;
14pub use provider::ContextProvider;
15pub use scanner::ProjectScanner;
16
17use crate::Result;
18use std::path::Path;
19
20#[derive(Debug, Clone, Default)]
22pub struct ProjectContext {
23 pub languages: Vec<String>,
25 pub frameworks: Vec<String>,
27 pub project_type: Option<String>,
29 pub directories: Vec<String>,
31 pub config_files: Vec<String>,
33 pub naming_conventions: NamingConventions,
35 pub dependencies: Vec<(String, String)>,
37 pub patterns: Vec<String>,
39}
40
41#[derive(Debug, Clone, Default)]
43pub struct NamingConventions {
44 pub functions: Option<NamingStyle>,
46 pub types: Option<NamingStyle>,
48 pub variables: Option<NamingStyle>,
50 pub files: Option<NamingStyle>,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum NamingStyle {
57 SnakeCase,
59 CamelCase,
61 PascalCase,
63 KebabCase,
65 ScreamingSnakeCase,
67}
68
69impl std::fmt::Display for NamingStyle {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 match self {
72 NamingStyle::SnakeCase => write!(f, "snake_case"),
73 NamingStyle::CamelCase => write!(f, "camelCase"),
74 NamingStyle::PascalCase => write!(f, "PascalCase"),
75 NamingStyle::KebabCase => write!(f, "kebab-case"),
76 NamingStyle::ScreamingSnakeCase => write!(f, "SCREAMING_SNAKE_CASE"),
77 }
78 }
79}
80
81pub struct ProjectAnalyzer {
83 scanner: ProjectScanner,
84 extractor: ConventionExtractor,
85}
86
87impl ProjectAnalyzer {
88 pub fn new() -> Self {
90 Self {
91 scanner: ProjectScanner::new(),
92 extractor: ConventionExtractor::new(),
93 }
94 }
95
96 pub fn analyze(&self, path: &Path) -> Result<ProjectContext> {
98 let scan_result = self.scanner.scan(path)?;
100
101 let conventions = self.extractor.extract(&scan_result)?;
103
104 let mut context = ProjectContext {
106 languages: scan_result.languages.clone(),
107 frameworks: scan_result.frameworks.clone(),
108 project_type: scan_result.project_type.clone(),
109 directories: scan_result.key_directories.clone(),
110 config_files: scan_result.config_files.clone(),
111 naming_conventions: conventions,
112 ..Default::default()
113 };
114
115 context.patterns = self.detect_patterns(&scan_result);
117
118 Ok(context)
119 }
120
121 fn detect_patterns(&self, scan_result: &scanner::ScanResult) -> Vec<String> {
122 let mut patterns = Vec::new();
123
124 if scan_result.has_tests {
125 patterns.push("Has test suite".to_string());
126 }
127
128 if scan_result.has_ci {
129 patterns.push("Has CI/CD configuration".to_string());
130 }
131
132 if scan_result.has_docs {
133 patterns.push("Has documentation".to_string());
134 }
135
136 patterns
137 }
138}
139
140impl Default for ProjectAnalyzer {
141 fn default() -> Self {
142 Self::new()
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn test_naming_style_display() {
152 assert_eq!(format!("{}", NamingStyle::SnakeCase), "snake_case");
153 assert_eq!(format!("{}", NamingStyle::CamelCase), "camelCase");
154 assert_eq!(format!("{}", NamingStyle::PascalCase), "PascalCase");
155 }
156
157 #[test]
158 fn test_project_context_default() {
159 let context = ProjectContext::default();
160 assert!(context.languages.is_empty());
161 assert!(context.project_type.is_none());
162 }
163}