use crate::ansi_colors::Colorize;
use crate::config::BatutaConfig;
use crate::tools::{self, ToolInfo, ToolRegistry};
use crate::types::{Language, WorkflowPhase, WorkflowState};
use std::path::Path;
use std::path::PathBuf;
use tracing::warn;
pub fn cmd_transpile(
incremental: bool,
cache: bool,
modules: Option<Vec<String>>,
ruchy: bool,
repl: bool,
) -> anyhow::Result<()> {
contract_pre_transpile!(incremental);
println!("{}", "🔄 Transpiling code...".bright_cyan().bold());
println!();
let state_file = crate::cli::get_state_file_path();
let mut state = WorkflowState::load(&state_file).unwrap_or_else(|e| {
warn!("Failed to load workflow state, starting fresh: {e}");
WorkflowState::new()
});
let config = match check_transpile_prerequisites(&state) {
Ok(c) => c,
Err(e) => {
state.fail_phase(WorkflowPhase::Transpilation, e.to_string());
state.save(&state_file)?;
return Ok(());
}
};
state.start_phase(WorkflowPhase::Transpilation);
state.save(&state_file)?;
let (_tools, _primary_lang, transpiler) = match setup_transpiler(&config) {
Ok(t) => t,
Err(e) => {
state.fail_phase(WorkflowPhase::Transpilation, e.to_string());
state.save(&state_file)?;
return Err(e);
}
};
display_transpilation_settings(&config, incremental, cache, ruchy, &modules);
std::fs::create_dir_all(&config.transpilation.output_dir)?;
std::fs::create_dir_all(config.transpilation.output_dir.join("src"))?;
println!("{}", "🚀 Starting transpilation...".bright_green().bold());
println!();
let owned_args =
crate::cli::build_transpiler_args(&config, incremental, cache, ruchy, &modules);
let args: Vec<&str> = owned_args.iter().map(|s| s.as_str()).collect();
println!("{}", "Executing:".dimmed());
println!(" {} {} {}", "$".dimmed(), transpiler.name.cyan(), args.join(" ").dimmed());
println!();
println!("{}", "Transpiling...".bright_yellow());
let result = tools::run_tool(&transpiler.name, &args, Some(&config.source.path));
handle_transpile_result(result, &mut state, &state_file, &config, &transpiler, repl)
}
fn check_transpile_prerequisites(state: &WorkflowState) -> anyhow::Result<BatutaConfig> {
if !state.is_phase_completed(WorkflowPhase::Analysis) {
println!("{}", "⚠️ Analysis phase not completed!".yellow().bold());
println!();
println!("Run {} first to analyze your project.", "batuta analyze <path>".cyan());
println!();
crate::cli::workflow::display_workflow_progress(state);
anyhow::bail!("Analysis phase not completed");
}
let config_path = PathBuf::from("batuta.toml");
if !config_path.exists() {
println!("{}", "⚠️ No configuration file found!".yellow().bold());
println!();
println!("Run {} to create a configuration file.", "batuta init".cyan());
println!();
anyhow::bail!("No batuta.toml configuration file");
}
BatutaConfig::load(&config_path)
}
fn setup_transpiler(config: &BatutaConfig) -> anyhow::Result<(ToolRegistry, Language, ToolInfo)> {
let tools = ToolRegistry::detect();
let primary_lang = config
.project
.primary_language
.as_ref()
.and_then(|s| s.parse().ok())
.unwrap_or(Language::Other("unknown".to_string()));
let transpiler = match tools.get_transpiler_for_language(&primary_lang) {
Some(t) => t.clone(),
None => {
handle_missing_tools(&tools, &primary_lang)?;
unreachable!("handle_missing_tools always returns Err")
}
};
println!(
"{} Using transpiler: {} ({})",
"✓".bright_green(),
transpiler.name.cyan(),
transpiler.version.as_ref().unwrap_or(&"unknown".to_string())
);
println!();
Ok((tools, primary_lang, transpiler))
}
fn handle_missing_tools(tools: &ToolRegistry, primary_lang: &Language) -> anyhow::Result<()> {
println!("{}", "❌ No transpiler available!".red().bold());
println!();
println!("{}", "Available tools:".bright_yellow());
for tool in tools.available_tools() {
println!(" {} {}", "•".bright_blue(), tool.cyan());
}
println!();
println!(
"{}: Install the appropriate transpiler for {}",
"Hint".bold(),
format!("{}", primary_lang).cyan()
);
println!(" {} cargo install depyler", "Python:".dimmed());
println!(" {} cargo install bashrs", "Shell:".dimmed());
println!(" {} cargo install decy", "C/C++:".dimmed());
println!();
anyhow::bail!("No transpiler available for {}", primary_lang)
}
fn display_transpilation_settings(
config: &BatutaConfig,
incremental: bool,
cache: bool,
ruchy: bool,
modules: &Option<Vec<String>>,
) {
println!("{}", "Transpilation Settings:".bright_yellow().bold());
println!(" {} Source: {:?}", "•".bright_blue(), config.source.path);
println!(" {} Output: {:?}", "•".bright_blue(), config.transpilation.output_dir);
println!(
" {} Incremental: {}",
"•".bright_blue(),
if incremental || config.transpilation.incremental {
"enabled".green()
} else {
"disabled".dimmed()
}
);
println!(
" {} Caching: {}",
"•".bright_blue(),
if cache || config.transpilation.cache { "enabled".green() } else { "disabled".dimmed() }
);
if let Some(mods) = modules {
println!(" {} Modules: {}", "•".bright_blue(), mods.join(", ").cyan());
}
if ruchy || config.transpilation.use_ruchy {
println!(" {} Target: {}", "•".bright_blue(), "Ruchy".cyan());
if let Some(strictness) = &config.transpilation.ruchy_strictness {
println!(" {} Strictness: {}", "•".bright_blue(), strictness.cyan());
}
}
println!();
}
fn handle_transpile_result(
result: Result<String, anyhow::Error>,
state: &mut WorkflowState,
state_file: &Path,
config: &BatutaConfig,
transpiler: &ToolInfo,
repl: bool,
) -> anyhow::Result<()> {
match result {
Ok(output) => handle_transpile_success(output, state, state_file, config, repl),
Err(e) => handle_transpile_failure(e, state, state_file, config, transpiler),
}
}
fn handle_transpile_success(
output: String,
state: &mut WorkflowState,
state_file: &Path,
config: &BatutaConfig,
repl: bool,
) -> anyhow::Result<()> {
println!();
println!("{}", "✅ Transpilation completed successfully!".bright_green().bold());
println!();
if !output.trim().is_empty() {
println!("{}", "Transpiler output:".bright_yellow());
println!("{}", "─".repeat(50).dimmed());
for line in output.lines().take(20) {
println!(" {}", line.dimmed());
}
if output.lines().count() > 20 {
println!(" {} ... ({} more lines)", "...".dimmed(), output.lines().count() - 20);
}
println!("{}", "─".repeat(50).dimmed());
println!();
}
state.complete_phase(WorkflowPhase::Transpilation);
state.save(state_file)?;
crate::cli::workflow::display_workflow_progress(state);
println!("{}", "💡 Next Steps:".bright_green().bold());
println!(
" {} Check output directory: {:?}",
"1.".bright_blue(),
config.transpilation.output_dir
);
println!(" {} Run {} to optimize", "2.".bright_blue(), "batuta optimize".cyan());
println!(" {} Run {} to validate", "3.".bright_blue(), "batuta validate".cyan());
println!();
if repl {
run_ruchy_repl(config)?;
}
Ok(())
}
fn run_ruchy_repl(config: &BatutaConfig) -> anyhow::Result<()> {
let tools = ToolRegistry::detect();
if tools.ruchy.is_none() {
println!("{} Ruchy is not installed", "✗".red());
println!();
println!("Install Ruchy to use the interactive REPL:");
println!(" {}", "cargo install ruchy".cyan());
anyhow::bail!("Ruchy not found in PATH");
}
print_repl_banner(config);
let stdin = std::io::stdin();
let mut buffer = String::new();
loop {
print_repl_prompt(&buffer);
let mut line = String::new();
if stdin.read_line(&mut line)? == 0 {
break;
}
match process_repl_line(&line, &mut buffer, config) {
ReplAction::Continue => {}
ReplAction::Quit => break,
}
}
println!();
println!("{}", "Ruchy REPL session ended.".dimmed());
Ok(())
}
enum ReplAction {
Continue,
Quit,
}
fn print_repl_banner(config: &BatutaConfig) {
println!("{}", "🔬 Ruchy REPL".bright_cyan().bold());
println!("{}", "─".repeat(50).dimmed());
println!(" Output dir: {}", config.transpilation.output_dir.display());
println!(
" Strictness: {}",
config.transpilation.ruchy_strictness.as_deref().unwrap_or("gradual")
);
println!();
println!(" Type Ruchy code, then press Enter twice to execute.");
println!(" Commands: {} {} {}", ":help".cyan(), ":clear".cyan(), ":quit".cyan());
println!("{}", "─".repeat(50).dimmed());
println!();
}
fn print_repl_prompt(buffer: &str) {
if buffer.is_empty() {
eprint!("{} ", "ruchy>".bright_blue());
} else {
eprint!("{} ", " ..>".dimmed());
}
}
fn process_repl_line(line: &str, buffer: &mut String, config: &BatutaConfig) -> ReplAction {
let trimmed = line.trim();
if buffer.is_empty() {
match trimmed {
":quit" | ":q" | ":exit" => return ReplAction::Quit,
":help" | ":h" => {
print_repl_help();
return ReplAction::Continue;
}
":clear" | ":c" => {
buffer.clear();
println!("{}", "Buffer cleared.".dimmed());
return ReplAction::Continue;
}
_ => {}
}
}
if trimmed.is_empty() && !buffer.is_empty() {
execute_repl_snippet(buffer, config);
buffer.clear();
return ReplAction::Continue;
}
if !trimmed.is_empty() {
buffer.push_str(line);
}
ReplAction::Continue
}
fn print_repl_help() {
println!();
println!("{}", "Ruchy REPL Commands:".bright_yellow().bold());
println!(" {} — Show this help", ":help".cyan());
println!(" {} — Clear the input buffer", ":clear".cyan());
println!(" {} — Exit the REPL", ":quit".cyan());
println!();
println!("{}", "Usage:".bright_yellow().bold());
println!(" Type code, press Enter twice to transpile and run.");
println!(" Multi-line input is supported — keep typing until");
println!(" you enter a blank line.");
println!();
}
fn execute_repl_snippet(code: &str, config: &BatutaConfig) {
let tmp_dir = std::env::temp_dir();
let snippet_path = tmp_dir.join("batuta_repl_snippet.rcy");
if let Err(e) = std::fs::write(&snippet_path, code) {
println!("{} Failed to write snippet: {}", "✗".red(), e);
return;
}
let snippet_str = snippet_path.to_string_lossy().to_string();
let strictness = config.transpilation.ruchy_strictness.as_deref().unwrap_or("gradual");
let args: Vec<&str> = vec!["run", "--strictness", strictness, &snippet_str];
println!("{}", "─".repeat(50).dimmed());
match tools::run_tool("ruchy", &args, None) {
Ok(output) => {
if output.trim().is_empty() {
println!("{}", "(no output)".dimmed());
} else {
println!("{}", output);
}
}
Err(e) => {
println!("{} {}", "Error:".red(), e);
}
}
println!("{}", "─".repeat(50).dimmed());
println!();
let _ = std::fs::remove_file(&snippet_path);
}
fn handle_transpile_failure(
e: anyhow::Error,
state: &mut WorkflowState,
state_file: &Path,
config: &BatutaConfig,
transpiler: &ToolInfo,
) -> anyhow::Result<()> {
println!();
println!("{}", "❌ Transpilation failed!".red().bold());
println!();
println!("{}: {}", "Error".bold(), e.to_string().red());
println!();
state.fail_phase(WorkflowPhase::Transpilation, e.to_string());
state.save(state_file)?;
crate::cli::workflow::display_workflow_progress(state);
println!("{}", "💡 Troubleshooting:".bright_yellow().bold());
println!(" {} Verify {} is properly installed", "•".bright_blue(), transpiler.name.cyan());
println!(" {} Check that source path is correct: {:?}", "•".bright_blue(), config.source.path);
println!(" {} Try running with {} for more details", "•".bright_blue(), "--verbose".cyan());
println!(
" {} See transpiler docs: {}",
"•".bright_blue(),
format!("https://github.com/paiml/{}", transpiler.name).cyan()
);
println!();
Err(e)
}