1#![allow(dead_code)]
2
3use anyhow::{Context, Result};
4use std::process::Command;
5
6#[cfg(feature = "native")]
7use tracing::{debug, info};
8
9#[cfg(not(feature = "native"))]
11macro_rules! info {
12 ($($arg:tt)*) => {{}};
13}
14
15#[cfg(not(feature = "native"))]
16macro_rules! debug {
17 ($($arg:tt)*) => {{}};
18}
19
20#[derive(Debug, Clone)]
22pub struct ToolInfo {
23 pub name: String,
24 pub version: Option<String>,
25 pub path: String,
26 pub available: bool,
27}
28
29#[derive(Debug, Clone)]
31pub struct ToolRegistry {
32 pub decy: Option<ToolInfo>,
33 pub depyler: Option<ToolInfo>,
34 pub bashrs: Option<ToolInfo>,
35 pub ruchy: Option<ToolInfo>,
36 pub trueno: Option<ToolInfo>,
37 pub aprender: Option<ToolInfo>,
38 pub realizar: Option<ToolInfo>,
39 pub renacer: Option<ToolInfo>,
40 pub pmat: Option<ToolInfo>,
41}
42
43impl ToolRegistry {
44 pub fn detect() -> Self {
46 info!("Detecting installed Pragmatic AI Labs tools...");
47
48 Self {
49 decy: detect_tool("decy"),
50 depyler: detect_tool("depyler"),
51 bashrs: detect_tool("bashrs"),
52 ruchy: detect_tool("ruchy"),
53 trueno: detect_tool("trueno"),
54 aprender: detect_tool("aprender"),
55 realizar: detect_tool("realizar"),
56 renacer: detect_tool("renacer"),
57 pmat: detect_tool("pmat"),
58 }
59 }
60
61 pub fn has_transpiler(&self) -> bool {
63 self.decy.is_some() || self.depyler.is_some() || self.bashrs.is_some()
64 }
65
66 pub fn get_transpiler_for_language(&self, lang: &crate::types::Language) -> Option<&ToolInfo> {
68 use crate::types::Language;
69
70 match lang {
71 Language::C | Language::Cpp => self.decy.as_ref(),
72 Language::Python => self.depyler.as_ref(),
73 Language::Shell => self.bashrs.as_ref(),
74 _ => None,
75 }
76 }
77
78 pub fn available_tools(&self) -> Vec<String> {
80 let mut tools = Vec::new();
81
82 if let Some(tool) = &self.decy {
83 if tool.available {
84 tools.push("Decy (C/C++ → Rust)".to_string());
85 }
86 }
87 if let Some(tool) = &self.depyler {
88 if tool.available {
89 tools.push("Depyler (Python → Rust)".to_string());
90 }
91 }
92 if let Some(tool) = &self.bashrs {
93 if tool.available {
94 tools.push("Bashrs (Shell → Rust)".to_string());
95 }
96 }
97 if let Some(tool) = &self.ruchy {
98 if tool.available {
99 tools.push("Ruchy (Rust scripting)".to_string());
100 }
101 }
102 if let Some(tool) = &self.pmat {
103 if tool.available {
104 tools.push("PMAT (Quality analysis)".to_string());
105 }
106 }
107 if let Some(tool) = &self.trueno {
108 if tool.available {
109 tools.push("Trueno (Multi-target compute)".to_string());
110 }
111 }
112 if let Some(tool) = &self.aprender {
113 if tool.available {
114 tools.push("Aprender (ML library)".to_string());
115 }
116 }
117 if let Some(tool) = &self.realizar {
118 if tool.available {
119 tools.push("Realizar (Inference runtime)".to_string());
120 }
121 }
122 if let Some(tool) = &self.renacer {
123 if tool.available {
124 tools.push("Renacer (Syscall tracing)".to_string());
125 }
126 }
127
128 tools
129 }
130
131 pub fn get_installation_instructions(&self, needed_tools: &[&str]) -> Vec<String> {
133 let mut instructions = Vec::new();
134
135 for tool in needed_tools {
136 let instruction = match *tool {
137 "decy" if self.decy.is_none() => Some("Install Decy: cargo install decy"),
138 "depyler" if self.depyler.is_none() => {
139 Some("Install Depyler: cargo install depyler")
140 }
141 "bashrs" if self.bashrs.is_none() => Some("Install Bashrs: cargo install bashrs"),
142 "ruchy" if self.ruchy.is_none() => Some("Install Ruchy: cargo install ruchy"),
143 "pmat" if self.pmat.is_none() => Some("Install PMAT: cargo install pmat"),
144 "trueno" if self.trueno.is_none() => {
145 Some("Install Trueno: Add 'trueno' to Cargo.toml dependencies")
146 }
147 "aprender" if self.aprender.is_none() => {
148 Some("Install Aprender: Add 'aprender' to Cargo.toml dependencies")
149 }
150 "realizar" if self.realizar.is_none() => {
151 Some("Install Realizar: Add 'realizar' to Cargo.toml dependencies")
152 }
153 "renacer" if self.renacer.is_none() => {
154 Some("Install Renacer: cargo install renacer")
155 }
156 _ => None,
157 };
158
159 if let Some(inst) = instruction {
160 instructions.push(inst.to_string());
161 }
162 }
163
164 instructions
165 }
166}
167
168fn detect_tool(name: &str) -> Option<ToolInfo> {
170 debug!("Checking for tool: {}", name);
171
172 let path = match which::which(name) {
174 Ok(p) => p.to_string_lossy().to_string(),
175 Err(_) => {
176 debug!("Tool '{}' not found in PATH", name);
177 return None;
178 }
179 };
180
181 let version = get_tool_version(name);
183
184 debug!(
185 "Found tool '{}' at '{}' (version: {:?})",
186 name, path, version
187 );
188
189 Some(ToolInfo {
190 name: name.to_string(),
191 version,
192 path,
193 available: true,
194 })
195}
196
197fn get_tool_version(name: &str) -> Option<String> {
199 let output = Command::new(name).arg("--version").output().ok()?;
200
201 if !output.status.success() {
202 return None;
203 }
204
205 let stdout = String::from_utf8_lossy(&output.stdout);
206 let version_line = stdout.lines().next()?;
207
208 let parts: Vec<&str> = version_line.split_whitespace().collect();
214 let version = parts.last()?.to_string();
215
216 Some(version)
217}
218
219pub fn run_tool(
221 tool_name: &str,
222 args: &[&str],
223 working_dir: Option<&std::path::Path>,
224) -> Result<String> {
225 debug!("Running tool: {} {:?}", tool_name, args);
226
227 let mut cmd = Command::new(tool_name);
228 cmd.args(args);
229
230 if let Some(dir) = working_dir {
231 cmd.current_dir(dir);
232 }
233
234 let output = cmd
235 .output()
236 .with_context(|| format!("Failed to run tool: {}", tool_name))?;
237
238 if !output.status.success() {
239 let stderr = String::from_utf8_lossy(&output.stderr);
240 anyhow::bail!(
241 "Tool '{}' failed with exit code {:?}: {}",
242 tool_name,
243 output.status.code(),
244 stderr
245 );
246 }
247
248 let stdout = String::from_utf8_lossy(&output.stdout).to_string();
249 Ok(stdout)
250}
251
252pub fn transpile_python(
254 input_path: &std::path::Path,
255 output_path: &std::path::Path,
256) -> Result<String> {
257 info!(
258 "Transpiling Python with Depyler: {:?} → {:?}",
259 input_path, output_path
260 );
261
262 let input_str = input_path.to_string_lossy();
263 let output_str = output_path.to_string_lossy();
264
265 let args = vec![
266 "transpile",
267 "--input",
268 &input_str,
269 "--output",
270 &output_str,
271 "--format",
272 "project", ];
274
275 run_tool("depyler", &args, None)
276}
277
278pub fn transpile_shell(
280 input_path: &std::path::Path,
281 output_path: &std::path::Path,
282) -> Result<String> {
283 info!(
284 "Transpiling Shell with Bashrs: {:?} → {:?}",
285 input_path, output_path
286 );
287
288 let input_str = input_path.to_string_lossy();
289 let output_str = output_path.to_string_lossy();
290
291 let args = vec![
292 "build",
293 &input_str,
294 "-o",
295 &output_str,
296 "--target",
297 "posix", "--verify",
299 "strict", ];
301
302 run_tool("bashrs", &args, None)
303}
304
305pub fn transpile_c_cpp(
307 input_path: &std::path::Path,
308 output_path: &std::path::Path,
309) -> Result<String> {
310 info!(
311 "Transpiling C/C++ with Decy: {:?} → {:?}",
312 input_path, output_path
313 );
314
315 let input_str = input_path.to_string_lossy();
316 let output_str = output_path.to_string_lossy();
317
318 let args = vec!["transpile", "--input", &input_str, "--output", &output_str];
320
321 run_tool("decy", &args, None)
322}
323
324pub fn analyze_quality(path: &std::path::Path) -> Result<String> {
326 info!("Running PMAT quality analysis: {:?}", path);
327
328 let path_str = path.to_string_lossy();
329
330 let args = vec!["analyze", "complexity", &path_str, "--format", "json"];
331
332 run_tool("pmat", &args, None)
333}
334
335pub fn run_ruchy_script(script_path: &std::path::Path) -> Result<String> {
337 info!("Running Ruchy script: {:?}", script_path);
338
339 let script_str = script_path.to_string_lossy();
340
341 let args = vec!["run", &script_str];
342
343 run_tool("ruchy", &args, None)
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349 use std::path::PathBuf;
350
351 #[test]
356 fn test_tool_info_creation() {
357 let tool = ToolInfo {
358 name: "decy".to_string(),
359 version: Some("1.0.0".to_string()),
360 path: "/usr/local/bin/decy".to_string(),
361 available: true,
362 };
363
364 assert_eq!(tool.name, "decy");
365 assert_eq!(tool.version, Some("1.0.0".to_string()));
366 assert_eq!(tool.path, "/usr/local/bin/decy");
367 assert!(tool.available);
368 }
369
370 #[test]
371 fn test_tool_info_no_version() {
372 let tool = ToolInfo {
373 name: "test_tool".to_string(),
374 version: None,
375 path: "/bin/test".to_string(),
376 available: true,
377 };
378
379 assert_eq!(tool.name, "test_tool");
380 assert!(tool.version.is_none());
381 }
382
383 #[test]
384 fn test_tool_info_clone() {
385 let tool1 = ToolInfo {
386 name: "depyler".to_string(),
387 version: Some("2.0.0".to_string()),
388 path: "/usr/bin/depyler".to_string(),
389 available: true,
390 };
391
392 let tool2 = tool1.clone();
393 assert_eq!(tool1.name, tool2.name);
394 assert_eq!(tool1.version, tool2.version);
395 assert_eq!(tool1.path, tool2.path);
396 assert_eq!(tool1.available, tool2.available);
397 }
398
399 #[test]
400 fn test_tool_info_debug() {
401 let tool = ToolInfo {
402 name: "bashrs".to_string(),
403 version: Some("0.5.0".to_string()),
404 path: "/usr/local/bin/bashrs".to_string(),
405 available: true,
406 };
407
408 let debug_str = format!("{:?}", tool);
409 assert!(debug_str.contains("bashrs"));
410 assert!(debug_str.contains("0.5.0"));
411 }
412
413 fn create_test_registry() -> ToolRegistry {
418 ToolRegistry {
419 decy: Some(ToolInfo {
420 name: "decy".to_string(),
421 version: Some("1.0.0".to_string()),
422 path: "/usr/bin/decy".to_string(),
423 available: true,
424 }),
425 depyler: Some(ToolInfo {
426 name: "depyler".to_string(),
427 version: Some("2.0.0".to_string()),
428 path: "/usr/bin/depyler".to_string(),
429 available: true,
430 }),
431 bashrs: None,
432 ruchy: Some(ToolInfo {
433 name: "ruchy".to_string(),
434 version: Some("0.3.0".to_string()),
435 path: "/usr/bin/ruchy".to_string(),
436 available: true,
437 }),
438 trueno: None,
439 aprender: None,
440 realizar: None,
441 renacer: None,
442 pmat: Some(ToolInfo {
443 name: "pmat".to_string(),
444 version: Some("1.5.0".to_string()),
445 path: "/usr/bin/pmat".to_string(),
446 available: true,
447 }),
448 }
449 }
450
451 fn create_empty_registry() -> ToolRegistry {
452 ToolRegistry {
453 decy: None,
454 depyler: None,
455 bashrs: None,
456 ruchy: None,
457 trueno: None,
458 aprender: None,
459 realizar: None,
460 renacer: None,
461 pmat: None,
462 }
463 }
464
465 #[test]
466 fn test_tool_registry_clone() {
467 let registry1 = create_test_registry();
468 let registry2 = registry1.clone();
469
470 assert!(registry2.decy.is_some());
471 assert!(registry2.depyler.is_some());
472 assert!(registry2.bashrs.is_none());
473 }
474
475 #[test]
476 fn test_tool_registry_debug() {
477 let registry = create_test_registry();
478 let debug_str = format!("{:?}", registry);
479 assert!(debug_str.contains("decy"));
480 assert!(debug_str.contains("depyler"));
481 }
482
483 #[test]
484 fn test_tool_detection() {
485 let registry = ToolRegistry::detect();
487
488 println!("Available tools: {:?}", registry.available_tools());
491 }
492
493 #[test]
494 fn test_has_transpiler_with_tools() {
495 let registry = create_test_registry();
496 assert!(registry.has_transpiler());
497 }
498
499 #[test]
500 fn test_has_transpiler_empty() {
501 let registry = create_empty_registry();
502 assert!(!registry.has_transpiler());
503 }
504
505 #[test]
506 fn test_has_transpiler_only_decy() {
507 let mut registry = create_empty_registry();
508 registry.decy = Some(ToolInfo {
509 name: "decy".to_string(),
510 version: None,
511 path: "/usr/bin/decy".to_string(),
512 available: true,
513 });
514 assert!(registry.has_transpiler());
515 }
516
517 #[test]
518 fn test_has_transpiler_only_depyler() {
519 let mut registry = create_empty_registry();
520 registry.depyler = Some(ToolInfo {
521 name: "depyler".to_string(),
522 version: None,
523 path: "/usr/bin/depyler".to_string(),
524 available: true,
525 });
526 assert!(registry.has_transpiler());
527 }
528
529 #[test]
530 fn test_has_transpiler_only_bashrs() {
531 let mut registry = create_empty_registry();
532 registry.bashrs = Some(ToolInfo {
533 name: "bashrs".to_string(),
534 version: None,
535 path: "/usr/bin/bashrs".to_string(),
536 available: true,
537 });
538 assert!(registry.has_transpiler());
539 }
540
541 #[test]
542 fn test_get_transpiler_for_language_c() {
543 let registry = create_test_registry();
544 let tool = registry.get_transpiler_for_language(&crate::types::Language::C);
545 assert!(tool.is_some());
546 assert_eq!(tool.unwrap().name, "decy");
547 }
548
549 #[test]
550 fn test_get_transpiler_for_language_cpp() {
551 let registry = create_test_registry();
552 let tool = registry.get_transpiler_for_language(&crate::types::Language::Cpp);
553 assert!(tool.is_some());
554 assert_eq!(tool.unwrap().name, "decy");
555 }
556
557 #[test]
558 fn test_get_transpiler_for_language_python() {
559 let registry = create_test_registry();
560 let tool = registry.get_transpiler_for_language(&crate::types::Language::Python);
561 assert!(tool.is_some());
562 assert_eq!(tool.unwrap().name, "depyler");
563 }
564
565 #[test]
566 fn test_get_transpiler_for_language_shell() {
567 let mut registry = create_test_registry();
568 registry.bashrs = Some(ToolInfo {
569 name: "bashrs".to_string(),
570 version: None,
571 path: "/usr/bin/bashrs".to_string(),
572 available: true,
573 });
574
575 let tool = registry.get_transpiler_for_language(&crate::types::Language::Shell);
576 assert!(tool.is_some());
577 assert_eq!(tool.unwrap().name, "bashrs");
578 }
579
580 #[test]
581 fn test_get_transpiler_for_language_rust() {
582 let registry = create_test_registry();
583 let tool = registry.get_transpiler_for_language(&crate::types::Language::Rust);
584 assert!(tool.is_none());
585 }
586
587 #[test]
588 fn test_get_transpiler_for_language_javascript() {
589 let registry = create_test_registry();
590 let tool = registry.get_transpiler_for_language(&crate::types::Language::JavaScript);
591 assert!(tool.is_none());
592 }
593
594 #[test]
595 fn test_get_transpiler_for_language_other() {
596 let registry = create_test_registry();
597 let tool = registry
598 .get_transpiler_for_language(&crate::types::Language::Other("Kotlin".to_string()));
599 assert!(tool.is_none());
600 }
601
602 #[test]
603 fn test_available_tools_all_installed() {
604 let mut registry = create_test_registry();
605 registry.bashrs = Some(ToolInfo {
606 name: "bashrs".to_string(),
607 version: Some("1.0.0".to_string()),
608 path: "/usr/bin/bashrs".to_string(),
609 available: true,
610 });
611 registry.trueno = Some(ToolInfo {
612 name: "trueno".to_string(),
613 version: Some("2.0.0".to_string()),
614 path: "/usr/bin/trueno".to_string(),
615 available: true,
616 });
617 registry.aprender = Some(ToolInfo {
618 name: "aprender".to_string(),
619 version: Some("1.0.0".to_string()),
620 path: "/usr/bin/aprender".to_string(),
621 available: true,
622 });
623 registry.realizar = Some(ToolInfo {
624 name: "realizar".to_string(),
625 version: Some("1.0.0".to_string()),
626 path: "/usr/bin/realizar".to_string(),
627 available: true,
628 });
629 registry.renacer = Some(ToolInfo {
630 name: "renacer".to_string(),
631 version: Some("1.0.0".to_string()),
632 path: "/usr/bin/renacer".to_string(),
633 available: true,
634 });
635
636 let tools = registry.available_tools();
637 assert_eq!(tools.len(), 9);
638 assert!(tools.contains(&"Decy (C/C++ → Rust)".to_string()));
639 assert!(tools.contains(&"Depyler (Python → Rust)".to_string()));
640 assert!(tools.contains(&"Bashrs (Shell → Rust)".to_string()));
641 assert!(tools.contains(&"Ruchy (Rust scripting)".to_string()));
642 assert!(tools.contains(&"PMAT (Quality analysis)".to_string()));
643 }
644
645 #[test]
646 fn test_available_tools_empty() {
647 let registry = create_empty_registry();
648 let tools = registry.available_tools();
649 assert_eq!(tools.len(), 0);
650 }
651
652 #[test]
653 fn test_available_tools_partial() {
654 let registry = create_test_registry();
655 let tools = registry.available_tools();
656
657 assert_eq!(tools.len(), 4);
659 assert!(tools.contains(&"Decy (C/C++ → Rust)".to_string()));
660 assert!(tools.contains(&"Depyler (Python → Rust)".to_string()));
661 assert!(tools.contains(&"Ruchy (Rust scripting)".to_string()));
662 assert!(tools.contains(&"PMAT (Quality analysis)".to_string()));
663 }
664
665 #[test]
666 fn test_available_tools_unavailable_flag() {
667 let mut registry = create_test_registry();
668 if let Some(tool) = &mut registry.depyler {
670 tool.available = false;
671 }
672
673 let tools = registry.available_tools();
674 assert!(!tools.iter().any(|t| t.contains("Depyler")));
676 }
677
678 #[test]
679 fn test_get_installation_instructions_all_missing() {
680 let registry = create_empty_registry();
681 let instructions = registry.get_installation_instructions(&[
682 "decy", "depyler", "bashrs", "ruchy", "pmat", "trueno", "aprender", "realizar",
683 "renacer",
684 ]);
685
686 assert_eq!(instructions.len(), 9);
687 assert!(instructions.contains(&"Install Decy: cargo install decy".to_string()));
688 assert!(instructions.contains(&"Install Depyler: cargo install depyler".to_string()));
689 assert!(instructions.contains(&"Install Bashrs: cargo install bashrs".to_string()));
690 assert!(instructions.contains(&"Install Ruchy: cargo install ruchy".to_string()));
691 assert!(instructions.contains(&"Install PMAT: cargo install pmat".to_string()));
692 assert!(instructions
693 .contains(&"Install Trueno: Add 'trueno' to Cargo.toml dependencies".to_string()));
694 assert!(instructions
695 .contains(&"Install Aprender: Add 'aprender' to Cargo.toml dependencies".to_string()));
696 assert!(instructions
697 .contains(&"Install Realizar: Add 'realizar' to Cargo.toml dependencies".to_string()));
698 assert!(instructions.contains(&"Install Renacer: cargo install renacer".to_string()));
699 }
700
701 #[test]
702 fn test_get_installation_instructions_none_missing() {
703 let registry = create_test_registry();
704 let instructions =
705 registry.get_installation_instructions(&["decy", "depyler", "ruchy", "pmat"]);
706
707 assert_eq!(instructions.len(), 0);
709 }
710
711 #[test]
712 fn test_get_installation_instructions_partial() {
713 let registry = create_test_registry();
714 let instructions = registry.get_installation_instructions(&["decy", "bashrs", "trueno"]);
715
716 assert_eq!(instructions.len(), 2);
718 assert!(instructions.contains(&"Install Bashrs: cargo install bashrs".to_string()));
719 assert!(instructions
720 .contains(&"Install Trueno: Add 'trueno' to Cargo.toml dependencies".to_string()));
721 }
722
723 #[test]
724 fn test_get_installation_instructions_unknown_tool() {
725 let registry = create_empty_registry();
726 let instructions = registry.get_installation_instructions(&["unknown_tool", "decy"]);
727
728 assert_eq!(instructions.len(), 1);
730 assert!(instructions.contains(&"Install Decy: cargo install decy".to_string()));
731 }
732
733 #[test]
734 fn test_get_installation_instructions_empty_list() {
735 let registry = create_test_registry();
736 let instructions = registry.get_installation_instructions(&[]);
737
738 assert_eq!(instructions.len(), 0);
739 }
740
741 #[test]
746 fn test_transpile_python_paths() {
747 let input = PathBuf::from("/path/to/input.py");
748 let output = PathBuf::from("/path/to/output");
749
750 let _result = transpile_python(&input, &output);
753 }
754
755 #[test]
756 fn test_transpile_shell_paths() {
757 let input = PathBuf::from("/path/to/script.sh");
758 let output = PathBuf::from("/path/to/output");
759
760 let _result = transpile_shell(&input, &output);
762 }
763
764 #[test]
765 fn test_transpile_c_cpp_paths() {
766 let input = PathBuf::from("/path/to/code.c");
767 let output = PathBuf::from("/path/to/output");
768
769 let _result = transpile_c_cpp(&input, &output);
771 }
772
773 #[test]
774 fn test_analyze_quality_path() {
775 let path = PathBuf::from("/path/to/project");
776
777 let _result = analyze_quality(&path);
779 }
780
781 #[test]
782 fn test_run_ruchy_script_path() {
783 let script = PathBuf::from("/path/to/script.ruchy");
784
785 let _result = run_ruchy_script(&script);
787 }
788
789 #[test]
790 fn test_run_tool_basic_args() {
791 let result = run_tool("echo", &["test"], None);
793
794 if let Ok(output) = result {
796 assert!(output.contains("test"));
797 }
798 }
799
800 #[test]
801 fn test_run_tool_with_working_dir() {
802 use std::env;
803 let current_dir = env::current_dir().unwrap();
804
805 let result = run_tool("pwd", &[], Some(¤t_dir));
806
807 if let Ok(output) = result {
809 assert!(!output.is_empty());
810 }
811 }
812}