recipes/db/build.rs

185 lines
4.0 KiB
Rust

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<fs::File>) -> 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<RecipeIngredient>,
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<RecipeTranslate>,
}
#[derive(Debug)]
pub struct RecipeTranslate {
name: &'static str,
instructions: Vec<&'static str>,
}
"#;
writeln!(file, "{}", structs)?;
Ok(())
}
#[derive(Deserialize, Debug)]
pub struct Main {
ingredients: Option<Vec<Ingredient>>,
recipes: Option<Vec<Recipe>>,
}
#[derive(Deserialize, Debug)]
pub struct Ingredient {
key: String,
translates: IngredientTranslate,
}
#[derive(Deserialize, Debug)]
pub struct IngredientTranslate {
ru: String,
en: Option<String>,
}
#[derive(Deserialize, Debug)]
pub struct Recipe {
key: String,
ingredients: Vec<RecipeIngredient>,
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<RecipeTranslate>,
}
#[derive(Deserialize, Debug)]
pub struct RecipeTranslate {
name: String,
instructions: Vec<String>,
}
fn write_ingredients(file: &mut BufWriter<fs::File>) -> 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::<String>()
)?;
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<Vec<Ingredient>, std::io::Error> {
Ok(fs::read_dir("data/ingredients")?
.map(|res| res.and_then(|e| fs::read_to_string(e.path())))
.collect::<Result<Vec<_>, std::io::Error>>()?
.into_iter()
.map(|content| toml::from_str(&content).unwrap())
.filter_map(|cfg: Main| cfg.ingredients)
.flatten()
.collect::<Vec<Ingredient>>())
}
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())
}