1use serde::{Deserialize, Serialize};
2use std::path::PathBuf;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
6pub enum WorkflowPhase {
7 Analysis,
8 Transpilation,
9 Optimization,
10 Validation,
11 Deployment,
12}
13
14impl WorkflowPhase {
15 pub fn next(&self) -> Option<WorkflowPhase> {
17 match self {
18 WorkflowPhase::Analysis => Some(WorkflowPhase::Transpilation),
19 WorkflowPhase::Transpilation => Some(WorkflowPhase::Optimization),
20 WorkflowPhase::Optimization => Some(WorkflowPhase::Validation),
21 WorkflowPhase::Validation => Some(WorkflowPhase::Deployment),
22 WorkflowPhase::Deployment => None,
23 }
24 }
25
26 pub fn all() -> Vec<WorkflowPhase> {
28 vec![
29 WorkflowPhase::Analysis,
30 WorkflowPhase::Transpilation,
31 WorkflowPhase::Optimization,
32 WorkflowPhase::Validation,
33 WorkflowPhase::Deployment,
34 ]
35 }
36}
37
38impl std::fmt::Display for WorkflowPhase {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 match self {
41 WorkflowPhase::Analysis => write!(f, "Analysis"),
42 WorkflowPhase::Transpilation => write!(f, "Transpilation"),
43 WorkflowPhase::Optimization => write!(f, "Optimization"),
44 WorkflowPhase::Validation => write!(f, "Validation"),
45 WorkflowPhase::Deployment => write!(f, "Deployment"),
46 }
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
52pub enum PhaseStatus {
53 NotStarted,
54 InProgress,
55 Completed,
56 Failed,
57}
58
59impl std::fmt::Display for PhaseStatus {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 match self {
62 PhaseStatus::NotStarted => write!(f, "Not Started"),
63 PhaseStatus::InProgress => write!(f, "In Progress"),
64 PhaseStatus::Completed => write!(f, "Completed"),
65 PhaseStatus::Failed => write!(f, "Failed"),
66 }
67 }
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct PhaseInfo {
73 pub phase: WorkflowPhase,
74 pub status: PhaseStatus,
75 pub started_at: Option<chrono::DateTime<chrono::Utc>>,
76 pub completed_at: Option<chrono::DateTime<chrono::Utc>>,
77 pub error: Option<String>,
78}
79
80impl PhaseInfo {
81 pub fn new(phase: WorkflowPhase) -> Self {
82 Self {
83 phase,
84 status: PhaseStatus::NotStarted,
85 started_at: None,
86 completed_at: None,
87 error: None,
88 }
89 }
90
91 pub fn start(&mut self) {
92 self.status = PhaseStatus::InProgress;
93 self.started_at = Some(chrono::Utc::now());
94 }
95
96 pub fn complete(&mut self) {
97 self.status = PhaseStatus::Completed;
98 self.completed_at = Some(chrono::Utc::now());
99 }
100
101 pub fn fail(&mut self, error: String) {
102 self.status = PhaseStatus::Failed;
103 self.error = Some(error);
104 self.completed_at = Some(chrono::Utc::now());
105 }
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct WorkflowState {
111 pub current_phase: Option<WorkflowPhase>,
112 pub phases: std::collections::HashMap<WorkflowPhase, PhaseInfo>,
113}
114
115impl Default for WorkflowState {
116 fn default() -> Self {
117 Self::new()
118 }
119}
120
121impl WorkflowState {
122 pub fn new() -> Self {
123 let mut phases = std::collections::HashMap::new();
124 for phase in WorkflowPhase::all() {
125 phases.insert(phase, PhaseInfo::new(phase));
126 }
127
128 Self { current_phase: None, phases }
129 }
130
131 pub fn load(path: &std::path::Path) -> anyhow::Result<Self> {
133 if !path.exists() {
134 return Ok(Self::new());
135 }
136
137 let content = std::fs::read_to_string(path)?;
138 let state = serde_json::from_str(&content)?;
139 Ok(state)
140 }
141
142 pub fn save(&self, path: &std::path::Path) -> anyhow::Result<()> {
144 let content = serde_json::to_string_pretty(self)?;
145 std::fs::write(path, content)?;
146 Ok(())
147 }
148
149 pub fn start_phase(&mut self, phase: WorkflowPhase) {
151 self.current_phase = Some(phase);
152 if let Some(info) = self.phases.get_mut(&phase) {
153 info.start();
154 }
155 }
156
157 pub fn complete_phase(&mut self, phase: WorkflowPhase) {
159 if let Some(info) = self.phases.get_mut(&phase) {
160 info.complete();
161 }
162
163 if let Some(next) = phase.next() {
165 self.current_phase = Some(next);
166 } else {
167 self.current_phase = None;
168 }
169 }
170
171 pub fn fail_phase(&mut self, phase: WorkflowPhase, error: String) {
173 if let Some(info) = self.phases.get_mut(&phase) {
174 info.fail(error);
175 }
176 self.current_phase = Some(phase);
177 }
178
179 pub fn get_phase_status(&self, phase: WorkflowPhase) -> PhaseStatus {
181 self.phases.get(&phase).map(|info| info.status).unwrap_or(PhaseStatus::NotStarted)
182 }
183
184 pub fn is_phase_completed(&self, phase: WorkflowPhase) -> bool {
186 self.get_phase_status(phase) == PhaseStatus::Completed
187 }
188
189 pub fn progress_percentage(&self) -> f64 {
191 let total = WorkflowPhase::all().len() as f64;
192 let completed =
193 self.phases.values().filter(|info| info.status == PhaseStatus::Completed).count()
194 as f64;
195
196 (completed / total) * 100.0
197 }
198}
199
200#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
202pub enum Language {
203 Python,
204 C,
205 Cpp,
206 Rust,
207 Shell,
208 JavaScript,
209 TypeScript,
210 Go,
211 Java,
212 Other(String),
213}
214
215impl std::fmt::Display for Language {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 match self {
218 Language::Python => write!(f, "Python"),
219 Language::C => write!(f, "C"),
220 Language::Cpp => write!(f, "C++"),
221 Language::Rust => write!(f, "Rust"),
222 Language::Shell => write!(f, "Shell"),
223 Language::JavaScript => write!(f, "JavaScript"),
224 Language::TypeScript => write!(f, "TypeScript"),
225 Language::Go => write!(f, "Go"),
226 Language::Java => write!(f, "Java"),
227 Language::Other(name) => write!(f, "{}", name),
228 }
229 }
230}
231
232impl std::str::FromStr for Language {
233 type Err = std::convert::Infallible;
234
235 fn from_str(s: &str) -> Result<Self, Self::Err> {
236 Ok(match s.to_lowercase().as_str() {
237 "python" => Language::Python,
238 "c" => Language::C,
239 "c++" | "cpp" => Language::Cpp,
240 "rust" => Language::Rust,
241 "shell" | "bash" | "sh" => Language::Shell,
242 "javascript" | "js" => Language::JavaScript,
243 "typescript" | "ts" => Language::TypeScript,
244 "go" | "golang" => Language::Go,
245 "java" => Language::Java,
246 other => Language::Other(other.to_string()),
247 })
248 }
249}
250
251#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
253pub enum DependencyManager {
254 Pip, Pipenv, Poetry, Conda, Cargo, Npm, Yarn, GoMod, Maven, Gradle, Make, }
266
267impl std::fmt::Display for DependencyManager {
268 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269 match self {
270 DependencyManager::Pip => write!(f, "pip (requirements.txt)"),
271 DependencyManager::Pipenv => write!(f, "Pipenv"),
272 DependencyManager::Poetry => write!(f, "Poetry"),
273 DependencyManager::Conda => write!(f, "Conda"),
274 DependencyManager::Cargo => write!(f, "Cargo"),
275 DependencyManager::Npm => write!(f, "npm"),
276 DependencyManager::Yarn => write!(f, "Yarn"),
277 DependencyManager::GoMod => write!(f, "Go modules"),
278 DependencyManager::Maven => write!(f, "Maven"),
279 DependencyManager::Gradle => write!(f, "Gradle"),
280 DependencyManager::Make => write!(f, "Make"),
281 }
282 }
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct DependencyInfo {
288 pub manager: DependencyManager,
289 pub file_path: PathBuf,
290 pub count: Option<usize>,
291}
292
293#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct LanguageStats {
296 pub language: Language,
297 pub file_count: usize,
298 pub line_count: usize,
299 pub percentage: f64,
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct ProjectAnalysis {
305 pub root_path: PathBuf,
306 pub languages: Vec<LanguageStats>,
307 pub primary_language: Option<Language>,
308 pub dependencies: Vec<DependencyInfo>,
309 pub total_files: usize,
310 pub total_lines: usize,
311 pub tdg_score: Option<f64>,
312}
313
314impl ProjectAnalysis {
315 pub fn new(root_path: PathBuf) -> Self {
316 Self {
317 root_path,
318 languages: Vec::new(),
319 primary_language: None,
320 dependencies: Vec::new(),
321 total_files: 0,
322 total_lines: 0,
323 tdg_score: None,
324 }
325 }
326
327 pub fn recommend_transpiler(&self) -> Option<&'static str> {
328 contract_pre_transpile!(self);
329 match self.primary_language.as_ref()? {
330 Language::Python => Some("Depyler (Python → Rust)"),
331 Language::C | Language::Cpp => Some("Decy (C/C++ → Rust)"),
332 Language::Shell => Some("Bashrs (Shell → Rust)"),
333 Language::Rust => Some("Already Rust! Consider Ruchy for gradual typing."),
334 _ => None,
335 }
336 }
337
338 pub fn has_ml_dependencies(&self) -> bool {
339 self.dependencies.iter().any(|dep| {
341 matches!(
342 dep.manager,
343 DependencyManager::Pip | DependencyManager::Conda | DependencyManager::Poetry
344 )
345 })
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
358 fn test_workflow_phase_next() {
359 assert_eq!(WorkflowPhase::Analysis.next(), Some(WorkflowPhase::Transpilation));
360 assert_eq!(WorkflowPhase::Transpilation.next(), Some(WorkflowPhase::Optimization));
361 assert_eq!(WorkflowPhase::Optimization.next(), Some(WorkflowPhase::Validation));
362 assert_eq!(WorkflowPhase::Validation.next(), Some(WorkflowPhase::Deployment));
363 assert_eq!(WorkflowPhase::Deployment.next(), None);
364 }
365
366 #[test]
367 fn test_workflow_phase_all() {
368 let phases = WorkflowPhase::all();
369 assert_eq!(phases.len(), 5);
370 assert_eq!(phases[0], WorkflowPhase::Analysis);
371 assert_eq!(phases[1], WorkflowPhase::Transpilation);
372 assert_eq!(phases[2], WorkflowPhase::Optimization);
373 assert_eq!(phases[3], WorkflowPhase::Validation);
374 assert_eq!(phases[4], WorkflowPhase::Deployment);
375 }
376
377 #[test]
378 fn test_workflow_phase_display() {
379 assert_eq!(WorkflowPhase::Analysis.to_string(), "Analysis");
380 assert_eq!(WorkflowPhase::Transpilation.to_string(), "Transpilation");
381 assert_eq!(WorkflowPhase::Optimization.to_string(), "Optimization");
382 assert_eq!(WorkflowPhase::Validation.to_string(), "Validation");
383 assert_eq!(WorkflowPhase::Deployment.to_string(), "Deployment");
384 }
385
386 #[test]
387 fn test_workflow_phase_serialization() {
388 let phase = WorkflowPhase::Analysis;
389 let json = serde_json::to_string(&phase).expect("json serialize failed");
390 let deserialized: WorkflowPhase =
391 serde_json::from_str(&json).expect("json deserialize failed");
392 assert_eq!(phase, deserialized);
393 }
394
395 #[test]
400 fn test_phase_status_display() {
401 assert_eq!(PhaseStatus::NotStarted.to_string(), "Not Started");
402 assert_eq!(PhaseStatus::InProgress.to_string(), "In Progress");
403 assert_eq!(PhaseStatus::Completed.to_string(), "Completed");
404 assert_eq!(PhaseStatus::Failed.to_string(), "Failed");
405 }
406
407 #[test]
408 fn test_phase_status_equality() {
409 assert_eq!(PhaseStatus::NotStarted, PhaseStatus::NotStarted);
410 assert_eq!(PhaseStatus::Completed, PhaseStatus::Completed);
411 assert_ne!(PhaseStatus::NotStarted, PhaseStatus::Completed);
412 assert_ne!(PhaseStatus::InProgress, PhaseStatus::Failed);
413 }
414
415 #[test]
416 fn test_phase_status_serialization() {
417 let status = PhaseStatus::Completed;
418 let json = serde_json::to_string(&status).expect("json serialize failed");
419 let deserialized: PhaseStatus =
420 serde_json::from_str(&json).expect("json deserialize failed");
421 assert_eq!(status, deserialized);
422 }
423
424 #[test]
429 fn test_phase_info_new() {
430 let info = PhaseInfo::new(WorkflowPhase::Analysis);
431 assert_eq!(info.phase, WorkflowPhase::Analysis);
432 assert_eq!(info.status, PhaseStatus::NotStarted);
433 assert!(info.started_at.is_none());
434 assert!(info.completed_at.is_none());
435 assert!(info.error.is_none());
436 }
437
438 #[test]
439 fn test_phase_info_start() {
440 let mut info = PhaseInfo::new(WorkflowPhase::Analysis);
441 info.start();
442 assert_eq!(info.status, PhaseStatus::InProgress);
443 assert!(info.started_at.is_some());
444 assert!(info.completed_at.is_none());
445 }
446
447 #[test]
448 fn test_phase_info_complete() {
449 let mut info = PhaseInfo::new(WorkflowPhase::Analysis);
450 info.start();
451 info.complete();
452 assert_eq!(info.status, PhaseStatus::Completed);
453 assert!(info.started_at.is_some());
454 assert!(info.completed_at.is_some());
455 assert!(info.error.is_none());
456 }
457
458 #[test]
459 fn test_phase_info_fail() {
460 let mut info = PhaseInfo::new(WorkflowPhase::Analysis);
461 info.start();
462 info.fail("Test error".to_string());
463 assert_eq!(info.status, PhaseStatus::Failed);
464 assert_eq!(info.error.as_deref(), Some("Test error"));
465 assert!(info.completed_at.is_some());
466 }
467
468 #[test]
469 fn test_phase_info_serialization() {
470 let info = PhaseInfo::new(WorkflowPhase::Transpilation);
471 let json = serde_json::to_string(&info).expect("json serialize failed");
472 let deserialized: PhaseInfo = serde_json::from_str(&json).expect("json deserialize failed");
473 assert_eq!(info.phase, deserialized.phase);
474 assert_eq!(info.status, deserialized.status);
475 }
476
477 #[test]
482 fn test_workflow_state_new() {
483 let state = WorkflowState::new();
484 assert!(state.current_phase.is_none());
485 assert_eq!(state.phases.len(), 5);
486
487 for phase in WorkflowPhase::all() {
488 assert!(state.phases.contains_key(&phase));
489 assert_eq!(state.get_phase_status(phase), PhaseStatus::NotStarted);
490 }
491 }
492
493 #[test]
494 fn test_workflow_state_default() {
495 let state = WorkflowState::default();
496 assert!(state.current_phase.is_none());
497 assert_eq!(state.phases.len(), 5);
498 }
499
500 #[test]
501 fn test_workflow_state_start_phase() {
502 let mut state = WorkflowState::new();
503 state.start_phase(WorkflowPhase::Analysis);
504
505 assert_eq!(state.current_phase, Some(WorkflowPhase::Analysis));
506 assert_eq!(state.get_phase_status(WorkflowPhase::Analysis), PhaseStatus::InProgress);
507 }
508
509 #[test]
510 fn test_workflow_state_complete_phase() {
511 let mut state = WorkflowState::new();
512 state.start_phase(WorkflowPhase::Analysis);
513 state.complete_phase(WorkflowPhase::Analysis);
514
515 assert_eq!(state.get_phase_status(WorkflowPhase::Analysis), PhaseStatus::Completed);
516 assert_eq!(state.current_phase, Some(WorkflowPhase::Transpilation));
518 }
519
520 #[test]
521 fn test_workflow_state_complete_final_phase() {
522 let mut state = WorkflowState::new();
523 state.start_phase(WorkflowPhase::Deployment);
524 state.complete_phase(WorkflowPhase::Deployment);
525
526 assert_eq!(state.get_phase_status(WorkflowPhase::Deployment), PhaseStatus::Completed);
527 assert!(state.current_phase.is_none());
529 }
530
531 #[test]
532 fn test_workflow_state_fail_phase() {
533 let mut state = WorkflowState::new();
534 state.start_phase(WorkflowPhase::Analysis);
535 state.fail_phase(WorkflowPhase::Analysis, "Analysis failed".to_string());
536
537 assert_eq!(state.get_phase_status(WorkflowPhase::Analysis), PhaseStatus::Failed);
538 assert_eq!(state.current_phase, Some(WorkflowPhase::Analysis));
539
540 let phase_info = state.phases.get(&WorkflowPhase::Analysis).expect("key not found");
541 assert_eq!(phase_info.error.as_deref(), Some("Analysis failed"));
542 }
543
544 #[test]
545 fn test_workflow_state_is_phase_completed() {
546 let mut state = WorkflowState::new();
547 assert!(!state.is_phase_completed(WorkflowPhase::Analysis));
548
549 state.start_phase(WorkflowPhase::Analysis);
550 assert!(!state.is_phase_completed(WorkflowPhase::Analysis));
551
552 state.complete_phase(WorkflowPhase::Analysis);
553 assert!(state.is_phase_completed(WorkflowPhase::Analysis));
554 }
555
556 #[test]
557 fn test_workflow_state_progress_percentage() {
558 let mut state = WorkflowState::new();
559 assert_eq!(state.progress_percentage(), 0.0);
560
561 state.start_phase(WorkflowPhase::Analysis);
562 state.complete_phase(WorkflowPhase::Analysis);
563 assert_eq!(state.progress_percentage(), 20.0); state.start_phase(WorkflowPhase::Transpilation);
566 state.complete_phase(WorkflowPhase::Transpilation);
567 assert_eq!(state.progress_percentage(), 40.0); }
569
570 #[test]
571 fn test_workflow_state_save_and_load() {
572 use tempfile::TempDir;
573
574 let temp_dir = TempDir::new().expect("tempdir creation failed");
575 let state_path = temp_dir.path().join("workflow-state.json");
576
577 let mut state = WorkflowState::new();
578 state.start_phase(WorkflowPhase::Analysis);
579 state.complete_phase(WorkflowPhase::Analysis);
580
581 state.save(&state_path).expect("save failed");
582 assert!(state_path.exists());
583
584 let loaded_state = WorkflowState::load(&state_path).expect("unexpected failure");
585 assert_eq!(loaded_state.current_phase, state.current_phase);
586 assert_eq!(loaded_state.get_phase_status(WorkflowPhase::Analysis), PhaseStatus::Completed);
587 }
588
589 #[test]
590 fn test_workflow_state_load_nonexistent() {
591 use tempfile::TempDir;
592
593 let temp_dir = TempDir::new().expect("tempdir creation failed");
594 let state_path = temp_dir.path().join("nonexistent.json");
595
596 let state = WorkflowState::load(&state_path).expect("unexpected failure");
597 assert!(state.current_phase.is_none());
598 assert_eq!(state.progress_percentage(), 0.0);
599 }
600
601 #[test]
606 fn test_language_display() {
607 assert_eq!(Language::Python.to_string(), "Python");
608 assert_eq!(Language::C.to_string(), "C");
609 assert_eq!(Language::Cpp.to_string(), "C++");
610 assert_eq!(Language::Rust.to_string(), "Rust");
611 assert_eq!(Language::Shell.to_string(), "Shell");
612 assert_eq!(Language::JavaScript.to_string(), "JavaScript");
613 assert_eq!(Language::TypeScript.to_string(), "TypeScript");
614 assert_eq!(Language::Go.to_string(), "Go");
615 assert_eq!(Language::Java.to_string(), "Java");
616 assert_eq!(Language::Other("Ruby".to_string()).to_string(), "Ruby");
617 }
618
619 #[test]
620 fn test_language_equality() {
621 assert_eq!(Language::Python, Language::Python);
622 assert_ne!(Language::Python, Language::Rust);
623 assert_eq!(Language::Other("Kotlin".to_string()), Language::Other("Kotlin".to_string()));
624 }
625
626 #[test]
627 fn test_language_serialization() {
628 let lang = Language::Python;
629 let json = serde_json::to_string(&lang).expect("json serialize failed");
630 let deserialized: Language = serde_json::from_str(&json).expect("json deserialize failed");
631 assert_eq!(lang, deserialized);
632
633 let other_lang = Language::Other("Haskell".to_string());
634 let json2 = serde_json::to_string(&other_lang).expect("json serialize failed");
635 let deserialized2: Language =
636 serde_json::from_str(&json2).expect("json deserialize failed");
637 assert_eq!(other_lang, deserialized2);
638 }
639
640 #[test]
645 fn test_dependency_manager_display() {
646 assert_eq!(DependencyManager::Pip.to_string(), "pip (requirements.txt)");
647 assert_eq!(DependencyManager::Pipenv.to_string(), "Pipenv");
648 assert_eq!(DependencyManager::Poetry.to_string(), "Poetry");
649 assert_eq!(DependencyManager::Conda.to_string(), "Conda");
650 assert_eq!(DependencyManager::Cargo.to_string(), "Cargo");
651 assert_eq!(DependencyManager::Npm.to_string(), "npm");
652 assert_eq!(DependencyManager::Yarn.to_string(), "Yarn");
653 assert_eq!(DependencyManager::GoMod.to_string(), "Go modules");
654 assert_eq!(DependencyManager::Maven.to_string(), "Maven");
655 assert_eq!(DependencyManager::Gradle.to_string(), "Gradle");
656 assert_eq!(DependencyManager::Make.to_string(), "Make");
657 }
658
659 #[test]
660 fn test_dependency_manager_equality() {
661 assert_eq!(DependencyManager::Pip, DependencyManager::Pip);
662 assert_ne!(DependencyManager::Pip, DependencyManager::Cargo);
663 }
664
665 #[test]
666 fn test_dependency_manager_serialization() {
667 let manager = DependencyManager::Cargo;
668 let json = serde_json::to_string(&manager).expect("json serialize failed");
669 let deserialized: DependencyManager =
670 serde_json::from_str(&json).expect("json deserialize failed");
671 assert_eq!(manager, deserialized);
672 }
673
674 #[test]
679 fn test_dependency_info_creation() {
680 let dep_info = DependencyInfo {
681 manager: DependencyManager::Pip,
682 file_path: PathBuf::from("requirements.txt"),
683 count: Some(10),
684 };
685
686 assert_eq!(dep_info.manager, DependencyManager::Pip);
687 assert_eq!(dep_info.file_path, PathBuf::from("requirements.txt"));
688 assert_eq!(dep_info.count, Some(10));
689 }
690
691 #[test]
692 fn test_dependency_info_serialization() {
693 let dep_info = DependencyInfo {
694 manager: DependencyManager::Cargo,
695 file_path: PathBuf::from("Cargo.toml"),
696 count: Some(5),
697 };
698
699 let json = serde_json::to_string(&dep_info).expect("json serialize failed");
700 let deserialized: DependencyInfo =
701 serde_json::from_str(&json).expect("json deserialize failed");
702
703 assert_eq!(dep_info.manager, deserialized.manager);
704 assert_eq!(dep_info.file_path, deserialized.file_path);
705 assert_eq!(dep_info.count, deserialized.count);
706 }
707
708 #[test]
713 fn test_language_stats_creation() {
714 let stats = LanguageStats {
715 language: Language::Python,
716 file_count: 50,
717 line_count: 10000,
718 percentage: 75.5,
719 };
720
721 assert_eq!(stats.language, Language::Python);
722 assert_eq!(stats.file_count, 50);
723 assert_eq!(stats.line_count, 10000);
724 assert_eq!(stats.percentage, 75.5);
725 }
726
727 #[test]
728 fn test_language_stats_serialization() {
729 let stats = LanguageStats {
730 language: Language::Rust,
731 file_count: 30,
732 line_count: 5000,
733 percentage: 24.5,
734 };
735
736 let json = serde_json::to_string(&stats).expect("json serialize failed");
737 let deserialized: LanguageStats =
738 serde_json::from_str(&json).expect("json deserialize failed");
739
740 assert_eq!(stats.language, deserialized.language);
741 assert_eq!(stats.file_count, deserialized.file_count);
742 assert_eq!(stats.line_count, deserialized.line_count);
743 assert_eq!(stats.percentage, deserialized.percentage);
744 }
745
746 #[test]
751 fn test_project_analysis_new() {
752 let analysis = ProjectAnalysis::new(PathBuf::from("/test/project"));
753
754 assert_eq!(analysis.root_path, PathBuf::from("/test/project"));
755 assert_eq!(analysis.languages.len(), 0);
756 assert!(analysis.primary_language.is_none());
757 assert_eq!(analysis.dependencies.len(), 0);
758 assert_eq!(analysis.total_files, 0);
759 assert_eq!(analysis.total_lines, 0);
760 assert!(analysis.tdg_score.is_none());
761 }
762
763 #[test]
764 fn test_project_analysis_recommend_transpiler_python() {
765 let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
766 analysis.primary_language = Some(Language::Python);
767
768 assert_eq!(analysis.recommend_transpiler(), Some("Depyler (Python → Rust)"));
769 }
770
771 #[test]
772 fn test_project_analysis_recommend_transpiler_c() {
773 let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
774 analysis.primary_language = Some(Language::C);
775
776 assert_eq!(analysis.recommend_transpiler(), Some("Decy (C/C++ → Rust)"));
777 }
778
779 #[test]
780 fn test_project_analysis_recommend_transpiler_cpp() {
781 let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
782 analysis.primary_language = Some(Language::Cpp);
783
784 assert_eq!(analysis.recommend_transpiler(), Some("Decy (C/C++ → Rust)"));
785 }
786
787 #[test]
788 fn test_project_analysis_recommend_transpiler_shell() {
789 let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
790 analysis.primary_language = Some(Language::Shell);
791
792 assert_eq!(analysis.recommend_transpiler(), Some("Bashrs (Shell → Rust)"));
793 }
794
795 #[test]
796 fn test_project_analysis_recommend_transpiler_rust() {
797 let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
798 analysis.primary_language = Some(Language::Rust);
799
800 assert_eq!(
801 analysis.recommend_transpiler(),
802 Some("Already Rust! Consider Ruchy for gradual typing.")
803 );
804 }
805
806 #[test]
807 fn test_project_analysis_recommend_transpiler_other() {
808 let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
809 analysis.primary_language = Some(Language::Java);
810
811 assert_eq!(analysis.recommend_transpiler(), None);
812 }
813
814 #[test]
815 fn test_project_analysis_recommend_transpiler_none() {
816 let analysis = ProjectAnalysis::new(PathBuf::from("/test"));
817 assert_eq!(analysis.recommend_transpiler(), None);
818 }
819
820 #[test]
821 fn test_project_analysis_has_ml_dependencies_pip() {
822 let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
823 analysis.dependencies.push(DependencyInfo {
824 manager: DependencyManager::Pip,
825 file_path: PathBuf::from("requirements.txt"),
826 count: Some(10),
827 });
828
829 assert!(analysis.has_ml_dependencies());
830 }
831
832 #[test]
833 fn test_project_analysis_has_ml_dependencies_conda() {
834 let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
835 analysis.dependencies.push(DependencyInfo {
836 manager: DependencyManager::Conda,
837 file_path: PathBuf::from("environment.yml"),
838 count: Some(5),
839 });
840
841 assert!(analysis.has_ml_dependencies());
842 }
843
844 #[test]
845 fn test_project_analysis_has_ml_dependencies_poetry() {
846 let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
847 analysis.dependencies.push(DependencyInfo {
848 manager: DependencyManager::Poetry,
849 file_path: PathBuf::from("pyproject.toml"),
850 count: Some(8),
851 });
852
853 assert!(analysis.has_ml_dependencies());
854 }
855
856 #[test]
857 fn test_project_analysis_has_ml_dependencies_false() {
858 let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
859 analysis.dependencies.push(DependencyInfo {
860 manager: DependencyManager::Cargo,
861 file_path: PathBuf::from("Cargo.toml"),
862 count: Some(5),
863 });
864
865 assert!(!analysis.has_ml_dependencies());
866 }
867
868 #[test]
869 fn test_project_analysis_has_ml_dependencies_empty() {
870 let analysis = ProjectAnalysis::new(PathBuf::from("/test"));
871 assert!(!analysis.has_ml_dependencies());
872 }
873
874 #[test]
875 fn test_project_analysis_serialization() {
876 let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
877 analysis.primary_language = Some(Language::Python);
878 analysis.total_files = 10;
879 analysis.total_lines = 1000;
880 analysis.tdg_score = Some(85.5);
881
882 let json = serde_json::to_string(&analysis).expect("json serialize failed");
883 let deserialized: ProjectAnalysis =
884 serde_json::from_str(&json).expect("json deserialize failed");
885
886 assert_eq!(analysis.root_path, deserialized.root_path);
887 assert_eq!(analysis.primary_language, deserialized.primary_language);
888 assert_eq!(analysis.total_files, deserialized.total_files);
889 assert_eq!(analysis.total_lines, deserialized.total_lines);
890 assert_eq!(analysis.tdg_score, deserialized.tdg_score);
891 }
892}