ruchy 4.2.1

A systems scripting language that transpiles to idiomatic Rust with extreme quality engineering
Documentation
//! Handler for `ruchy build` command (CARGO-004)
//!
//! Wrapper around `cargo build` for Ruchy projects

use anyhow::{Context, Result};
use std::path::Path;
use std::process::Command;

/// Handle `ruchy build` command - wrapper around cargo build
///
/// # Arguments
///
/// * `release` - Whether to build in release mode (--release)
/// * `verbose` - Enable verbose output
///
/// # Examples
///
/// ```no_run
/// # use ruchy::handle_build_command;
/// // Debug build
/// handle_build_command(false, false).expect("Failed to build");
///
/// // Release build
/// handle_build_command(true, false).expect("Failed to build");
/// ```
///
/// # Errors
///
/// Returns an error if:
/// - Cargo.toml doesn't exist (not in a Cargo project)
/// - cargo build command fails
///
/// # Complexity
///
/// Complexity: 6 (within Toyota Way limits ≤10)
pub fn handle_build_command(release: bool, verbose: bool) -> Result<()> {
    // Step 1: Verify we're in a Cargo project
    verify_cargo_project()?;

    // Step 2: Run cargo build
    run_cargo_build(release, verbose)?;

    // Step 3: Print success message
    print_build_success_message(release);

    Ok(())
}

/// Verify that Cargo.toml exists in current directory
///
/// # Complexity
///
/// Complexity: 2 (within Toyota Way limits ≤10)
fn verify_cargo_project() -> Result<()> {
    let cargo_toml = Path::new("Cargo.toml");
    if !cargo_toml.exists() {
        anyhow::bail!(
            "Cargo.toml not found. Run this command from a Cargo project directory.\n\
             Hint: Use `ruchy new <name>` to create a new Ruchy project."
        );
    }
    Ok(())
}

/// Run `cargo build` command
///
/// # Complexity
///
/// Complexity: 7 (within Toyota Way limits ≤10)
fn run_cargo_build(release: bool, verbose: bool) -> Result<()> {
    let mut cmd = Command::new("cargo");
    cmd.arg("build");

    if release {
        cmd.arg("--release");
    }

    if verbose {
        cmd.arg("--verbose");
        let mode = if release { "release" } else { "debug" };
        println!("Running: cargo build --{mode}");
    }

    let output = cmd
        .output()
        .context("Failed to run cargo build - ensure cargo is installed")?;

    // Always show cargo output
    if !output.stdout.is_empty() {
        let stdout = String::from_utf8_lossy(&output.stdout);
        print!("{stdout}");
    }

    if !output.stderr.is_empty() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        eprint!("{stderr}");
    }

    if !output.status.success() {
        anyhow::bail!("cargo build failed");
    }

    Ok(())
}

/// Print success message after building
///
/// # Complexity
///
/// Complexity: 2 (within Toyota Way limits ≤10)
fn print_build_success_message(release: bool) {
    let mode = if release { "release" } else { "debug" };
    println!("Build complete ({mode} mode)");
    println!("Run `cargo run` to execute the program");
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::env;
    use tempfile::TempDir;

    /// Test helper: Create a temporary Cargo project for testing
    fn create_test_project() -> (TempDir, String) {
        let temp_dir = TempDir::new().expect("Failed to create temp dir");
        let project_name = "test_project";
        let project_path = temp_dir.path().join(project_name);

        // Use cargo new to create real project
        std::process::Command::new("cargo")
            .arg("new")
            .arg(project_name)
            .current_dir(temp_dir.path())
            .output()
            .expect("Failed to run cargo new");

        (
            temp_dir,
            project_path
                .to_str()
                .expect("Project path should be valid UTF-8")
                .to_string(),
        )
    }

    #[test]
    fn test_verify_cargo_project_missing_cargo_toml() {
        let temp_dir = TempDir::new().expect("Failed to create temp dir");
        let _original_dir = env::current_dir().expect("Failed to get current dir");

        // Change to temp dir without Cargo.toml
        env::set_current_dir(temp_dir.path()).expect("Failed to change dir");

        let result = verify_cargo_project();

        // Change back to original dir
        env::set_current_dir(_original_dir).expect("Failed to restore dir");

        assert!(result.is_err(), "Should fail when Cargo.toml doesn't exist");
        assert!(
            result
                .unwrap_err()
                .to_string()
                .contains("Cargo.toml not found"),
            "Error message should mention Cargo.toml"
        );
    }

    #[test]
    fn test_verify_cargo_project_success() {
        let (_temp_dir, project_path) = create_test_project();
        let _original_dir = env::current_dir().expect("Failed to get current dir");

        // Change to project dir
        env::set_current_dir(&project_path).expect("Failed to change dir");

        let result = verify_cargo_project();

        // Change back to original dir
        env::set_current_dir(_original_dir).expect("Failed to restore dir");

        assert!(result.is_ok(), "Should succeed when Cargo.toml exists");
    }

    #[test]
    fn test_print_build_success_message_debug() {
        // This test just ensures the function doesn't panic
        print_build_success_message(false);
    }

    #[test]
    fn test_print_build_success_message_release() {
        // This test just ensures the function doesn't panic
        print_build_success_message(true);
    }

    // Property-based test: verify_cargo_project is idempotent
    #[test]
    fn test_verify_cargo_project_idempotent() {
        let (_temp_dir, project_path) = create_test_project();
        let _original_dir = env::current_dir().expect("Failed to get current dir");

        env::set_current_dir(&project_path).expect("Failed to change dir");

        let result1 = verify_cargo_project();
        let result2 = verify_cargo_project();

        env::set_current_dir(_original_dir).expect("Failed to restore dir");

        assert_eq!(
            result1.is_ok(),
            result2.is_ok(),
            "Multiple calls should have same result"
        );
    }

    // ===== EXTREME TDD Round 150 - Build Handler Tests =====

    #[test]
    fn test_print_build_success_message_formats() {
        // Verify both modes produce output
        print_build_success_message(true);
        print_build_success_message(false);
    }

    #[test]
    fn test_verify_cargo_project_cwd() {
        // Test current directory
        let result = verify_cargo_project();
        // May succeed or fail depending on where tests are run
        let _ = result;
    }

    #[test]
    fn test_run_cargo_build_no_project() {
        let temp_dir = TempDir::new().expect("Failed to create temp dir");
        let _original_dir = env::current_dir().expect("Failed to get current dir");

        env::set_current_dir(temp_dir.path()).expect("Failed to change dir");

        // Create a minimal Cargo.toml for testing
        std::fs::write(
            temp_dir.path().join("Cargo.toml"),
            "[package]\nname = \"test\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
        )
        .expect("Failed to write Cargo.toml");

        // Create src/main.rs
        std::fs::create_dir(temp_dir.path().join("src")).ok();
        std::fs::write(temp_dir.path().join("src/main.rs"), "fn main() {}")
            .expect("Failed to write main.rs");

        let result = run_cargo_build(false, false);
        env::set_current_dir(_original_dir).expect("Failed to restore dir");

        // Should succeed with valid project
        assert!(result.is_ok());
    }

    #[test]
    fn test_run_cargo_build_verbose() {
        let temp_dir = TempDir::new().expect("Failed to create temp dir");
        let _original_dir = env::current_dir().expect("Failed to get current dir");

        env::set_current_dir(temp_dir.path()).expect("Failed to change dir");

        std::fs::write(
            temp_dir.path().join("Cargo.toml"),
            "[package]\nname = \"test2\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
        )
        .expect("Failed to write Cargo.toml");

        std::fs::create_dir(temp_dir.path().join("src")).ok();
        std::fs::write(
            temp_dir.path().join("src/main.rs"),
            "fn main() { println!(\"Hello\"); }",
        )
        .expect("Failed to write main.rs");

        let result = run_cargo_build(false, true); // verbose
        env::set_current_dir(_original_dir).expect("Failed to restore dir");

        assert!(result.is_ok());
    }

    #[test]
    fn test_handle_build_command_no_cargo() {
        let temp_dir = TempDir::new().expect("Failed to create temp dir");
        let _original_dir = env::current_dir().expect("Failed to get current dir");

        env::set_current_dir(temp_dir.path()).expect("Failed to change dir");

        let result = handle_build_command(false, false);

        env::set_current_dir(_original_dir).expect("Failed to restore dir");

        // Should fail - no Cargo.toml
        assert!(result.is_err());
    }

    #[test]
    fn test_handle_build_command_release_mode() {
        // Just verify the function signature works
        // Actual build test is expensive
        let temp_dir = TempDir::new().expect("Failed to create temp dir");
        let _original_dir = env::current_dir().expect("Failed to get current dir");

        env::set_current_dir(temp_dir.path()).expect("Failed to change dir");

        let _ = handle_build_command(true, false);

        env::set_current_dir(_original_dir).expect("Failed to restore dir");
    }

    #[test]
    fn test_handle_build_command_all_options() {
        let temp_dir = TempDir::new().expect("Failed to create temp dir");
        let _original_dir = env::current_dir().expect("Failed to get current dir");

        env::set_current_dir(temp_dir.path()).expect("Failed to change dir");

        let _ = handle_build_command(true, true); // release + verbose

        env::set_current_dir(_original_dir).expect("Failed to restore dir");
    }
}