ishou-render 0.1.6

ishou — target-specific renderers for the pleme-io design token set
Documentation
//! Nix renderer — emits an importable Nix expression for the Nord
//! palette in the shape every pleme-io HM wrapper historically carried
//! locally (`{ polar = { night0 = "…"; … }; snow = …; frost = …;
//! aurora = …; }`).
//!
//! Closes the M5 `no-foreign-nord-source` invariant for the
//! `blackmatter-ghostty` / `blackmatter-mado` / `blackmatter-opencode`
//! wrappers: each currently `import ./module/themes/nord/colors.nix`
//! their own copy of the palette. After M6.1 they `import` the
//! `ishou.packages.${system}.nord-palette-nix` build artifact instead,
//! the local files are deleted, and the fleet has one Nord.
//!
//! Output is byte-stable: a future operator who runs
//! `nix run github:pleme-io/ishou#nix` always gets the same bytes for
//! the same TokenSet. The shape is intentionally compatible with the
//! retiring colors.nix files so the migration is a one-line
//! import-path swap.

use ishou_tokens::{Rgb, TokenSet};

use crate::nix_ast::{AttrEntry, NixFile, attrset, str_};

/// Render the canonical pleme-io Nord palette as an importable Nix
/// attrset. Hex values are uppercase with `#` prefix — matches the
/// shape every retiring `module/themes/nord/colors.nix` carried.
///
/// Built via the typed `nix_ast` printer — string concatenation of
/// Nix syntax is forbidden because it ships malformed output silently.
#[must_use]
pub fn render(t: &TokenSet) -> String {
    let c = &t.color;
    let hex = |rgb: Rgb| format!("#{:02X}{:02X}{:02X}", rgb.r, rgb.g, rgb.b);

    let polar = attrset(vec![
        AttrEntry::new("night0", str_(hex(c.polar_night_0))),
        AttrEntry::new("night1", str_(hex(c.polar_night_1))),
        AttrEntry::new("night2", str_(hex(c.polar_night_2))),
        AttrEntry::new("night3", str_(hex(c.polar_night_3))),
    ]);
    let snow = attrset(vec![
        AttrEntry::new("storm0", str_(hex(c.snow_storm_0))),
        AttrEntry::new("storm1", str_(hex(c.snow_storm_1))),
        AttrEntry::new("storm2", str_(hex(c.snow_storm_2))),
    ]);
    let frost = attrset(vec![
        AttrEntry::new("frost0", str_(hex(c.frost_0))),
        AttrEntry::new("frost1", str_(hex(c.frost_1))),
        AttrEntry::new("frost2", str_(hex(c.frost_2))),
        AttrEntry::new("frost3", str_(hex(c.frost_3))),
    ]);
    let aurora = attrset(vec![
        AttrEntry::new("red",    str_(hex(c.aurora_red))),
        AttrEntry::new("orange", str_(hex(c.aurora_orange))),
        AttrEntry::new("yellow", str_(hex(c.aurora_yellow))),
        AttrEntry::new("green",  str_(hex(c.aurora_green))),
        AttrEntry::new("purple", str_(hex(c.aurora_purple))),
    ]);

    let body = attrset(vec![
        AttrEntry::new("polar", polar),
        AttrEntry::new("snow", snow).with_blank_above(),
        AttrEntry::new("frost", frost).with_blank_above(),
        AttrEntry::new("aurora", aurora).with_blank_above(),
    ]);

    let file = NixFile::new(
        [
            "Generated by ishou-render::nix — DO NOT EDIT",
            "Source of truth: pleme-io/ishou/crates/ishou-tokens/src/color.rs",
            "Architecture:    pleme-io/theory/THEME-ARCHITECTURE.md",
            "Shape compatible with the legacy",
            "`<wrapper>/module/themes/nord/colors.nix` files this output",
            "replaces — import the package and consume identically.",
        ],
        body,
    );

    file.render()
}

#[cfg(test)]
mod tests {
    use super::*;
    use ishou_tokens::TokenSet;

    #[test]
    fn deterministic() {
        let t = TokenSet::pleme();
        assert_eq!(render(&t), render(&t));
    }

    #[test]
    fn matches_legacy_blackmatter_ghostty_colors_nix() {
        // Byte-for-byte snapshot of the canonical Nord attrset the
        // three retiring HM wrappers each carried locally. The headers
        // differ (legacy is a one-line `# Nord Color Palette` comment;
        // ishou's render emits provenance) but the structural body is
        // identical so `import` consumers see the same shape.
        let out = render(&TokenSet::pleme());
        let expected_body = "\
{
  polar = {
    night0 = \"#2E3440\";
    night1 = \"#3B4252\";
    night2 = \"#434C5E\";
    night3 = \"#4C566A\";
  };

  snow = {
    storm0 = \"#D8DEE9\";
    storm1 = \"#E5E9F0\";
    storm2 = \"#ECEFF4\";
  };

  frost = {
    frost0 = \"#8FBCBB\";
    frost1 = \"#88C0D0\";
    frost2 = \"#81A1C1\";
    frost3 = \"#5E81AC\";
  };

  aurora = {
    red = \"#BF616A\";
    orange = \"#D08770\";
    yellow = \"#EBCB8B\";
    green = \"#A3BE8C\";
    purple = \"#B48EAD\";
  };
}
";
        assert!(
            out.ends_with(expected_body),
            "rendered output's body must match the legacy colors.nix exactly:\n--- got ---\n{out}--- expected end ---\n{expected_body}"
        );
    }

    #[test]
    fn nord_polar_night_0_lands_at_polar_night0() {
        let out = render(&TokenSet::pleme());
        assert!(out.contains("night0 = \"#2E3440\""), "got:\n{out}");
    }

    #[test]
    fn header_documents_provenance() {
        let out = render(&TokenSet::pleme());
        assert!(out.contains("THEME-ARCHITECTURE.md"));
        assert!(out.contains("Source of truth"));
    }
}