torrust-linting 0.1.0

Linting utilities for Torrust projects
Documentation
use std::process::Command;
use std::time::Instant;

use anyhow::Result;
use tracing::{error, info};

use crate::utils::is_command_available;

/// Install Taplo CLI using cargo
///
/// # Errors
///
/// Returns an error if cargo is not available or if the installation fails.
fn install_taplo() -> Result<()> {
    info!("Installing Taplo CLI...");

    // Check if cargo is available
    if !is_command_available("cargo") {
        error!("Cargo is required to install Taplo CLI");
        return Err(anyhow::anyhow!("Cargo is not available"));
    }

    let output = Command::new("cargo").args(["install", "taplo-cli", "--locked"]).output()?;

    if output.status.success() {
        info!("Taplo CLI installed successfully");
        Ok(())
    } else {
        let stderr = String::from_utf8_lossy(&output.stderr);
        error!("Failed to install Taplo CLI: {}", stderr);
        Err(anyhow::anyhow!("Failed to install Taplo CLI"))
    }
}

/// Run the TOML linter using Taplo
///
/// # Errors
///
/// Returns an error if Taplo is not available, cannot be installed,
/// or if the linting fails.
pub fn run_toml_linter() -> Result<()> {
    // Check if taplo is installed
    if !is_command_available("taplo") {
        install_taplo()?;
    }

    let t = Instant::now();
    info!(target: "toml", "Scanning TOML files...");

    // Run taplo check with recursive glob pattern
    let check_output = Command::new("taplo").args(["check", "**/*.toml"]).output()?;

    if !check_output.status.success() {
        let stderr = String::from_utf8_lossy(&check_output.stderr);
        let stdout = String::from_utf8_lossy(&check_output.stdout);

        // Print the output from taplo
        if !stdout.is_empty() {
            println!("{stdout}");
        }
        if !stderr.is_empty() {
            eprintln!("{stderr}");
        }

        println!();
        error!(target: "toml", "TOML linting failed. Please fix the issues above. ({:.3}s)", t.elapsed().as_secs_f64());
        return Err(anyhow::anyhow!("TOML linting failed"));
    }

    // Run taplo format check with recursive glob pattern
    let format_output = Command::new("taplo").args(["fmt", "--check", "**/*.toml"]).output()?;

    if format_output.status.success() {
        info!(target: "toml", "All TOML files passed linting and formatting checks! ({:.3}s)", t.elapsed().as_secs_f64());
        Ok(())
    } else {
        let stderr = String::from_utf8_lossy(&format_output.stderr);
        let stdout = String::from_utf8_lossy(&format_output.stdout);

        // Print the output from taplo
        if !stdout.is_empty() {
            println!("{stdout}");
        }
        if !stderr.is_empty() {
            eprintln!("{stderr}");
        }

        println!();
        error!(target: "toml", "TOML formatting failed. Please fix the issues above. ({:.3}s)", t.elapsed().as_secs_f64());
        error!(target: "toml", "Run 'taplo fmt **/*.toml' to auto-fix formatting issues.");
        Err(anyhow::anyhow!("TOML formatting failed"))
    }
}