colorant 0.8.0

Per-directory terminal theme switcher with system dark/light mode support
//! Scan `themes/*.colorant` at build time and emit a generated
//! `$OUT_DIR/bundled_themes.rs` containing a sorted slice of
//! `(name, contents)` pairs. `src/theme/bundled.rs` `include!`s the result.

use std::env;
use std::fs;
use std::path::Path;

// Keep in sync with `src/theme/resolve.rs::PALETTE_EXTENSION`. We can't
// `use` library code from a build script, so the const is duplicated here.
const PALETTE_EXTENSION: &str = "colorant";

fn main() {
    let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is set by cargo");
    let themes_dir = Path::new(&manifest_dir).join("themes");
    let out_dir = env::var("OUT_DIR").expect("OUT_DIR is set by cargo");
    let out_path = Path::new(&out_dir).join("bundled_themes.rs");

    // Two-tier rebuild trigger: the directory watch catches additions and
    // removals (changes the dir's mtime), the per-file watches emitted below
    // catch in-place edits to existing palettes. Both are needed.
    println!("cargo:rerun-if-changed=themes");

    let mut paths: Vec<_> = fs::read_dir(&themes_dir)
        .unwrap_or_else(|e| panic!("reading {}: {}", themes_dir.display(), e))
        .filter_map(|e| e.ok().map(|e| e.path()))
        .filter(|p| p.extension().and_then(|s| s.to_str()) == Some(PALETTE_EXTENSION))
        .collect();
    // Sort by file stem so `tokyo-night` precedes `tokyo-night-day` (which
    // matches natural reading order). A path-level sort would invert that
    // pair because `.` > `-` byte-wise.
    paths.sort_by(|a, b| a.file_stem().cmp(&b.file_stem()));

    let mut out = String::from("// Generated by build.rs — do not edit.\n");
    // Match the visibility of `src/theme/bundled` (declared `pub(crate)`).
    out.push_str("pub(crate) const BUNDLED_THEMES: &[(&str, &str)] = &[\n");
    for path in paths {
        let name = path
            .file_stem()
            .and_then(|s| s.to_str())
            .unwrap_or_else(|| panic!("non-UTF-8 theme filename: {}", path.display()));
        let content = fs::read_to_string(&path)
            .unwrap_or_else(|e| panic!("reading {}: {}", path.display(), e));
        out.push_str(&format!("    ({:?}, {:?}),\n", name, content));
        println!("cargo:rerun-if-changed={}", path.display());
    }
    out.push_str("];\n");

    fs::write(&out_path, out).unwrap_or_else(|e| panic!("writing {}: {}", out_path.display(), e));
}