Skip to main content

farben_build/
lib.rs

1//! Build-time support crate for loading and parsing `.frb.toml` config files.
2//!
3//! This crate is used by `build.rs` scripts to compile farben style definitions
4//! into the binary at build time.
5#![warn(missing_docs)]
6#![deny(rustdoc::broken_intra_doc_links)]
7#![warn(rustdoc::private_intra_doc_links)]
8
9pub mod core;
10mod parser;
11
12use std::{collections::HashMap, fs, path::Path};
13
14/// Runs the build script with the default config filename `farben.frb.toml`.
15pub fn run() {
16    run_with(&["farben.frb.toml"]);
17}
18
19/// Runs the build script with custom config file paths.
20pub fn run_with(paths: &[&str]) {
21    let out_dir = std::env::var("OUT_DIR").unwrap();
22    let dest = Path::new(&out_dir).join("farben_styles.rs");
23
24    for path in paths {
25        println!("cargo:rerun-if-changed={path}");
26    }
27
28    let mut all_styles: HashMap<String, String> = HashMap::new();
29    let mut all_prefixes: HashMap<String, String> = HashMap::new();
30
31    for path in paths {
32        let config = parser::parse(
33            &fs::read_to_string(path)
34                .unwrap_or_else(|_| panic!("farben-build: could not find '{path}'")),
35        )
36        .unwrap_or_else(|e| panic!("farben-build: error parsing '{path}': {e}"));
37
38        for (key, value) in config.styles {
39            if all_styles.contains_key(&key) {
40                panic!("farben-build: duplicate style '{key}' found in '{path}'");
41            }
42            all_styles.insert(key, value);
43        }
44
45        for (key, value) in config.prefixes {
46            if all_prefixes.contains_key(&key) {
47                panic!("farben-build: duplicate prefix '{key}' found in '{path}'");
48            }
49            all_prefixes.insert(key, value);
50        }
51    }
52
53    let mut code = String::new();
54
55    code.push_str("/// Registers `.frb.toml` styles. \n");
56    code.push_str("/// This function is automatically @generated by `farben-build`\n");
57    code.push_str("///\n");
58    code.push_str(
59        "/// It includes all styles declared in the `.frb.toml` file ran by `farben-build`'s `run` function \n",
60    );
61    code.push_str(
62        "/// in the form of style registry calls. It is meant to be called, not edited.\n",
63    );
64    code.push_str("pub fn init_styles() {\n");
65
66    for (key, value) in &all_styles {
67        code.push_str(&format!(
68            "    farben::insert_style({key:?}, farben::Style::parse({markup:?}).unwrap_or_else(|e| panic!(\"{{e}}\")));\n",
69            markup = format!("[{value}]")
70        ));
71    }
72
73    for (key, value) in &all_prefixes {
74        code.push_str(&format!(
75            "    farben::set_prefix({key:?}, {value:?}).unwrap_or_else(|e| panic!(\"{{e}}\"));\n"
76        ));
77    }
78
79    code.push_str("}\n");
80
81    std::fs::write(&dest, code)
82        .unwrap_or_else(|e| panic!("farben-build: could not write generated file: {e}"));
83
84    /*
85     * LSV: line separated values
86     *  A custom data format intentionally made dead simple
87     *  so my parser logic would also be dead simple. No spaces between =
88     * Why LSV?
89     *      I don't want to write any other parser.
90     * Why write a parser anyway?
91     *      Farben needs to not have any dependencies.
92     */
93    let registry_dest = Path::new(&out_dir).join("farben_registry.lsv");
94    let mut registry_content = all_styles
95        .iter()
96        .map(|(k, v)| format!("{k}={v}"))
97        .collect::<Vec<_>>()
98        .join("\n");
99    registry_content.push_str("\n---\n");
100    registry_content.push_str(
101        &all_prefixes
102            .iter()
103            .map(|(k, v)| format!("{k}={v}"))
104            .collect::<Vec<_>>()
105            .join("\n"),
106    );
107    std::fs::write(&registry_dest, registry_content)
108        .unwrap_or_else(|e| panic!("farben-build: could not write registry file: {e}"));
109}