use serde::Serialize; use std::fs; use crate::queries; #[derive(Debug, Serialize)] pub enum Header { Payments, Categories, Incomes, Balance, Statistics, } pub fn get() -> Result, String> { let mut env = minijinja::Environment::new(); for path in read_files_recursive("templates") { let path = path .to_str() .ok_or("Error getting string of path: {path:?}")? .to_string(); let content = fs::read_to_string(&path) .map_err(|_| "Error reading template {path}")?; let path_without_prefix = path .strip_prefix("templates/") .ok_or("Error removing prefix from template path")? .to_string(); env.add_template_owned(path_without_prefix, content) .map_err(|_| "Error adding template {path} to environment")?; } env.add_function("payments_params", payments_params); env.add_function("pluralize", pluralize); env.add_function("now", now); env.add_filter("numeric", numeric); env.add_filter("euros", euros); env.add_filter("round", round); env.add_filter("with_param", with_param); env.add_filter("filter", filter); Ok(env) } fn read_files_recursive( path: impl AsRef, ) -> Vec { let Ok(entries) = fs::read_dir(path) else { return vec![]; }; entries .flatten() .flat_map(|entry| { let Ok(meta) = entry.metadata() else { return vec![]; }; if meta.is_dir() { return read_files_recursive(entry.path()); } if meta.is_file() { return vec![entry.path()]; } vec![] }) .collect() } fn payments_params(value: minijinja::Value) -> String { let str = value.to_string().replace("none", "null"); match serde_json::from_str(&str) { Ok(q) => queries::payments_url(q), Err(err) => { log::error!("Error parsing payments params {}: {:?}", str, err); "".to_string() } } } fn now(format: &str) -> String { let date = chrono::Local::now(); format!("{}", date.format(format)) } fn euros(n: i64) -> String { let str = rgrouped(n.abs().to_string(), 3).join(" "); let sign = if n < 0 { "-" } else { "" }; format!("{}{} €", sign, str) } fn numeric(n: i64) -> String { let str = rgrouped(n.abs().to_string(), 3).join(" "); let sign = if n < 0 { "-" } else { "" }; format!("{}{}", sign, str) } fn pluralize(n: i32, s: String) -> String { if n > 0 { format!("{s}s") } else { s } } fn round(n: f32) -> i32 { n.round() as i32 } fn with_param(url: String, key: String, value: String) -> String { if url.contains("?") { format!("{url}&{key}={value}") } else { format!("{url}?{key}={value}") } } fn filter( xs: Vec, key: &str, value: String, ) -> Vec { let mut res = vec![]; for x in xs { if let Ok(v) = x.get_attr(key) { if let Some(v) = v.as_str() { if v == value { res.push(x); } } } } res } fn rgrouped(str: String, n: usize) -> Vec { let mut str = str; let mut l = str.len(); let mut res = vec![]; while l > n { let str2 = str.clone(); let (start, end) = str2.split_at(l - n); l -= n; str = start.to_string(); res.push(end.to_string()); } if !str.is_empty() { res.push(str); } res.reverse(); res }