nex-pkg 0.18.0

Package manager UX for nix-darwin + homebrew
Documentation
use std::path::Path;

use anyhow::{bail, Result};

use crate::profile_fragment::{find_fragment_files, infer_fragment_path_id, ProfileFragmentDocument};

pub fn run_validate(path: &Path) -> Result<()> {
    if path.is_dir() {
        let files = find_fragment_files(path)?;
        let mut count = 0usize;
        for file in files {
            if validate_file(&file)? {
                count += 1;
            }
        }
        eprintln!("{} profile fragments valid", count);
    } else {
        ProfileFragmentDocument::from_path(path)?;
        eprintln!("{} is valid", path.display());
    }
    Ok(())
}

pub fn run_inspect(path: &Path) -> Result<()> {
    if path.is_dir() {
        bail!("profile-fragment inspect expects a TOML file, not a directory");
    }
    let document = ProfileFragmentDocument::from_path(path)?;
    let fragment = &document.fragment;

    println!("\nProfile Fragment");
    print_kv("ID", &fragment.id);
    print_kv("Name", &fragment.name);
    print_kv("Category", &fragment.category.to_string());
    print_kv(
        "Platforms",
        &fragment
            .platforms
            .iter()
            .map(ToString::to_string)
            .collect::<Vec<_>>()
            .join(", "),
    );
    if let Some(description) = &fragment.description {
        print_kv("Description", description);
    }
    if let Some(visibility) = &fragment.visibility {
        print_kv("Visibility", &visibility.to_string());
    }
    if !fragment.requires.is_empty() {
        print_kv("Requires", &fragment.requires.join(", "));
    }
    if !fragment.conflicts.is_empty() {
        print_kv("Conflicts", &fragment.conflicts.join(", "));
    }
    if let Some(safety) = &fragment.safety {
        println!("\nSafety");
        print_kv(
            "Mutates system services",
            &safety.mutates_system_services.to_string(),
        );
        print_kv(
            "Mutates hardware drivers",
            &safety.mutates_hardware_drivers.to_string(),
        );
        print_kv("Requires confirmation", &safety.requires_confirmation.to_string());
    }

    Ok(())
}

fn validate_file(path: &Path) -> Result<bool> {
    let content = std::fs::read_to_string(path)?;
    if !content.contains("[fragment]") {
        return Ok(false);
    }
    let document = ProfileFragmentDocument::from_str(&content)?;
    if let Some(path_id) = infer_fragment_path_id(path) {
        if document.fragment.id != path_id {
            bail!(
                "fragment id '{}' does not match path-derived id '{}' for {}",
                document.fragment.id,
                path_id,
                path.display()
            );
        }
    }
    Ok(true)
}

fn print_kv(key: &str, value: &str) {
    println!("  {key}: {value}");
}