use ishou_tokens::{Rgb, TokenSet};
#[must_use]
pub fn render(t: &TokenSet) -> String {
let c = &t.color;
let base00 = c.polar_night_0;
let base01 = c.polar_night_1;
let base02 = c.polar_night_2;
let base03 = c.polar_night_3;
let base05 = c.snow_storm_1;
let base08 = c.aurora_red;
let base09 = c.aurora_orange;
let base0a = c.aurora_yellow;
let base0b = c.aurora_green;
let base0c = c.frost_1;
let base0d = c.frost_2;
let base0e = c.aurora_purple;
let base0f = c.frost_3;
let mut out = String::new();
out.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
out.push_str(
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \
\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n",
);
out.push_str("<plist version=\"1.0\">\n");
out.push_str("<dict>\n");
out.push_str("\t<key>name</key>\n");
out.push_str("\t<string>Nord (pleme-io / ishou)</string>\n");
out.push_str("\t<key>author</key>\n");
out.push_str("\t<string>Arctic Ice Studio; mapped by pleme-io ishou</string>\n");
out.push_str("\t<key>semanticClass</key>\n");
out.push_str("\t<string>theme.dark.nord_pleme_io_ishou</string>\n");
out.push_str("\t<key>colorSpaceName</key>\n");
out.push_str("\t<string>sRGB</string>\n");
out.push_str("\t<key>settings</key>\n");
out.push_str("\t<array>\n");
out.push_str("\t\t<dict>\n");
out.push_str("\t\t\t<key>settings</key>\n");
out.push_str("\t\t\t<dict>\n");
push_setting(&mut out, "background", base00);
push_setting(&mut out, "caret", base05);
push_setting(&mut out, "foreground", base05);
push_setting(&mut out, "invisibles", base03);
push_setting(&mut out, "lineHighlight", base01);
push_setting(&mut out, "selection", base02);
push_setting(&mut out, "gutterForeground", base03);
out.push_str("\t\t\t</dict>\n");
out.push_str("\t\t</dict>\n");
push_rule(&mut out, "Comment", "comment", base03);
push_rule(&mut out, "String", "string", base0b);
push_rule(&mut out, "Number", "constant.numeric", base09);
push_rule(
&mut out,
"Built-in constant",
"constant.language",
base09,
);
push_rule(
&mut out,
"User-defined constant",
"constant.character, constant.other",
base09,
);
push_rule(&mut out, "Variable", "variable", base08);
push_rule(&mut out, "Keyword", "keyword", base0e);
push_rule(&mut out, "Storage", "storage", base0e);
push_rule(
&mut out,
"Storage type",
"storage.type",
base0d,
);
push_rule(
&mut out,
"Class name",
"entity.name.class",
base0a,
);
push_rule(
&mut out,
"Inherited class",
"entity.other.inherited-class",
base0c,
);
push_rule(
&mut out,
"Function name",
"entity.name.function",
base0d,
);
push_rule(
&mut out,
"Function argument",
"variable.parameter",
base09,
);
push_rule(
&mut out,
"Tag name",
"entity.name.tag",
base08,
);
push_rule(
&mut out,
"Tag attribute",
"entity.other.attribute-name",
base0a,
);
push_rule(
&mut out,
"Library function",
"support.function",
base0d,
);
push_rule(
&mut out,
"Library constant",
"support.constant",
base0c,
);
push_rule(
&mut out,
"Library class/type",
"support.type, support.class",
base0a,
);
push_rule(
&mut out,
"Invalid",
"invalid",
base08,
);
push_rule(
&mut out,
"Invalid deprecated",
"invalid.deprecated",
base0f,
);
out.push_str("\t</array>\n");
out.push_str("\t<key>uuid</key>\n");
out.push_str("\t<string>nord-pleme-io-ishou</string>\n");
out.push_str("</dict>\n");
out.push_str("</plist>\n");
out
}
fn push_setting(out: &mut String, key: &str, rgb: Rgb) {
out.push_str(&format!("\t\t\t\t<key>{key}</key>\n"));
out.push_str(&format!("\t\t\t\t<string>{}</string>\n", hex(rgb)));
}
fn push_rule(out: &mut String, name: &str, scope: &str, rgb: Rgb) {
out.push_str("\t\t<dict>\n");
out.push_str("\t\t\t<key>name</key>\n");
out.push_str(&format!("\t\t\t<string>{name}</string>\n"));
out.push_str("\t\t\t<key>scope</key>\n");
out.push_str(&format!("\t\t\t<string>{scope}</string>\n"));
out.push_str("\t\t\t<key>settings</key>\n");
out.push_str("\t\t\t<dict>\n");
out.push_str("\t\t\t\t<key>foreground</key>\n");
out.push_str(&format!("\t\t\t\t<string>{}</string>\n", hex(rgb)));
out.push_str("\t\t\t</dict>\n");
out.push_str("\t\t</dict>\n");
}
fn hex(rgb: Rgb) -> String {
format!("#{:02x}{:02x}{:02x}", rgb.r, rgb.g, rgb.b)
}
#[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 output_is_non_empty() {
assert!(!render(&TokenSet::pleme()).is_empty());
}
#[test]
fn output_is_a_tmtheme_plist() {
let out = render(&TokenSet::pleme());
assert!(out.starts_with("<?xml version=\"1.0\""), "got:\n{out}");
assert!(out.contains("<plist version=\"1.0\">"), "got:\n{out}");
assert!(out.contains("<key>settings</key>"), "got:\n{out}");
assert!(out.trim_end().ends_with("</plist>"), "got:\n{out}");
}
#[test]
fn background_is_base00_polar_night_0() {
let out = render(&TokenSet::pleme());
assert!(
out.contains("<key>background</key>\n\t\t\t\t<string>#2e3440</string>"),
"background must be base00 (#2e3440); got:\n{out}"
);
}
#[test]
fn foreground_is_base05_snow_storm_1() {
let out = render(&TokenSet::pleme());
assert!(
out.contains("<key>foreground</key>\n\t\t\t\t<string>#e5e9f0</string>"),
"foreground must be base05 (#e5e9f0); got:\n{out}"
);
}
#[test]
fn strings_are_base0b_aurora_green() {
let out = render(&TokenSet::pleme());
assert!(out.contains("<string>#a3be8c</string>"), "got:\n{out}");
}
#[test]
fn hex_is_hash_prefixed_lowercase() {
let out = render(&TokenSet::pleme());
for line in out.lines() {
let trimmed = line.trim();
if let Some(v) = trimmed
.strip_prefix("<string>#")
.and_then(|r| r.strip_suffix("</string>"))
{
assert_eq!(v.len(), 6, "wrong hex length in: {line}");
assert!(
v.chars()
.all(|ch| ch.is_ascii_hexdigit() && !ch.is_ascii_uppercase()),
"expected lowercase hex in: {line}"
);
}
}
}
}