1use crate::error::{BevyAIError, Result};
4use std::fs;
5use std::path::{Path, PathBuf};
6use std::process::Command;
7use walkdir::WalkDir;
8
9pub mod code_analysis {
11 use super::*;
12 use syn::{File, Item, ItemFn, ItemStruct, ItemEnum, ItemImpl};
13
14 pub struct CodeAnalyzer;
16
17 #[derive(Debug, Clone)]
19 pub struct CodeStructure {
20 pub functions: Vec<FunctionInfo>,
22 pub structs: Vec<StructInfo>,
24 pub enums: Vec<EnumInfo>,
26 pub impls: Vec<ImplInfo>,
28 pub imports: Vec<String>,
30 pub features_used: Vec<String>,
32 }
33
34 #[derive(Debug, Clone)]
36 pub struct FunctionInfo {
37 pub name: String,
39 pub is_public: bool,
41 pub is_async: bool,
43 pub parameters: Vec<String>,
45 pub return_type: Option<String>,
47 pub line_number: usize,
49 }
50
51 #[derive(Debug, Clone)]
53 pub struct StructInfo {
54 pub name: String,
56 pub is_public: bool,
58 pub fields: Vec<String>,
60 pub derives: Vec<String>,
62 pub line_number: usize,
64 }
65
66 #[derive(Debug, Clone)]
68 pub struct EnumInfo {
69 pub name: String,
71 pub is_public: bool,
73 pub variants: Vec<String>,
75 pub derives: Vec<String>,
77 pub line_number: usize,
79 }
80
81 #[derive(Debug, Clone)]
83 pub struct ImplInfo {
84 pub target: String,
86 pub trait_name: Option<String>,
88 pub methods: Vec<String>,
90 pub line_number: usize,
92 }
93
94 impl CodeAnalyzer {
95 pub fn analyze_file<P: AsRef<Path>>(path: P) -> Result<CodeStructure> {
97 let content = fs::read_to_string(&path)?;
98 Self::analyze_code(&content)
99 }
100
101 pub fn analyze_code(code: &str) -> Result<CodeStructure> {
103 let syntax_tree: File = syn::parse_str(code)
104 .map_err(|e| BevyAIError::CodeParsing(format!("Failed to parse Rust code: {}", e)))?;
105
106 let mut structure = CodeStructure {
107 functions: Vec::new(),
108 structs: Vec::new(),
109 enums: Vec::new(),
110 impls: Vec::new(),
111 imports: Vec::new(),
112 features_used: Vec::new(),
113 };
114
115 for item in syntax_tree.items {
116 match item {
117 Item::Fn(func) => {
118 structure.functions.push(Self::analyze_function(&func));
119 }
120 Item::Struct(struct_item) => {
121 structure.structs.push(Self::analyze_struct(&struct_item));
122 }
123 Item::Enum(enum_item) => {
124 structure.enums.push(Self::analyze_enum(&enum_item));
125 }
126 Item::Impl(impl_item) => {
127 structure.impls.push(Self::analyze_impl(&impl_item));
128 }
129 Item::Use(use_item) => {
130 structure.imports.push(quote::quote!(#use_item).to_string());
131 }
132 _ => {}
133 }
134 }
135
136 structure.features_used = Self::detect_bevy_features(code);
138
139 Ok(structure)
140 }
141
142 fn analyze_function(func: &ItemFn) -> FunctionInfo {
143 FunctionInfo {
144 name: func.sig.ident.to_string(),
145 is_public: matches!(func.vis, syn::Visibility::Public(_)),
146 is_async: func.sig.asyncness.is_some(),
147 parameters: func.sig.inputs.iter()
148 .map(|param| quote::quote!(#param).to_string())
149 .collect(),
150 return_type: Self::extract_return_type(func.sig.output.clone()),
151 line_number: 0, }
153 }
154
155 fn analyze_struct(struct_item: &ItemStruct) -> StructInfo {
156 StructInfo {
157 name: struct_item.ident.to_string(),
158 is_public: matches!(struct_item.vis, syn::Visibility::Public(_)),
159 fields: struct_item.fields.iter()
160 .map(|field| {
161 field.ident.as_ref()
162 .map(|i| i.to_string())
163 .unwrap_or_else(|| "unnamed".to_string())
164 })
165 .collect(),
166 derives: struct_item.attrs.iter()
167 .filter_map(|attr| {
168 if attr.path().is_ident("derive") {
169 Some(quote::quote!(#attr).to_string())
170 } else {
171 None
172 }
173 })
174 .collect(),
175 line_number: 0,
176 }
177 }
178
179 fn analyze_enum(enum_item: &ItemEnum) -> EnumInfo {
180 EnumInfo {
181 name: enum_item.ident.to_string(),
182 is_public: matches!(enum_item.vis, syn::Visibility::Public(_)),
183 variants: enum_item.variants.iter()
184 .map(|variant| variant.ident.to_string())
185 .collect(),
186 derives: enum_item.attrs.iter()
187 .filter_map(|attr| {
188 if attr.path().is_ident("derive") {
189 Some(quote::quote!(#attr).to_string())
190 } else {
191 None
192 }
193 })
194 .collect(),
195 line_number: 0,
196 }
197 }
198
199 fn analyze_impl(impl_item: &ItemImpl) -> ImplInfo {
200 ImplInfo {
201 target: quote::quote!(#impl_item.self_ty).to_string(),
202 trait_name: impl_item.trait_.as_ref()
203 .map(|(_, path, _)| quote::quote!(#path).to_string()),
204 methods: impl_item.items.iter()
205 .filter_map(|item| {
206 if let syn::ImplItem::Fn(method) = item {
207 Some(method.sig.ident.to_string())
208 } else {
209 None
210 }
211 })
212 .collect(),
213 line_number: 0,
214 }
215 }
216
217 fn detect_bevy_features(code: &str) -> Vec<String> {
218 let mut features = Vec::new();
219
220 let feature_patterns = [
221 ("2D Rendering", vec!["Camera2dBundle", "Sprite", "SpriteBundle"]),
222 ("3D Rendering", vec!["Camera3dBundle", "PbrBundle", "Mesh"]),
223 ("UI", vec!["ButtonBundle", "TextBundle", "NodeBundle"]),
224 ("Audio", vec!["AudioBundle", "AudioSource"]),
225 ("Physics", vec!["RigidBody", "Collider", "Velocity"]),
226 ("Animation", vec!["AnimationPlayer", "AnimationClip"]),
227 ("Networking", vec!["NetworkEvent", "Connection"]),
228 ("Input", vec!["ButtonInput", "KeyCode", "MouseButton"]),
229 ("ECS", vec!["Component", "Resource", "System"]),
230 ("Transform", vec!["Transform", "GlobalTransform"]),
231 ("Time", vec!["Time", "Timer"]),
232 ("Events", vec!["EventReader", "EventWriter"]),
233 ("Assets", vec!["Assets", "Handle", "AssetServer"]),
234 ("Scene", vec!["Scene", "SceneBundle"]),
235 ("Lighting", vec!["DirectionalLight", "PointLight", "SpotLight"]),
236 ];
237
238 for (feature_name, patterns) in &feature_patterns {
239 for pattern in patterns {
240 if code.contains(pattern) {
241 features.push(feature_name.to_string());
242 break;
243 }
244 }
245 }
246
247 features
248 }
249
250 fn extract_return_type(return_type: syn::ReturnType) -> Option<String> {
252 match return_type {
253 syn::ReturnType::Default => None,
254 syn::ReturnType::Type(_, ty) => Some(quote::quote!(#ty).to_string()),
255 }
256 }
257 }
258}
259
260pub mod fs_utils {
262 use super::*;
263
264 pub fn find_rust_files<P: AsRef<Path>>(dir: P) -> Result<Vec<PathBuf>> {
266 let mut rust_files = Vec::new();
267
268 for entry in WalkDir::new(dir) {
269 let entry = entry?;
270 if entry.path().extension().map_or(false, |ext| ext == "rs") {
271 rust_files.push(entry.path().to_path_buf());
272 }
273 }
274
275 Ok(rust_files)
276 }
277
278 pub fn get_extension<P: AsRef<Path>>(path: P) -> Option<String> {
280 path.as_ref()
281 .extension()
282 .and_then(|ext| ext.to_str())
283 .map(|s| s.to_string())
284 }
285
286 pub fn backup_file<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
288 let path = path.as_ref();
289 let backup_path = path.with_extension(
290 format!("{}.backup.{}",
291 path.extension().unwrap_or_default().to_string_lossy(),
292 chrono::Utc::now().timestamp()
293 )
294 );
295
296 fs::copy(path, &backup_path)?;
297 Ok(backup_path)
298 }
299
300 pub fn calculate_file_hash<P: AsRef<Path>>(path: P) -> Result<String> {
302 let content = fs::read(path)?;
303 Ok(format!("{:x}", md5::compute(&content)))
304 }
305
306 pub fn count_lines<P: AsRef<Path>>(path: P) -> Result<usize> {
308 let content = fs::read_to_string(path)?;
309 Ok(content.lines().count())
310 }
311}
312
313pub mod build_utils {
315 use super::*;
316
317 pub fn check_rust_toolchain() -> Result<String> {
319 let output = Command::new("rustc")
320 .arg("--version")
321 .output()?;
322
323 if output.status.success() {
324 Ok(String::from_utf8_lossy(&output.stdout).to_string())
325 } else {
326 Err(BevyAIError::build_system("Rust toolchain not found".to_string()))
327 }
328 }
329
330 pub fn check_cargo() -> Result<String> {
332 let output = Command::new("cargo")
333 .arg("--version")
334 .output()?;
335
336 if output.status.success() {
337 Ok(String::from_utf8_lossy(&output.stdout).to_string())
338 } else {
339 Err(BevyAIError::build_system("Cargo not found".to_string()))
340 }
341 }
342
343 pub fn cargo_check<P: AsRef<Path>>(project_path: P) -> Result<String> {
345 let output = Command::new("cargo")
346 .arg("check")
347 .current_dir(project_path)
348 .output()?;
349
350 Ok(format!("{}\n{}",
351 String::from_utf8_lossy(&output.stdout),
352 String::from_utf8_lossy(&output.stderr)))
353 }
354
355 pub fn cargo_clippy<P: AsRef<Path>>(project_path: P) -> Result<String> {
357 let output = Command::new("cargo")
358 .args(&["clippy", "--", "-D", "warnings"])
359 .current_dir(project_path)
360 .output()?;
361
362 Ok(format!("{}\n{}",
363 String::from_utf8_lossy(&output.stdout),
364 String::from_utf8_lossy(&output.stderr)))
365 }
366
367 pub fn cargo_fmt<P: AsRef<Path>>(project_path: P) -> Result<String> {
369 let output = Command::new("cargo")
370 .arg("fmt")
371 .current_dir(project_path)
372 .output()?;
373
374 if output.status.success() {
375 Ok("Code formatted successfully".to_string())
376 } else {
377 Err(BevyAIError::build_system(
378 String::from_utf8_lossy(&output.stderr).to_string()
379 ))
380 }
381 }
382}
383
384pub mod text_utils {
386 use super::*;
387
388 pub fn extract_code_blocks(text: &str) -> Vec<(Option<String>, String)> {
390 let mut code_blocks = Vec::new();
391 let lines: Vec<&str> = text.lines().collect();
392 let mut i = 0;
393
394 while i < lines.len() {
395 if lines[i].starts_with("```") {
396 let language = if lines[i].len() > 3 {
397 Some(lines[i][3..].trim().to_string())
398 } else {
399 None
400 };
401
402 let mut code = String::new();
403 i += 1;
404
405 while i < lines.len() && !lines[i].starts_with("```") {
406 code.push_str(lines[i]);
407 code.push('\n');
408 i += 1;
409 }
410
411 code_blocks.push((language, code.trim().to_string()));
412 }
413 i += 1;
414 }
415
416 code_blocks
417 }
418
419 pub fn clean_ai_code(code: &str) -> String {
421 let mut cleaned = code.to_string();
423
424 cleaned = cleaned.lines()
426 .filter(|line| {
427 let trimmed = line.trim();
428 !trimmed.starts_with("// Note:") &&
429 !trimmed.starts_with("// TODO: Add") &&
430 !trimmed.starts_with("// TODO: Implement")
431 })
432 .collect::<Vec<_>>()
433 .join("\n");
434
435 cleaned = cleaned.trim().to_string();
437
438 cleaned
439 }
440
441 pub fn format_rust_code(code: &str) -> Result<String> {
443 use std::io::Write;
444 use std::process::Stdio;
445
446 let mut child = Command::new("rustfmt")
447 .stdin(Stdio::piped())
448 .stdout(Stdio::piped())
449 .stderr(Stdio::piped())
450 .spawn()?;
451
452 if let Some(stdin) = child.stdin.as_mut() {
453 stdin.write_all(code.as_bytes())?;
454 }
455
456 let output = child.wait_with_output()?;
457
458 if output.status.success() {
459 Ok(String::from_utf8_lossy(&output.stdout).to_string())
460 } else {
461 Ok(code.to_string())
463 }
464 }
465
466 pub fn snake_to_pascal(input: &str) -> String {
468 input.split('_')
469 .map(|word| {
470 let mut chars = word.chars();
471 match chars.next() {
472 None => String::new(),
473 Some(first) => first.to_uppercase().collect::<String>() + &chars.as_str().to_lowercase(),
474 }
475 })
476 .collect()
477 }
478
479 pub fn pascal_to_snake(input: &str) -> String {
481 let mut result = String::new();
482 let mut prev_was_upper = false;
483
484 for (i, c) in input.chars().enumerate() {
485 if c.is_uppercase() && i > 0 && !prev_was_upper {
486 result.push('_');
487 }
488 result.push(c.to_lowercase().next().unwrap_or(c));
489 prev_was_upper = c.is_uppercase();
490 }
491
492 result
493 }
494}
495
496pub mod config_utils {
498 use super::*;
499 use crate::config::AIConfig;
500
501 pub fn validate_config(config: &AIConfig) -> Result<Vec<String>> {
503 let mut warnings = Vec::new();
504
505 if config.openai.is_none() && config.anthropic.is_none() && config.google.is_none() {
506 warnings.push("No AI providers configured".to_string());
507 }
508
509 if config.generation.temperature < 0.0 || config.generation.temperature > 2.0 {
510 warnings.push("Temperature should be between 0.0 and 2.0".to_string());
511 }
512
513 if config.generation.max_tokens < 100 {
514 warnings.push("Max tokens is very low, may result in incomplete responses".to_string());
515 }
516
517 Ok(warnings)
518 }
519
520 pub fn get_latest_bevy_version() -> Result<String> {
522 Ok("0.12".to_string())
524 }
525
526 pub fn get_recommended_dependencies(game_type: &str) -> Vec<String> {
528 match game_type.to_lowercase().as_str() {
529 "platformer" | "2d" => vec![
530 "bevy".to_string(),
531 ],
532 "fps" | "3d" | "shooter" => vec![
533 "bevy".to_string(),
534 "bevy_rapier3d".to_string(),
535 ],
536 "physics" => vec![
537 "bevy".to_string(),
538 "bevy_rapier2d".to_string(),
539 "bevy_rapier3d".to_string(),
540 ],
541 "ui" | "menu" => vec![
542 "bevy".to_string(),
543 "bevy_egui".to_string(),
544 ],
545 _ => vec![
546 "bevy".to_string(),
547 ],
548 }
549 }
550}