1use crate::types::Result;
4use serde::{Deserialize, Serialize};
5use std::path::{Path, PathBuf};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Context {
10 pub working_directory: PathBuf,
11 pub project_type: Option<ProjectType>,
12 pub vcs: Option<VcsType>,
13 pub suggestions: Vec<Suggestion>,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18pub enum ProjectType {
19 Rust { has_cargo_toml: bool },
20 Node { has_package_json: bool },
21 Python { has_pyproject: bool, has_requirements: bool },
22 Go { has_go_mod: bool },
23 Java { has_pom: bool, has_gradle: bool },
24 Generic,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29pub enum VcsType {
30 Git,
31 Mercurial,
32 Svn,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct Suggestion {
38 pub command: String,
39 pub description: String,
40 pub category: SuggestionCategory,
41 pub relevance: f32, }
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
46pub enum SuggestionCategory {
47 Navigation,
48 Build,
49 Test,
50 VCS,
51 FileManagement,
52 Development,
53}
54
55pub struct ContextDetector;
57
58impl ContextDetector {
59 pub fn detect(path: &Path) -> Result<Context> {
61 let working_directory = path.to_path_buf();
62 let project_type = Self::detect_project_type(path);
63 let vcs = Self::detect_vcs(path);
64 let suggestions = Self::generate_suggestions(path, &project_type, &vcs);
65
66 Ok(Context {
67 working_directory,
68 project_type,
69 vcs,
70 suggestions,
71 })
72 }
73
74 fn detect_project_type(path: &Path) -> Option<ProjectType> {
76 if path.join("Cargo.toml").exists() {
78 return Some(ProjectType::Rust { has_cargo_toml: true });
79 }
80
81 if path.join("package.json").exists() {
83 return Some(ProjectType::Node { has_package_json: true });
84 }
85
86 let has_pyproject = path.join("pyproject.toml").exists();
88 let has_requirements = path.join("requirements.txt").exists();
89 if has_pyproject || has_requirements {
90 return Some(ProjectType::Python { has_pyproject, has_requirements });
91 }
92
93 if path.join("go.mod").exists() {
95 return Some(ProjectType::Go { has_go_mod: true });
96 }
97
98 let has_pom = path.join("pom.xml").exists();
100 let has_gradle = path.join("build.gradle").exists() || path.join("build.gradle.kts").exists();
101 if has_pom || has_gradle {
102 return Some(ProjectType::Java { has_pom, has_gradle });
103 }
104
105 None
106 }
107
108 fn detect_vcs(path: &Path) -> Option<VcsType> {
110 if path.join(".git").exists() {
111 return Some(VcsType::Git);
112 }
113 if path.join(".hg").exists() {
114 return Some(VcsType::Mercurial);
115 }
116 if path.join(".svn").exists() {
117 return Some(VcsType::Svn);
118 }
119 None
120 }
121
122 fn generate_suggestions(
124 _path: &Path,
125 project_type: &Option<ProjectType>,
126 vcs: &Option<VcsType>,
127 ) -> Vec<Suggestion> {
128 let mut suggestions = Vec::new();
129
130 if let Some(VcsType::Git) = vcs {
132 suggestions.push(Suggestion {
133 command: "git status".to_string(),
134 description: "Check repository status".to_string(),
135 category: SuggestionCategory::VCS,
136 relevance: 0.9,
137 });
138 suggestions.push(Suggestion {
139 command: "git log --oneline -10".to_string(),
140 description: "View recent commits".to_string(),
141 category: SuggestionCategory::VCS,
142 relevance: 0.7,
143 });
144 suggestions.push(Suggestion {
145 command: "git diff".to_string(),
146 description: "See uncommitted changes".to_string(),
147 category: SuggestionCategory::VCS,
148 relevance: 0.8,
149 });
150 }
151
152 match project_type {
154 Some(ProjectType::Rust { .. }) => {
155 suggestions.push(Suggestion {
156 command: "cargo build".to_string(),
157 description: "Build the Rust project".to_string(),
158 category: SuggestionCategory::Build,
159 relevance: 0.95,
160 });
161 suggestions.push(Suggestion {
162 command: "cargo test".to_string(),
163 description: "Run tests".to_string(),
164 category: SuggestionCategory::Test,
165 relevance: 0.85,
166 });
167 suggestions.push(Suggestion {
168 command: "cargo run".to_string(),
169 description: "Build and run the project".to_string(),
170 category: SuggestionCategory::Development,
171 relevance: 0.9,
172 });
173 }
174 Some(ProjectType::Node { .. }) => {
175 suggestions.push(Suggestion {
176 command: "npm install".to_string(),
177 description: "Install dependencies".to_string(),
178 category: SuggestionCategory::Build,
179 relevance: 0.9,
180 });
181 suggestions.push(Suggestion {
182 command: "npm test".to_string(),
183 description: "Run tests".to_string(),
184 category: SuggestionCategory::Test,
185 relevance: 0.85,
186 });
187 suggestions.push(Suggestion {
188 command: "npm start".to_string(),
189 description: "Start the application".to_string(),
190 category: SuggestionCategory::Development,
191 relevance: 0.9,
192 });
193 }
194 Some(ProjectType::Python { .. }) => {
195 suggestions.push(Suggestion {
196 command: "pip install -r requirements.txt".to_string(),
197 description: "Install dependencies".to_string(),
198 category: SuggestionCategory::Build,
199 relevance: 0.9,
200 });
201 suggestions.push(Suggestion {
202 command: "pytest".to_string(),
203 description: "Run tests".to_string(),
204 category: SuggestionCategory::Test,
205 relevance: 0.85,
206 });
207 }
208 Some(ProjectType::Go { .. }) => {
209 suggestions.push(Suggestion {
210 command: "go build".to_string(),
211 description: "Build the Go project".to_string(),
212 category: SuggestionCategory::Build,
213 relevance: 0.95,
214 });
215 suggestions.push(Suggestion {
216 command: "go test ./...".to_string(),
217 description: "Run all tests".to_string(),
218 category: SuggestionCategory::Test,
219 relevance: 0.85,
220 });
221 }
222 _ => {}
223 }
224
225 suggestions.push(Suggestion {
227 command: "ls -la".to_string(),
228 description: "List all files with details".to_string(),
229 category: SuggestionCategory::Navigation,
230 relevance: 0.6,
231 });
232
233 suggestions.sort_by(|a, b| b.relevance.partial_cmp(&a.relevance).unwrap());
235
236 suggestions
237 }
238
239 pub fn is_home_directory(path: &Path) -> bool {
241 if let Some(home) = dirs::home_dir() {
242 path == home
243 } else {
244 false
245 }
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252 use std::fs;
253
254 #[test]
255 fn test_detect_rust_project() {
256 let temp_dir = std::env::temp_dir().join("test_rust_project");
257 fs::create_dir_all(&temp_dir).unwrap();
258 fs::write(temp_dir.join("Cargo.toml"), "").unwrap();
259
260 let project_type = ContextDetector::detect_project_type(&temp_dir);
261 assert!(matches!(project_type, Some(ProjectType::Rust { .. })));
262
263 fs::remove_dir_all(temp_dir).ok();
264 }
265
266 #[test]
267 fn test_generate_suggestions_for_rust() {
268 let temp_dir = std::env::temp_dir().join("test_rust_suggestions");
269 fs::create_dir_all(&temp_dir).unwrap();
270 fs::write(temp_dir.join("Cargo.toml"), "").unwrap();
271
272 let context = ContextDetector::detect(&temp_dir).unwrap();
273 assert!(context.suggestions.iter().any(|s| s.command.contains("cargo")));
274
275 fs::remove_dir_all(temp_dir).ok();
276 }
277}