use anyhow::Result;
use depyler_core::debug::{DebuggerIntegration, DebuggerType, SourceMap};
use std::fs;
use std::path::{Path, PathBuf};
pub fn generate_debugger_script(
source_file: &Path,
rust_file: &Path,
debugger: &str,
output: Option<&Path>,
) -> Result<()> {
let source_map = SourceMap {
source_file: source_file.to_path_buf(),
target_file: rust_file.to_path_buf(),
mappings: vec![],
function_map: std::collections::HashMap::new(),
};
let debugger_type = match debugger.to_lowercase().as_str() {
"gdb" => DebuggerType::Gdb,
"rust-gdb" => DebuggerType::RustGdb,
"lldb" => DebuggerType::Lldb,
_ => return Err(anyhow::anyhow!("Unknown debugger: {}", debugger)),
};
let integration = DebuggerIntegration::new(debugger_type);
let script = integration.generate_init_script(&source_map);
let default_output = PathBuf::from(format!(
"{}.{}",
rust_file.file_stem().expect("file has stem").to_string_lossy(),
match debugger_type {
DebuggerType::Gdb | DebuggerType::RustGdb => "gdb",
DebuggerType::Lldb => "lldb",
}
));
let output_path = output.unwrap_or(&default_output);
fs::write(output_path, script)?;
println!("✅ Generated debugger script: {}", output_path.display());
Ok(())
}
pub fn launch_spydecy_debugger(source_file: &Path, visualize: bool) -> Result<()> {
use std::process::Command;
println!("🐛 Launching spydecy interactive debugger...");
println!(" Source: {}", source_file.display());
let mut cmd = Command::new("spydecy");
cmd.arg("debug").arg(source_file);
if visualize {
cmd.arg("--visualize");
println!(" Visualization: enabled");
}
let status = cmd.status()?;
if status.success() {
println!("✅ Debugger session completed");
Ok(())
} else {
Err(anyhow::anyhow!(
"Debugger exited with error code: {:?}",
status.code()
))
}
}
pub fn print_debugging_tips() {
println!("🐛 Depyler Debugging Guide");
println!("==========================");
println!();
println!("1. Transpile with debug info:");
println!(" depyler transpile script.py --debug");
println!();
println!("2. Generate source map:");
println!(" depyler transpile script.py --source-map");
println!();
println!("3. Interactive debugging with spydecy:");
println!(" depyler debug --spydecy script.py");
println!(" depyler debug --spydecy script.py --visualize");
println!();
println!("4. Debug with GDB:");
println!(" rust-gdb target/debug/your_program");
println!(" (gdb) source script.gdb");
println!();
println!("5. Debug with LLDB:");
println!(" rust-lldb target/debug/your_program");
println!(" (lldb) command source script.lldb");
println!();
println!("6. Set breakpoints:");
println!(" - In original Python function names");
println!(" - Line numbers map to Rust code");
println!();
println!("7. View variables:");
println!(" - Python variables retain their names");
println!(" - Use 'info locals' (gdb) or 'frame variable' (lldb)");
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_generate_debugger_script() {
let temp_dir = TempDir::new().unwrap();
let source_file = temp_dir.path().join("test.py");
let rust_file = temp_dir.path().join("test.rs");
fs::write(&source_file, "def test(): pass").unwrap();
fs::write(&rust_file, "fn test() {}").unwrap();
let gdb_output = temp_dir.path().join("test.gdb");
let result = generate_debugger_script(&source_file, &rust_file, "gdb", Some(&gdb_output));
assert!(result.is_ok());
assert!(gdb_output.exists());
let lldb_output = temp_dir.path().join("test.lldb");
let result = generate_debugger_script(&source_file, &rust_file, "lldb", Some(&lldb_output));
assert!(result.is_ok());
assert!(lldb_output.exists());
let result = generate_debugger_script(&source_file, &rust_file, "rust-gdb", None);
assert!(result.is_ok());
}
#[test]
fn test_unknown_debugger_error() {
let temp_dir = TempDir::new().unwrap();
let source_file = temp_dir.path().join("test.py");
let rust_file = temp_dir.path().join("test.rs");
let result = generate_debugger_script(&source_file, &rust_file, "unknown-debugger", None);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Unknown debugger"));
}
#[test]
fn test_default_output_path() {
let temp_dir = TempDir::new().unwrap();
let source_file = temp_dir.path().join("my_script.py");
let rust_file = temp_dir.path().join("my_script.rs");
fs::write(&source_file, "def test(): pass").unwrap();
fs::write(&rust_file, "fn test() {}").unwrap();
let result = generate_debugger_script(&source_file, &rust_file, "gdb", None);
assert!(result.is_ok());
let default_gdb = temp_dir.path().join("my_script.gdb");
assert!(default_gdb.exists() || PathBuf::from("my_script.gdb").exists());
}
#[test]
fn test_print_debugging_tips() {
print_debugging_tips();
}
#[test]
fn test_debugger_case_insensitive() {
let temp_dir = TempDir::new().unwrap();
let source_file = temp_dir.path().join("case.py");
let rust_file = temp_dir.path().join("case.rs");
fs::write(&source_file, "def foo(): pass").unwrap();
fs::write(&rust_file, "fn foo() {}").unwrap();
let gdb_upper = temp_dir.path().join("upper.gdb");
let result = generate_debugger_script(&source_file, &rust_file, "GDB", Some(&gdb_upper));
assert!(result.is_ok());
let lldb_mixed = temp_dir.path().join("mixed.lldb");
let result = generate_debugger_script(&source_file, &rust_file, "LlDb", Some(&lldb_mixed));
assert!(result.is_ok());
let result = generate_debugger_script(&source_file, &rust_file, "Rust-GDB", None);
assert!(result.is_ok());
}
#[test]
fn test_gdb_script_content() {
let temp_dir = TempDir::new().unwrap();
let source_file = temp_dir.path().join("content.py");
let rust_file = temp_dir.path().join("content.rs");
let gdb_output = temp_dir.path().join("content.gdb");
fs::write(&source_file, "def test_func(): pass").unwrap();
fs::write(&rust_file, "fn test_func() {}").unwrap();
let result = generate_debugger_script(&source_file, &rust_file, "gdb", Some(&gdb_output));
assert!(result.is_ok());
let content = fs::read_to_string(&gdb_output).unwrap();
assert!(!content.is_empty());
}
#[test]
fn test_lldb_script_content() {
let temp_dir = TempDir::new().unwrap();
let source_file = temp_dir.path().join("lldb_content.py");
let rust_file = temp_dir.path().join("lldb_content.rs");
let lldb_output = temp_dir.path().join("lldb_content.lldb");
fs::write(&source_file, "def another(): pass").unwrap();
fs::write(&rust_file, "fn another() {}").unwrap();
let result = generate_debugger_script(&source_file, &rust_file, "lldb", Some(&lldb_output));
assert!(result.is_ok());
let content = fs::read_to_string(&lldb_output).unwrap();
assert!(!content.is_empty());
}
#[test]
fn test_rust_gdb_script_default_extension() {
let temp_dir = TempDir::new().unwrap();
let source_file = temp_dir.path().join("rustgdb.py");
let rust_file = temp_dir.path().join("rustgdb.rs");
fs::write(&source_file, "def rust_debug(): pass").unwrap();
fs::write(&rust_file, "fn rust_debug() {}").unwrap();
let result = generate_debugger_script(&source_file, &rust_file, "rust-gdb", None);
assert!(result.is_ok());
}
#[test]
fn test_spydecy_not_installed() {
let temp_dir = TempDir::new().unwrap();
let source_file = temp_dir.path().join("spy.py");
fs::write(&source_file, "def spy(): pass").unwrap();
let result = launch_spydecy_debugger(&source_file, false);
assert!(result.is_err());
}
#[test]
fn test_spydecy_with_visualize() {
let temp_dir = TempDir::new().unwrap();
let source_file = temp_dir.path().join("viz.py");
fs::write(&source_file, "def visualize(): pass").unwrap();
let result = launch_spydecy_debugger(&source_file, true);
assert!(result.is_err());
}
#[test]
fn test_empty_debugger_name() {
let temp_dir = TempDir::new().unwrap();
let source_file = temp_dir.path().join("empty.py");
let rust_file = temp_dir.path().join("empty.rs");
let result = generate_debugger_script(&source_file, &rust_file, "", None);
assert!(result.is_err());
}
#[test]
fn test_various_unknown_debuggers() {
let temp_dir = TempDir::new().unwrap();
let source_file = temp_dir.path().join("unknown.py");
let rust_file = temp_dir.path().join("unknown.rs");
let unknown_debuggers = vec!["windbg", "ollydbg", "ida", "x64dbg", "radare2"];
for dbg in unknown_debuggers {
let result = generate_debugger_script(&source_file, &rust_file, dbg, None);
assert!(result.is_err(), "Expected error for debugger: {}", dbg);
}
}
#[test]
fn test_paths_with_spaces() {
let temp_dir = TempDir::new().unwrap();
let source_file = temp_dir.path().join("file with spaces.py");
let rust_file = temp_dir.path().join("file with spaces.rs");
let output = temp_dir.path().join("output with spaces.gdb");
fs::write(&source_file, "def spaced(): pass").unwrap();
fs::write(&rust_file, "fn spaced() {}").unwrap();
let result = generate_debugger_script(&source_file, &rust_file, "gdb", Some(&output));
assert!(result.is_ok());
assert!(output.exists());
}
#[test]
fn test_special_characters_in_filename() {
let temp_dir = TempDir::new().unwrap();
let source_file = temp_dir.path().join("test_file-v2.0.py");
let rust_file = temp_dir.path().join("test_file-v2.0.rs");
fs::write(&source_file, "def version(): pass").unwrap();
fs::write(&rust_file, "fn version() {}").unwrap();
let result = generate_debugger_script(&source_file, &rust_file, "gdb", None);
assert!(result.is_ok());
}
#[test]
fn test_all_debugger_types_produce_output() {
let temp_dir = TempDir::new().unwrap();
let source_file = temp_dir.path().join("alldbg.py");
let rust_file = temp_dir.path().join("alldbg.rs");
fs::write(&source_file, "def all_debug(): pass").unwrap();
fs::write(&rust_file, "fn all_debug() {}").unwrap();
let debuggers = vec![("gdb", "gdb"), ("lldb", "lldb"), ("rust-gdb", "gdb")];
for (dbg, ext) in debuggers {
let output = temp_dir.path().join(format!("alldbg_{}.{}", dbg, ext));
let result = generate_debugger_script(&source_file, &rust_file, dbg, Some(&output));
assert!(result.is_ok(), "Failed for debugger: {}", dbg);
assert!(output.exists(), "Output not created for: {}", dbg);
}
}
}
pub mod examples {}