use std::env; use std::fmt::Display; use std::fs::File; use std::io::Write; use std::io::{self, BufRead}; use std::path::Path; use std::path::PathBuf; use regex::Regex; fn main() { // Tell Cargo to rerun this build script if any SCSS file // in the 'src' directory or its subdirectories changes. println!("cargo:rerun-if-changed=src/ui/**/*.scss"); let out_dir = env::var("OUT_DIR").unwrap(); // let mut tasks = Vec::new(); let tasks = vec![ // Global tokens Task::new( PathBuf::from("src/ui/_base.scss"), Path::new(&out_dir).join("style_tokens.rs"), "style".to_string(), ), // variables defined by the Panel component Task::new( PathBuf::from("src/ui/components/_panel.scss"), Path::new(&out_dir).join("style_component_panel.rs"), "panel".to_string(), ), // Variables set by the Conversations layout Task::new( PathBuf::from("src/ui/layouts/conversations.scss"), Path::new(&out_dir).join("style_layout_conversations.rs"), "conversations".to_string(), ), ]; export_variables(tasks) } // From https://doc.rust-lang.org/rust-by-example/std_misc/file/read_lines.html // The output is wrapped in a Result to allow matching on errors. // Returns an Iterator to the Reader of the lines of the file. fn read_lines

(filename: P) -> io::Result>> where P: AsRef, { let file = File::open(filename)?; Ok(io::BufReader::new(file).lines()) } #[derive(Debug)] struct ColorVariable { name: String, value: String, } impl ColorVariable { pub fn new(name: String, value: String) -> Self { Self { name, value } } } impl Display for ColorVariable { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "const {name}: &str = \"{value}\";", name = self.name.replace('-', "_").to_uppercase(), value = self.value ) } } #[derive(Debug)] struct FloatVariable { name: String, value: f64, } impl FloatVariable { pub fn new(name: String, value: f64) -> Self { Self { name, value } } } impl Display for FloatVariable { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "const {name}: f64 = {value};", name = self.name.replace('-', "_").to_uppercase(), value = self.value ) } } struct Task { src_path: PathBuf, dst_path: PathBuf, module_name: String, } impl Task { pub fn new(src_path: PathBuf, dst_path: PathBuf, module_name: String) -> Self { Self { src_path, dst_path, module_name, } } } // fn export_variables(src_path: &PathBuf, dst_path: &PathBuf) { fn export_variables(tasks: Vec) { let color_re = Regex::new(r"^\$([^:]+):[[:space:]]*#([^$]+);[[:space:]]*$").unwrap(); let variable_re = Regex::new(r"^\$([^:]+):[[:space:]]*([^;]+)[[:space:]]*;").unwrap(); for task in tasks { let mut dst_file = File::create(task.dst_path).unwrap(); if let Err(err) = dst_file.write_fmt(format_args!( "#[allow(dead_code)]\nmod {} {{\n", task.module_name )) { println!("{err}"); return; }; let mut variables = Vec::>::new(); if let Ok(lines) = read_lines(task.src_path) { for line in lines.map_while(Result::ok) { if let Some(groups) = color_re.captures(&line) { let var = ColorVariable::new(groups[1].to_string(), groups[2].to_string()); variables.push(Box::new(var)); } else if let Some(groups) = variable_re.captures(&line) { if let Ok(value) = groups[2].parse::() { variables.push(Box::new(FloatVariable::new(groups[1].to_string(), value))); } } } } for variable in variables { if let Err(err) = dst_file.write_fmt(format_args!(" pub {variable}\n")) { println!("{err}"); break; } } if let Err(err) = dst_file.write(b"}\n") { println!("{err}"); }; } }