xacli 0.2.1

A modern, developer-friendly CLI framework for Rust
Documentation
//! Documentation build command

use std::path::{Path, PathBuf};
use xacli::testing::spec::{SpecFile, TapeFile};
use xacli::Context;

/// Handler for `doc build` command
pub fn build_command(ctx: &mut dyn Context) -> xacli::Result<()> {
    let info = ctx.info();
    let input_value = info
        .args
        .get("directory")
        .ok_or_else(|| xacli::Error::custom("Missing directory argument"))?;

    let directory: String = input_value
        .clone()
        .try_into()
        .map_err(|e| xacli::Error::custom(format!("Failed to parse directory: {}", e)))?;

    let dir_path = Path::new(&directory);

    if !dir_path.exists() {
        return Err(xacli::Error::custom(format!(
            "Directory does not exist: {}",
            directory
        )));
    }

    if !dir_path.is_dir() {
        return Err(xacli::Error::custom(format!(
            "Path is not a directory: {}",
            directory
        )));
    }

    // Create output directories
    let xacli_dir = Path::new(".xacli");
    let tapes_dir = xacli_dir.join("tapes");

    std::fs::create_dir_all(&tapes_dir)
        .map_err(|e| xacli::Error::custom(format!("Failed to create tapes directory: {}", e)))?;

    // Create .gitignore if it doesn't exist
    let gitignore_path = xacli_dir.join(".gitignore");
    if !gitignore_path.exists() {
        std::fs::write(&gitignore_path, "gifs/\nreports/\n")
            .map_err(|e| xacli::Error::custom(format!("Failed to create .gitignore: {}", e)))?;
    }

    // Find all .xacli.hcl files
    let pattern = format!("{}/**/*.xacli.hcl", directory);
    let paths: Vec<PathBuf> = glob::glob(&pattern)
        .map_err(|e| xacli::Error::custom(format!("Failed to read glob pattern: {}", e)))?
        .filter_map(Result::ok)
        .collect();

    if paths.is_empty() {
        println!("No .xacli.hcl files found in {}", directory);
        return Ok(());
    }

    let mut converted = 0;
    let total = paths.len();

    for hcl_path in paths {
        match convert_to_tape(&hcl_path, &tapes_dir) {
            Ok(tape_path) => {
                converted += 1;
                use owo_colors::OwoColorize;
                println!("{} Generated: {}", "".green().bold(), tape_path.display());
            }
            Err(e) => {
                use owo_colors::OwoColorize;
                eprintln!(
                    "{} Error processing {}: {}",
                    "".red().bold(),
                    hcl_path.display(),
                    e
                );
            }
        }
    }

    println!("\nProcessed {}/{} files successfully", converted, total);

    Ok(())
}

/// Convert .xacli.hcl file to tape file
fn convert_to_tape(
    hcl_path: &Path,
    tapes_dir: &Path,
) -> Result<PathBuf, Box<dyn std::error::Error>> {
    // Parse .xacli.hcl file
    let spec = SpecFile::parse_file(hcl_path)?;

    // Get base name for output files
    let base_name = hcl_path
        .file_stem()
        .and_then(|s| s.to_str())
        .ok_or("Invalid filename")?
        .trim_end_matches(".xacli");

    // Require CLI config for examples
    let cli_config = spec
        .cli
        .as_ref()
        .ok_or("CLI config is required in .xacli.hcl file")?;

    // Prioritize examples over suites for doc build
    let tape = if !spec.example.is_empty() {
        TapeFile::from((cli_config, &spec.example))
    } else {
        // For test suites, use CLI path from config
        TapeFile::from((cli_config.path.as_str(), &spec))
    };

    // Generate tape path
    let tape_path = tapes_dir.join(format!("{}.tape", base_name));

    // Save tape file
    tape.save(&tape_path)?;

    Ok(tape_path)
}