use std::fs; use std::io::{BufWriter, Write}; use serde::Deserialize; fn main() { println!("cargo:rerun-if-changed=build.rs"); if let Err(e) = gen_data_mod() { eprintln!("Error: {}", e); } } fn gen_data_mod() -> Result<(), std::io::Error> { let file = fs::File::create("src/data.rs")?; let mut buf = BufWriter::new(file); write_structs(&mut buf)?; println!("cargo:rerun-if-changed=data/ingredients/*.toml"); write_ingredients(&mut buf)?; Ok(()) } fn write_structs(file: &mut BufWriter) -> Result<(), std::io::Error> { let structs = r#" #[derive(Debug)] pub struct Ingredient { pub key: &'static str, pub translates: IngredientTranslate, } #[derive(Debug)] pub struct IngredientTranslate { pub ru: &'static str, pub en: Option<&'static str>, } #[derive(Debug)] pub struct Recipe { key: &'static str, ingredients: Vec, steps: u8, translates: RecipeTranslates, } #[derive(Debug)] pub struct RecipeIngredient { key: &'static str, measure: RecipeIngredientMeasure, } #[derive(Debug)] pub enum RecipeIngredientMeasure { Gram(u32), KiloGram(u32), MilliLiter(u32), Liter(u32), } #[derive(Debug)] pub struct RecipeTranslates { ru: RecipeTranslate, en: Option, } #[derive(Debug)] pub struct RecipeTranslate { name: &'static str, instructions: Vec<&'static str>, } "#; writeln!(file, "{}", structs)?; Ok(()) } #[derive(Deserialize, Debug)] pub struct Main { ingredients: Option>, recipes: Option>, } #[derive(Deserialize, Debug)] pub struct Ingredient { key: String, translates: IngredientTranslate, } #[derive(Deserialize, Debug)] pub struct IngredientTranslate { ru: String, en: Option, } #[derive(Deserialize, Debug)] pub struct Recipe { key: String, ingredients: Vec, steps: u8, translates: RecipeTranslates, } #[derive(Deserialize, Debug)] pub struct RecipeIngredient { key: String, #[serde(flatten)] measure: RecipeIngredientMeasure, } #[derive(Deserialize, Debug)] pub enum RecipeIngredientMeasure { #[serde(rename = "g")] Gram(u32), #[serde(rename = "kg")] KiloGram(u32), #[serde(rename = "ml")] MilliLiter(u32), #[serde(rename = "l")] Liter(u32), } #[derive(Deserialize, Debug)] pub struct RecipeTranslates { ru: RecipeTranslate, en: Option, } #[derive(Deserialize, Debug)] pub struct RecipeTranslate { name: String, instructions: Vec, } fn write_ingredients(file: &mut BufWriter) -> Result<(), std::io::Error> { let ingredients = get_ingredient_configs()?; writeln!( file, "pub const INGREDIENTS: [Ingredient; {}] = [{}];", ingredients.len(), ingredients .into_iter() .map(|i| to_ingredient_data_content(1, i)) .collect::() )?; Ok(()) } fn to_ingredient_data_content(indent_size: usize, ingredient: Ingredient) -> String { format!( r#" {i}Ingredient {{ {i} key: {key:?}, {i} translates: IngredientTranslate {{ {i} ru: {tr_ru:?}, {i} en: {tr_en:?}, {i} }}, {i}}}, "#, i = indent(indent_size), key = to_str(ingredient.key), tr_ru = to_str(ingredient.translates.ru), tr_en = ingredient.translates.en.map(to_str), ) } fn get_ingredient_configs() -> Result, std::io::Error> { Ok(fs::read_dir("data/ingredients")? .map(|res| res.and_then(|e| fs::read_to_string(e.path()))) .collect::, std::io::Error>>()? .into_iter() .map(|content| toml::from_str(&content).unwrap()) .filter_map(|cfg: Main| cfg.ingredients) .flatten() .collect::>()) } fn indent(indent_size: usize) -> String { std::iter::repeat(" ").take(indent_size * 4).collect() } fn to_str(string: String) -> &'static str { Box::leak(string.into_boxed_str()) }