Skip to main content

ishou_render/
nix.rs

1//! Nix renderer — emits an importable Nix expression for the Nord
2//! palette in the shape every pleme-io HM wrapper historically carried
3//! locally (`{ polar = { night0 = "…"; … }; snow = …; frost = …;
4//! aurora = …; }`).
5//!
6//! Closes the M5 `no-foreign-nord-source` invariant for the
7//! `blackmatter-ghostty` / `blackmatter-mado` / `blackmatter-opencode`
8//! wrappers: each currently `import ./module/themes/nord/colors.nix`
9//! their own copy of the palette. After M6.1 they `import` the
10//! `ishou.packages.${system}.nord-palette-nix` build artifact instead,
11//! the local files are deleted, and the fleet has one Nord.
12//!
13//! Output is byte-stable: a future operator who runs
14//! `nix run github:pleme-io/ishou#nix` always gets the same bytes for
15//! the same TokenSet. The shape is intentionally compatible with the
16//! retiring colors.nix files so the migration is a one-line
17//! import-path swap.
18
19use ishou_tokens::{Rgb, TokenSet};
20
21use crate::nix_ast::{AttrEntry, NixFile, attrset, str_};
22
23/// Render the canonical pleme-io Nord palette as an importable Nix
24/// attrset. Hex values are uppercase with `#` prefix — matches the
25/// shape every retiring `module/themes/nord/colors.nix` carried.
26///
27/// Built via the typed `nix_ast` printer — string concatenation of
28/// Nix syntax is forbidden because it ships malformed output silently.
29#[must_use]
30pub fn render(t: &TokenSet) -> String {
31    let c = &t.color;
32    let hex = |rgb: Rgb| format!("#{:02X}{:02X}{:02X}", rgb.r, rgb.g, rgb.b);
33
34    let polar = attrset(vec![
35        AttrEntry::new("night0", str_(hex(c.polar_night_0))),
36        AttrEntry::new("night1", str_(hex(c.polar_night_1))),
37        AttrEntry::new("night2", str_(hex(c.polar_night_2))),
38        AttrEntry::new("night3", str_(hex(c.polar_night_3))),
39    ]);
40    let snow = attrset(vec![
41        AttrEntry::new("storm0", str_(hex(c.snow_storm_0))),
42        AttrEntry::new("storm1", str_(hex(c.snow_storm_1))),
43        AttrEntry::new("storm2", str_(hex(c.snow_storm_2))),
44    ]);
45    let frost = attrset(vec![
46        AttrEntry::new("frost0", str_(hex(c.frost_0))),
47        AttrEntry::new("frost1", str_(hex(c.frost_1))),
48        AttrEntry::new("frost2", str_(hex(c.frost_2))),
49        AttrEntry::new("frost3", str_(hex(c.frost_3))),
50    ]);
51    let aurora = attrset(vec![
52        AttrEntry::new("red",    str_(hex(c.aurora_red))),
53        AttrEntry::new("orange", str_(hex(c.aurora_orange))),
54        AttrEntry::new("yellow", str_(hex(c.aurora_yellow))),
55        AttrEntry::new("green",  str_(hex(c.aurora_green))),
56        AttrEntry::new("purple", str_(hex(c.aurora_purple))),
57    ]);
58
59    let body = attrset(vec![
60        AttrEntry::new("polar", polar),
61        AttrEntry::new("snow", snow).with_blank_above(),
62        AttrEntry::new("frost", frost).with_blank_above(),
63        AttrEntry::new("aurora", aurora).with_blank_above(),
64    ]);
65
66    let file = NixFile::new(
67        [
68            "Generated by ishou-render::nix — DO NOT EDIT",
69            "Source of truth: pleme-io/ishou/crates/ishou-tokens/src/color.rs",
70            "Architecture:    pleme-io/theory/THEME-ARCHITECTURE.md",
71            "Shape compatible with the legacy",
72            "`<wrapper>/module/themes/nord/colors.nix` files this output",
73            "replaces — import the package and consume identically.",
74        ],
75        body,
76    );
77
78    file.render()
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use ishou_tokens::TokenSet;
85
86    #[test]
87    fn deterministic() {
88        let t = TokenSet::pleme();
89        assert_eq!(render(&t), render(&t));
90    }
91
92    #[test]
93    fn matches_legacy_blackmatter_ghostty_colors_nix() {
94        // Byte-for-byte snapshot of the canonical Nord attrset the
95        // three retiring HM wrappers each carried locally. The headers
96        // differ (legacy is a one-line `# Nord Color Palette` comment;
97        // ishou's render emits provenance) but the structural body is
98        // identical so `import` consumers see the same shape.
99        let out = render(&TokenSet::pleme());
100        let expected_body = "\
101{
102  polar = {
103    night0 = \"#2E3440\";
104    night1 = \"#3B4252\";
105    night2 = \"#434C5E\";
106    night3 = \"#4C566A\";
107  };
108
109  snow = {
110    storm0 = \"#D8DEE9\";
111    storm1 = \"#E5E9F0\";
112    storm2 = \"#ECEFF4\";
113  };
114
115  frost = {
116    frost0 = \"#8FBCBB\";
117    frost1 = \"#88C0D0\";
118    frost2 = \"#81A1C1\";
119    frost3 = \"#5E81AC\";
120  };
121
122  aurora = {
123    red = \"#BF616A\";
124    orange = \"#D08770\";
125    yellow = \"#EBCB8B\";
126    green = \"#A3BE8C\";
127    purple = \"#B48EAD\";
128  };
129}
130";
131        assert!(
132            out.ends_with(expected_body),
133            "rendered output's body must match the legacy colors.nix exactly:\n--- got ---\n{out}--- expected end ---\n{expected_body}"
134        );
135    }
136
137    #[test]
138    fn nord_polar_night_0_lands_at_polar_night0() {
139        let out = render(&TokenSet::pleme());
140        assert!(out.contains("night0 = \"#2E3440\""), "got:\n{out}");
141    }
142
143    #[test]
144    fn header_documents_provenance() {
145        let out = render(&TokenSet::pleme());
146        assert!(out.contains("THEME-ARCHITECTURE.md"));
147        assert!(out.contains("Source of truth"));
148    }
149}