KeiSeiKit-1.0/_primitives/_rust/tokens-sync/src/emit.rs
Parfii-bot 0be354a920 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

127 lines
4.3 KiB
Rust

//! Tokens → Tailwind config (TypeScript) + CSS custom-property emission.
//! Emits stable, deterministically-ordered output (BTreeMap input guarantees).
use crate::parse::Tokens;
use std::fmt::Write as _;
use std::fs;
use std::path::Path;
pub fn tailwind_config(t: &Tokens, out: &Path) -> Result<(), String> {
let mut s = String::new();
s.push_str("// GENERATED by tokens-sync — do not edit by hand.\n");
s.push_str("import type { Config } from 'tailwindcss';\n\n");
s.push_str("const config: Config = {\n");
s.push_str(" content: [],\n");
s.push_str(" theme: {\n extend: {\n");
emit_category(&mut s, "colors", &t.colors);
emit_category(&mut s, "fontFamily", &t.fonts);
emit_category(&mut s, "spacing", &t.spacing);
emit_category(&mut s, "borderRadius", &t.radius);
s.push_str(" },\n },\n plugins: [],\n};\n\nexport default config;\n");
fs::write(out, s).map_err(|e| format!("write {}: {e}", out.display()))
}
pub fn css_vars(t: &Tokens, out: &Path) -> Result<(), String> {
let mut s = String::new();
s.push_str("/* GENERATED by tokens-sync — do not edit by hand. */\n:root {\n");
for (k, v) in &t.colors {
let _ = writeln!(s, " --color-{}: {};", css_ident(k), v);
}
for (k, v) in &t.fonts {
let _ = writeln!(s, " --font-{}: {};", css_ident(k), v);
}
for (k, v) in &t.spacing {
let _ = writeln!(s, " --space-{}: {};", css_ident(k), v);
}
for (k, v) in &t.radius {
let _ = writeln!(s, " --radius-{}: {};", css_ident(k), v);
}
s.push_str("}\n");
fs::write(out, s).map_err(|e| format!("write {}: {e}", out.display()))
}
fn emit_category(
s: &mut String,
key: &str,
map: &std::collections::BTreeMap<String, String>,
) {
if map.is_empty() {
return;
}
let _ = writeln!(s, " {key}: {{");
for (k, v) in map {
// Escape JS single quotes inside the value string.
let escaped = v.replace('\'', "\\'");
let _ = writeln!(s, " '{k}': '{escaped}',");
}
s.push_str(" },\n");
}
fn css_ident(raw: &str) -> String {
raw.chars()
.map(|c| if c.is_ascii_alphanumeric() || c == '-' { c } else { '-' })
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeMap;
fn sample_tokens() -> Tokens {
let mut colors = BTreeMap::new();
colors.insert("primary".into(), "oklch(0.6 0.2 250)".into());
colors.insert("surface".into(), "oklch(0.99 0 0)".into());
let mut fonts = BTreeMap::new();
fonts.insert("body".into(), "Inter, sans-serif".into());
let mut spacing = BTreeMap::new();
spacing.insert("md".into(), "1rem".into());
let mut radius = BTreeMap::new();
radius.insert("card".into(), "0.75rem".into());
Tokens { colors, fonts, spacing, radius }
}
#[test]
fn tailwind_emits_extend_categories() {
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("tw.ts");
tailwind_config(&sample_tokens(), &out).unwrap();
let text = fs::read_to_string(&out).unwrap();
assert!(text.contains("colors: {"));
assert!(text.contains("'primary': 'oklch(0.6 0.2 250)'"));
assert!(text.contains("fontFamily: {"));
assert!(text.contains("borderRadius: {"));
}
#[test]
fn css_emits_vars_under_root() {
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("tokens.css");
css_vars(&sample_tokens(), &out).unwrap();
let text = fs::read_to_string(&out).unwrap();
assert!(text.contains(":root {"));
assert!(text.contains("--color-primary: oklch(0.6 0.2 250);"));
assert!(text.contains("--font-body: Inter, sans-serif;"));
assert!(text.contains("--space-md: 1rem;"));
assert!(text.contains("--radius-card: 0.75rem;"));
}
#[test]
fn empty_tokens_still_emit_valid_shells() {
let dir = tempfile::tempdir().unwrap();
let t = Tokens::default();
let tw = dir.path().join("tw.ts");
let css = dir.path().join("c.css");
tailwind_config(&t, &tw).unwrap();
css_vars(&t, &css).unwrap();
let css_text = fs::read_to_string(&css).unwrap();
assert!(css_text.contains(":root {"));
assert!(css_text.trim_end().ends_with('}'));
}
}