//! Besides Tauri's own build step: the two themes' page colour, which the window itself paints //! before the page has (so a dark theme does not start white), read out of `ui/tokens.css` (the server's tokens, fetched by `scripts/fetch-tokens.sh`) into //! `$OUT_DIR/tokens.rs`, so no colour is written in Rust. `lib.rs` includes that file. use std::env; use std::fs; use std::path::Path; /// The themes the app knows, by the name `tokens.css` selects them with (`data-theme`), and the /// Rust names the constants get. const THEMES: [(&str, &str); 2] = [("moria", "MORIA"), ("shire", "SHIRE")]; /// The tokens taken from each theme, by the custom property and the constant's suffix. const TOKENS: [(&str, &str); 1] = [("--bg", "BG")]; fn main() { let tokens = Path::new("../ui/tokens.css"); println!("cargo:rerun-if-changed={}", tokens.display()); let css = fs::read_to_string(tokens) .unwrap_or_else(|e| panic!("{}: {e}; run scripts/fetch-tokens.sh", tokens.display())); let mut out = String::from( "// Generated by build.rs from ui/tokens.css: the colours the window paints itself.\n", ); for (theme, name) in THEMES { let block = theme_block(&css, theme).unwrap_or_else(|| { panic!("ui/tokens.css has no `:root[data-theme=\"{theme}\"]` block") }); for (token, suffix) in TOKENS { let (r, g, b) = hex_token(block, token).unwrap_or_else(|| { panic!( "ui/tokens.css: `{token}` of the {theme} theme is not a six-digit hex colour" ) }); out.push_str(&format!( "pub const {name}_{suffix}: (u8, u8, u8) = (0x{r:02X}, 0x{g:02X}, 0x{b:02X});\n" )); } } let dest = Path::new(&env::var("OUT_DIR").expect("OUT_DIR")).join("tokens.rs"); fs::write(&dest, out).expect("write tokens.rs"); tauri_build::build() } /// The declarations of the theme's block: from the selector naming it (at the start of a line, /// so a mention in a comment does not count) to the block's closing brace at the start of a /// line, which is the file's own layout, one block per theme. fn theme_block<'a>(css: &'a str, theme: &str) -> Option<&'a str> { let selector = format!("\n:root[data-theme=\"{theme}\"]"); let at = css.find(&selector)?; let open = at + css[at..].find('{')?; let close = open + css[open..].find("\n}")?; Some(&css[open + 1..close]) } /// `--token: #RRGGBB;` in the block, as bytes. fn hex_token(block: &str, token: &str) -> Option<(u8, u8, u8)> { let value = block .lines() .map(str::trim) .find_map(|l| { l.strip_prefix(token) .and_then(|r| r.trim_start().strip_prefix(':')) })? .trim() .trim_end_matches(';') .trim(); let hex = value.strip_prefix('#')?; if hex.len() != 6 { return None; } let byte = |i: usize| u8::from_str_radix(&hex[i..i + 2], 16).ok(); Some((byte(0)?, byte(2)?, byte(4)?)) }