ishou-render 0.1.6

ishou — target-specific renderers for the pleme-io design token set
Documentation
//! Stylix fonts renderer — emits the Nix attrset shape
//! `stylix.fonts.*` consumes (`{ monospace = { name = …; package = …; };
//! sansSerif = …; serif = …; emoji = …; }`).
//!
//! Closes the loop: just as the `stylix` render target sources the
//! fleet's base16 palette from ishou's single Nord, this render target
//! sources the fleet's font posture from ishou's typed Typography. One
//! Nerd Font, one calligraphic italic, fleet-wide — change in
//! `crates/ishou-tokens/src/typography.rs`, every foreign-app font
//! propagates on the next rebuild.
//!
//! Output format (matches `stylix.fonts.*` HM-module schema):
//!
//! ```nix
//! { pkgs }: {
//!   monospace = {
//!     name = "JetBrainsMono Nerd Font";
//!     package = pkgs.nerd-fonts.jetbrains-mono;
//!   };
//!   sansSerif = {
//!     name = "Inter";
//!     package = pkgs.inter;
//!   };
//!//! }
//! ```
//!
//! Consumers import the result and set `stylix.fonts = import path { inherit pkgs; }`.

use ishou_tokens::TokenSet;

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

/// Render the canonical pleme-io stylix.fonts attrset.
///
/// Maps ishou's typed `Typography.mono_fonts` + family stacks to
/// stylix's four font slots (`monospace`, `sansSerif`, `serif`,
/// `emoji`). Each slot resolves to a `pkgs.<attr>` reference — the
/// HM build inherits the package from nixpkgs without operators
/// declaring it per-app.
///
/// Built via the typed `nix_ast` printer — string concatenation of
/// Nix syntax is forbidden because it ships malformed output silently
/// (unbalanced braces, missing semicolons, broken interpolation).
#[must_use]
pub fn render(t: &TokenSet) -> String {
    let mono = &t.typography.mono_fonts;

    let monospace_pkg = match mono.nerd_font_package_attr {
        Some(attr) => raw(format!("pkgs.nerd-fonts.{attr}")),
        None => raw("pkgs.dejavu_fonts"),
    };
    let monospace = attrset(vec![
        AttrEntry::new("name", str_(mono.primary)),
        AttrEntry::new("package", monospace_pkg),
    ]);

    let sans_serif = attrset(vec![
        AttrEntry::new("name", str_("Inter")),
        AttrEntry::new("package", raw("pkgs.inter")),
    ]);

    let serif = attrset(vec![
        AttrEntry::new("name", str_("DejaVu Serif")),
        AttrEntry::new("package", raw("pkgs.dejavu_fonts")),
    ]);

    let emoji = attrset(vec![
        AttrEntry::new("name", str_("Noto Color Emoji")),
        AttrEntry::new("package", raw("pkgs.noto-fonts-color-emoji")),
    ]);

    let body = attrset(vec![
        AttrEntry::new("monospace", monospace)
            .with_comment(["Source of every ASCII glyph the fleet's terminals draw."]),
        AttrEntry::new("sansSerif", sans_serif)
            .with_comment(["Canonical pleme-io sans for GTK / Cocoa app text."]),
        AttrEntry::new("serif", serif)
            .with_comment([
                "DejaVu Serif ships on every nixpkgs-supported platform —",
                "earlier `pkgs.charter` reference broke flake-eval.",
            ]),
        AttrEntry::new("emoji", emoji)
            .with_comment([
                "nixpkgs renamed noto-fonts-emoji → noto-fonts-color-emoji;",
                "macOS apps prefer the system Apple Color Emoji at runtime.",
            ]),
    ]);

    let file = NixFile::new(
        [
            "Generated by ishou-render::stylix_fonts — DO NOT EDIT",
            "Source of truth: pleme-io/ishou/crates/ishou-tokens/src/typography.rs",
            "Architecture:    pleme-io/theory/THEME-ARCHITECTURE.md",
            "",
            "Consumed as `stylix.fonts = import this { inherit pkgs; };`",
            "in the fleet's darwin / home-manager configuration.",
        ],
        lambda(vec!["pkgs"], body),
    );

    file.render()
}

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

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

    #[test]
    fn monospace_resolves_to_nerd_fonts_jetbrains_mono() {
        let out = render(&TokenSet::pleme());
        assert!(out.contains("name = \"JetBrainsMono Nerd Font\""), "got:\n{out}");
        assert!(out.contains("package = pkgs.nerd-fonts.jetbrains-mono"), "got:\n{out}");
    }

    #[test]
    fn output_has_all_four_stylix_font_slots() {
        let out = render(&TokenSet::pleme());
        for slot in ["monospace", "sansSerif", "serif", "emoji"] {
            assert!(out.contains(&format!("{slot} = {{")), "missing {slot}");
        }
    }

    #[test]
    fn output_is_valid_nix_function_shape() {
        let out = render(&TokenSet::pleme());
        assert!(out.contains("{ pkgs }:"));
        assert!(out.trim_end().ends_with('}'));
    }

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