mecha10-cli 0.1.47

Mecha10 CLI tool
Documentation
//! Format command handler
//!
//! Orchestrates code formatting.

use crate::context::CliContext;
use crate::paths;
use anyhow::Result;

/// Arguments for format command
#[derive(Debug, Clone)]
pub struct FormatArgs {
    pub check: bool,
}

/// Handle format command
///
/// Formats all project code.
///
/// # Arguments
///
/// * `ctx` - CLI execution context
/// * `args` - Format command arguments
pub async fn handle_format(ctx: &mut CliContext, args: &FormatArgs) -> Result<()> {
    println!();
    if args.check {
        println!("🔍 Code Format Check");
    } else {
        println!("✨ Code Formatting");
    }
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    println!();

    // Check project initialization
    if !ctx.is_project_initialized() {
        return Err(anyhow::anyhow!(
            "Not in a Mecha10 project directory.\n\
             Run 'mecha10 init' to create a new project."
        ));
    }

    let project = ctx.project()?;
    println!("Project: {}", project.root().display());
    println!();

    let mut all_passed = true;

    // Rust formatting (rustfmt)
    println!("Formatting Rust code...");
    let rustfmt_result = if args.check {
        std::process::Command::new("cargo")
            .args(["fmt", "--all", "--", "--check"])
            .current_dir(project.root())
            .status()
    } else {
        std::process::Command::new("cargo")
            .args(["fmt", "--all"])
            .current_dir(project.root())
            .status()
    };

    match rustfmt_result {
        Ok(status) if status.success() => {
            if args.check {
                println!("✅ Rust code is formatted correctly");
            } else {
                println!("✅ Rust code formatted");
            }
        }
        Ok(_) => {
            if args.check {
                println!("❌ Rust code needs formatting");
                println!("   Run: mecha10 format");
            } else {
                println!("❌ Rust formatting failed");
            }
            all_passed = false;
        }
        Err(e) => {
            println!("âš ī¸  Could not run rustfmt: {}", e);
            println!("   Install with: rustup component add rustfmt");
        }
    }
    println!();

    // Python formatting (ruff format)
    println!("Formatting Python code...");
    let python_dir = project.root().join(paths::framework::TASKRUNNER_DIR);
    if python_dir.exists() {
        let ruff_result = if args.check {
            std::process::Command::new("ruff")
                .args(["format", "--check", "."])
                .current_dir(&python_dir)
                .status()
        } else {
            std::process::Command::new("ruff")
                .args(["format", "."])
                .current_dir(&python_dir)
                .status()
        };

        match ruff_result {
            Ok(status) if status.success() => {
                if args.check {
                    println!("✅ Python code is formatted correctly");
                } else {
                    println!("✅ Python code formatted");
                }
            }
            Ok(_) => {
                if args.check {
                    println!("❌ Python code needs formatting");
                    println!("   Run: mecha10 format");
                } else {
                    println!("❌ Python formatting failed");
                }
                all_passed = false;
            }
            Err(e) => {
                println!("âš ī¸  Could not run ruff: {}", e);
                println!("   Install with: pip install ruff");
            }
        }
    } else {
        println!("â„šī¸  No Python code found, skipping");
    }
    println!();

    // GDScript formatting
    println!("Formatting GDScript code...");
    // Determine Godot project path based on context
    let godot_dir = if let Ok(framework_path) = std::env::var("MECHA10_FRAMEWORK_PATH") {
        // Framework dev mode - use monorepo path
        std::path::PathBuf::from(framework_path).join(paths::framework::SIMULATION_GODOT_DIR)
    } else {
        // Generated project mode - use relative path
        project.root().join(paths::project::SIMULATION_GODOT_DIR)
    };

    if godot_dir.exists() {
        let gdformat_result = if args.check {
            std::process::Command::new("gdformat")
                .args(["--check", "."])
                .current_dir(&godot_dir)
                .status()
        } else {
            std::process::Command::new("gdformat")
                .arg(".")
                .current_dir(&godot_dir)
                .status()
        };

        match gdformat_result {
            Ok(status) if status.success() => {
                if args.check {
                    println!("✅ GDScript code is formatted correctly");
                } else {
                    println!("✅ GDScript code formatted");
                }
            }
            Ok(_) => {
                if args.check {
                    println!("❌ GDScript code needs formatting");
                } else {
                    println!("❌ GDScript formatting failed");
                }
                all_passed = false;
            }
            Err(_) => {
                println!("â„šī¸  gdformat not available, skipping");
            }
        }
    } else {
        println!("â„šī¸  No GDScript code found, skipping");
    }
    println!();

    // TOML formatting (taplo)
    println!("Formatting TOML files...");
    // Find all TOML files in the project
    let toml_files: Vec<_> = walkdir::WalkDir::new(project.root())
        .into_iter()
        .filter_map(|e| e.ok())
        .filter(|e| {
            let path = e.path();
            path.extension().is_some_and(|ext| ext == "toml")
                && !path.to_string_lossy().contains("/target/")
                && !path.to_string_lossy().contains("/node_modules/")
        })
        .map(|e| e.path().to_path_buf())
        .collect();

    if toml_files.is_empty() {
        println!("â„šī¸  No TOML files found, skipping");
    } else {
        let taplo_result = if args.check {
            std::process::Command::new("taplo")
                .arg("format")
                .arg("--check")
                .args(&toml_files)
                .current_dir(project.root())
                .status()
        } else {
            std::process::Command::new("taplo")
                .arg("format")
                .args(&toml_files)
                .current_dir(project.root())
                .status()
        };

        match taplo_result {
            Ok(status) if status.success() => {
                if args.check {
                    println!("✅ TOML files are formatted correctly");
                } else {
                    println!("✅ TOML files formatted");
                }
            }
            Ok(_) => {
                if args.check {
                    println!("❌ TOML files need formatting");
                    println!("   Run: mecha10 format");
                } else {
                    println!("❌ TOML formatting failed");
                }
                all_passed = false;
            }
            Err(e) => {
                println!("âš ī¸  Could not run taplo: {}", e);
                println!("   Install with: cargo install taplo-cli --locked");
            }
        }
    }
    println!();

    // Summary
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    if all_passed {
        if args.check {
            println!("✅ All code is formatted correctly!");
        } else {
            println!("✅ All code formatted successfully!");
        }
    } else {
        if args.check {
            println!("❌ Some code needs formatting");
            println!();
            println!("Run without --check to format:");
            println!("  mecha10 format");
        } else {
            println!("❌ Some formatting operations failed");
        }
        return Err(anyhow::anyhow!("Formatting check failed"));
    }
    println!();

    Ok(())
}