bettertui_engine 0.1.1

High-performance terminal UI framework
Documentation
use std::env;
use std::fs;
use std::path::Path;

fn main() {
    napi_build::setup();

    println!("cargo::rerun-if-changed=fonts/glyphnames.json");

    let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
    let json_path = Path::new(&manifest_dir).join("fonts/glyphnames.json");
    let json_data = fs::read_to_string(&json_path).expect("Failed to read glyphnames.json");

    let raw: serde_json::Value = serde_json::from_str(&json_data).expect("Failed to parse glyphnames.json");

    let mut icons: Vec<(u32, String, String)> = Vec::new();
    let mut unknown_names: Vec<String> = Vec::new();

    if let Some(obj) = raw.as_object() {
        for (key, val) in obj {
            if key == "METADATA" {
                continue;
            }
            let code_str = val.get("code").and_then(|c| c.as_str()).unwrap_or("");
            let codepoint = u32::from_str_radix(code_str, 16).unwrap_or(0);
            if codepoint == 0 {
                unknown_names.push(key.clone());
                continue;
            }

            let (name, category) = classify_icon(key);
            icons.push((codepoint, name, category));
        }
    }

    icons.sort_by_key(|(cp, _, _)| *cp);

    if !unknown_names.is_empty() {
        println!(
            "cargo::warning=Font build: {} entries without valid codepoint. First few: {:?}",
            unknown_names.len(),
            &unknown_names[..unknown_names.len().min(5)]
        );
    }

    let out_dir = env::var("OUT_DIR").unwrap();
    let out_path = Path::new(&out_dir).join("builtin.rs");

    let mut output = String::new();
    output.push_str("// Auto-generated by build.rs from glyphnames.json\n");
    output.push_str("// Do not edit manually.\n\n");
    output.push_str("#[allow(dead_code)]\n");
    output.push_str("pub(crate) struct BuiltinIcon {\n");
    output.push_str("    pub codepoint: u32,\n");
    output.push_str("    pub name: &'static str,\n");
    output.push_str("    pub category: &'static str,\n");
    output.push_str("}\n\n");
    output.push_str("#[allow(dead_code)]\n");
    output.push_str(&format!("pub(crate) const BUILTIN_ICON_COUNT: usize = {};\n\n", icons.len()));
    output.push_str("#[allow(dead_code)]\n");
    output.push_str("pub(crate) static BUILTIN_ICONS: &[BuiltinIcon] = &[\n");

    for (cp, name, category) in &icons {
        output.push_str(&format!(
            "    BuiltinIcon {{ codepoint: 0x{:04X}, name: \"{}\", category: \"{}\" }},\n",
            cp,
            name.escape_default(),
            category
        ));
    }

    output.push_str("];\n");

    output.push_str("\n#[allow(dead_code)]\n");
    output.push_str("pub(crate) static BUILTIN_CODEPOINTS: &[u32] = &[\n");
    for (cp, _, _) in &icons {
        output.push_str(&format!("    0x{:04X},\n", cp));
    }
    output.push_str("];\n");

    fs::write(&out_path, &output).expect("Failed to write builtin.rs");

    let icon_count = icons.len();
    println!("cargo::warning=Generated {} built-in NerdFont icon entries", icon_count);
}

fn classify_icon(key: &str) -> (String, String) {
    let parts: Vec<&str> = key.splitn(2, '-').collect();
    let category = if parts.len() >= 2 { parts[0].to_string() } else { "unknown".to_string() };

    (key.to_string(), category)
}