muntjac 0.2.0

Translate uv.lock into Buck2 build rules
Documentation
use anyhow::{Context, Result};
use clap::Args;
use std::fs;
use std::path::{Path, PathBuf};
use std::str::FromStr;

use crate::cli::Globals;
use crate::config::Config;

#[derive(Args, Debug)]
pub struct ConfigCheckArgs {
    #[arg(long, default_value = "human")]
    pub format: ConfigCheckFormat,
}

#[derive(clap::ValueEnum, Clone, Debug)]
pub enum ConfigCheckFormat {
    Human,
    Json,
}

pub fn run(args: ConfigCheckArgs, globals: &Globals) -> Result<()> {
    let cwd = globals.workdir().context("resolving working directory")?;
    let cfg_path = cwd.join("muntjac.toml");

    let bytes = fs::read_to_string(&cfg_path)
        .with_context(|| format!("reading muntjac.toml at {}", cfg_path.display()))?;
    let config = Config::from_str(&bytes)
        .with_context(|| format!("parsing muntjac.toml at {}", cfg_path.display()))?;
    config
        .validate()
        .with_context(|| format!("validating muntjac.toml at {}", cfg_path.display()))?;

    check_paths(&cfg_path, &config)?;

    match args.format {
        ConfigCheckFormat::Human => {
            println!(
                "muntjac.toml ok: {} platforms, {} trees",
                config.platforms.len(),
                config.trees.len()
            );
        }
        ConfigCheckFormat::Json => {
            let report = serde_json::json!({
                "ok": true,
                "platforms": config.platforms.len(),
                "trees": config.trees.len(),
            });
            println!("{}", report);
        }
    }
    Ok(())
}

fn check_paths(cfg_path: &Path, config: &Config) -> Result<()> {
    let base = cfg_path.parent().unwrap_or(Path::new("."));
    for tree in &config.trees {
        let manifest = resolve(base, &tree.manifest_path);
        anyhow::ensure!(
            manifest.exists(),
            "manifest_path for tree `{}` does not exist: {}",
            tree.name,
            manifest.display()
        );
        let tpd = resolve(base, &tree.third_party_dir);
        if !tpd.exists() {
            fs::create_dir_all(&tpd).with_context(|| {
                format!(
                    "creating third_party_dir for tree `{}`: {}",
                    tree.name,
                    tpd.display()
                )
            })?;
        }
    }
    Ok(())
}

fn resolve(base: &Path, p: &Path) -> PathBuf {
    if p.is_absolute() {
        p.to_path_buf()
    } else {
        base.join(p)
    }
}