diff options
author | Joris | 2025-01-26 17:58:57 +0100 |
---|---|---|
committer | Joris | 2025-01-26 17:58:57 +0100 |
commit | 24eeb54a6b7159964e8887ade7fa5173b50feb3a (patch) | |
tree | 91af6253df784445db9b084b02b38b37a83224e8 /src | |
parent | c5759f348e70cf54b4bfa4cd17e1fe1828ead30a (diff) |
Replace tera by minijinjamain
tera was doing the job all right, but minijinja has fewer dependencies.
Diffstat (limited to 'src')
-rw-r--r-- | src/assets.rs | 2 | ||||
-rw-r--r-- | src/controller/balance.rs | 21 | ||||
-rw-r--r-- | src/controller/categories.rs | 40 | ||||
-rw-r--r-- | src/controller/error.rs | 14 | ||||
-rw-r--r-- | src/controller/incomes.rs | 56 | ||||
-rw-r--r-- | src/controller/login.rs | 21 | ||||
-rw-r--r-- | src/controller/payments.rs | 62 | ||||
-rw-r--r-- | src/controller/statistics.rs | 15 | ||||
-rw-r--r-- | src/controller/utils.rs | 39 | ||||
-rw-r--r-- | src/controller/wallet.rs | 3 | ||||
-rw-r--r-- | src/db/jobs.rs | 4 | ||||
-rw-r--r-- | src/db/payments.rs | 6 | ||||
-rw-r--r-- | src/jobs/mod.rs | 7 | ||||
-rw-r--r-- | src/jobs/weekly_report.rs | 27 | ||||
-rw-r--r-- | src/mail.rs | 4 | ||||
-rw-r--r-- | src/main.rs | 3 | ||||
-rw-r--r-- | src/routes.rs | 3 | ||||
-rw-r--r-- | src/templates.rs | 164 |
18 files changed, 283 insertions, 208 deletions
diff --git a/src/assets.rs b/src/assets.rs index 36fab55..fdfbfdd 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -1,8 +1,8 @@ use sha2::{Digest, Sha256}; use std::collections::HashMap; +use std::fmt::Write; use std::fs; use std::iter::FromIterator; -use std::fmt::Write; pub fn get() -> HashMap<String, String> { let paths = fs::read_dir("assets").unwrap().map(|e| { diff --git a/src/controller/balance.rs b/src/controller/balance.rs index c5d9d4a..efe5af6 100644 --- a/src/controller/balance.rs +++ b/src/controller/balance.rs @@ -2,7 +2,6 @@ use http_body_util::Full; use hyper::body::Bytes; use hyper::Response; use std::collections::HashMap; -use tera::Context; use crate::controller::utils; use crate::controller::wallet::Wallet; @@ -31,18 +30,16 @@ pub async fn get(wallet: &Wallet) -> Response<Full<Bytes>> { let exceeding_payers = payer::exceeding(&users, &user_incomes, &user_payments); - let mut context = Context::new(); - context.insert("header", &templates::Header::Balance); - context.insert("connected_user", &wallet.user); - context.insert( - "incomes_from", - &incomes_from.map(|d| d.format("%d/%m/%Y").to_string()), + let context = minijinja::context!( + header => templates::Header::Balance, + connected_user => wallet.user, + incomes_from => incomes_from.map(|d| d.format("%d/%m/%Y").to_string()), + total_income => total_income, + user_incomes => template_user_incomes, + total_payments => total_payments, + user_payments => template_user_payments, + exceeding_payers => exceeding_payers ); - context.insert("total_income", &total_income); - context.insert("user_incomes", &template_user_incomes); - context.insert("total_payments", &total_payments); - context.insert("user_payments", &template_user_payments); - context.insert("exceeding_payers", &exceeding_payers); utils::template(&wallet.assets, &wallet.templates, "balance.html", context) } diff --git a/src/controller/categories.rs b/src/controller/categories.rs index ff2d8e7..fbbd309 100644 --- a/src/controller/categories.rs +++ b/src/controller/categories.rs @@ -2,7 +2,6 @@ use http_body_util::Full; use hyper::body::Bytes; use hyper::Response; use std::collections::HashMap; -use tera::Context; use crate::controller::utils; use crate::controller::wallet::Wallet; @@ -17,11 +16,12 @@ pub async fn table( ) -> Response<Full<Bytes>> { let categories = db::categories::list(&wallet.pool).await; - let mut context = Context::new(); - context.insert("header", &templates::Header::Categories); - context.insert("connected_user", &wallet.user); - context.insert("categories", &categories); - context.insert("highlight", &query.highlight); + let context = minijinja::context!( + header => templates::Header::Categories, + connected_user => wallet.user, + categories => categories, + highlight => query.highlight + ); utils::template( &wallet.assets, @@ -40,11 +40,12 @@ async fn create_form_feedback( form: HashMap<String, String>, error: Option<String>, ) -> Response<Full<Bytes>> { - let mut context = Context::new(); - context.insert("header", &templates::Header::Categories); - context.insert("connected_user", &wallet.user.clone()); - context.insert("form", &form); - context.insert("error", &error); + let context = minijinja::context!( + header => &templates::Header::Categories, + connected_user => &wallet.user.clone(), + form => &form, + error => &error + ); utils::template( &wallet.assets, @@ -89,14 +90,15 @@ async fn update_form_feedback( let is_category_used = db::payments::is_category_used(&wallet.pool, id).await; - let mut context = Context::new(); - context.insert("header", &templates::Header::Categories); - context.insert("connected_user", &wallet.user); - context.insert("id", &id); - context.insert("category", &category); - context.insert("is_category_used", &is_category_used); - context.insert("form", &form); - context.insert("error", &error); + let context = minijinja::context!( + header => templates::Header::Categories, + connected_user => wallet.user, + id => id, + category => category, + is_category_used => is_category_used, + form => form, + error => error + ); utils::template( &wallet.assets, diff --git a/src/controller/error.rs b/src/controller/error.rs index 0f6dcc1..fb1375c 100644 --- a/src/controller/error.rs +++ b/src/controller/error.rs @@ -3,7 +3,6 @@ use hyper::body::Bytes; use hyper::header::CACHE_CONTROL; use hyper::Response; use std::collections::HashMap; -use tera::{Context, Tera}; use crate::controller::utils; use crate::controller::wallet::Wallet; @@ -24,14 +23,15 @@ pub fn error( pub fn template( assets: &HashMap<String, String>, - templates: &Tera, + templates: &minijinja::Environment<'_>, title: &str, message: &str, ) -> String { - let mut context = Context::new(); - context.insert("title", title); - context.insert("message", message); - context.insert("assets", assets); + let context = minijinja::context!( + title => title, + message => message, + assets => assets + ); - templates.render("error.html", &context).unwrap() + templates.render_str("error.html", &context).unwrap() } diff --git a/src/controller/incomes.rs b/src/controller/incomes.rs index f22098b..ac3a332 100644 --- a/src/controller/incomes.rs +++ b/src/controller/incomes.rs @@ -4,7 +4,6 @@ use http_body_util::Full; use hyper::body::Bytes; use hyper::Response; use std::collections::HashMap; -use tera::Context; use crate::controller::utils; use crate::controller::wallet::Wallet; @@ -24,13 +23,14 @@ pub async fn table( let incomes = db::incomes::list(&wallet.pool, page, PER_PAGE).await; let max_page = (count as f32 / PER_PAGE as f32).ceil() as i64; - let mut context = Context::new(); - context.insert("header", &templates::Header::Incomes); - context.insert("connected_user", &wallet.user); - context.insert("incomes", &incomes); - context.insert("page", &page); - context.insert("max_page", &max_page); - context.insert("highlight", &query.highlight); + let context = minijinja::context!( + header => templates::Header::Incomes, + connected_user => wallet.user, + incomes => incomes, + page => page, + max_page => max_page, + highlight => query.highlight + ); utils::template( &wallet.assets, @@ -70,15 +70,16 @@ async fn create_form_feedback( ) -> Response<Full<Bytes>> { let users = db::users::list(&wallet.pool).await; - let mut context = Context::new(); - context.insert("header", &templates::Header::Incomes); - context.insert("connected_user", &wallet.user); - context.insert("users", &users); - context.insert("query", &query); - context.insert("current_month", &Utc::now().date_naive().month()); - context.insert("months", &MONTHS); - context.insert("form", &form); - context.insert("error", &error); + let context = minijinja::context!( + header => templates::Header::Incomes, + connected_user => wallet.user, + users => users, + query => query, + current_month => Utc::now().date_naive().month(), + months => MONTHS, + form => form, + error => error, + ); utils::template( &wallet.assets, @@ -144,16 +145,17 @@ async fn update_form_feedback( let users = db::users::list(&wallet.pool).await; let income = db::incomes::get(&wallet.pool, id).await; - let mut context = Context::new(); - context.insert("header", &templates::Header::Incomes); - context.insert("connected_user", &wallet.user); - context.insert("users", &users); - context.insert("id", &id); - context.insert("income", &income); - context.insert("query", &query); - context.insert("months", &MONTHS); - context.insert("form", &form); - context.insert("error", &error); + let context = minijinja::context!( + header => &templates::Header::Incomes, + connected_user => &wallet.user, + users => &users, + id => &id, + income => &income, + query => &query, + months => &MONTHS, + form => &form, + error => &error + ); utils::template( &wallet.assets, diff --git a/src/controller/login.rs b/src/controller/login.rs index a1bf466..31370cc 100644 --- a/src/controller/login.rs +++ b/src/controller/login.rs @@ -5,7 +5,6 @@ use hyper::header::SET_COOKIE; use hyper::Response; use sqlx::sqlite::SqlitePool; use std::collections::HashMap; -use tera::{Context, Tera}; use crate::controller::utils::with_headers; use crate::controller::wallet::Wallet; @@ -18,14 +17,15 @@ use crate::validation; pub async fn page( assets: &HashMap<String, String>, - templates: &Tera, + templates: &minijinja::Environment<'_>, error: Option<&str>, ) -> Response<Full<Bytes>> { let connected_user: Option<User> = None; - let mut context = Context::new(); - context.insert("connected_user", &connected_user); - context.insert("error", &error); + let context = minijinja::context!( + connected_user => &connected_user, + error => &error + ); utils::template(assets, templates, "login.html", context) } @@ -33,7 +33,7 @@ pub async fn page( pub async fn login( config: &Config, assets: &HashMap<String, String>, - templates: &Tera, + templates: &minijinja::Environment<'_>, form: HashMap<String, String>, pool: SqlitePool, ) -> Response<Full<Bytes>> { @@ -75,7 +75,10 @@ pub async fn login( } Ok(false) => not_authorized(assets, templates).await, Err(err) => { - log::error!("Error verifying bcrypt password: {:?}", err); + log::error!( + "Error verifying bcrypt password: {:?}", + err + ); server_error(assets, templates, "Erreur serveur").await } }, @@ -88,7 +91,7 @@ pub async fn login( async fn server_error( assets: &HashMap<String, String>, - templates: &Tera, + templates: &minijinja::Environment<'_>, msg: &str, ) -> Response<Full<Bytes>> { page(assets, templates, Some(msg)).await @@ -96,7 +99,7 @@ async fn server_error( async fn not_authorized( assets: &HashMap<String, String>, - templates: &Tera, + templates: &minijinja::Environment<'_>, ) -> Response<Full<Bytes>> { page( assets, diff --git a/src/controller/payments.rs b/src/controller/payments.rs index 8184015..b5c0256 100644 --- a/src/controller/payments.rs +++ b/src/controller/payments.rs @@ -3,7 +3,6 @@ use hyper::body::Bytes; use hyper::header::CONTENT_TYPE; use hyper::Response; use std::collections::HashMap; -use tera::Context; use crate::controller::utils; use crate::controller::wallet::Wallet; @@ -27,17 +26,18 @@ pub async fn table( let users = db::users::list(&wallet.pool).await; let categories = db::categories::list(&wallet.pool).await; - let mut context = Context::new(); - context.insert("header", &templates::Header::Payments); - context.insert("connected_user", &wallet.user); - context.insert("payments", &payments); - context.insert("page", &page); - context.insert("max_page", &max_page); - context.insert("query", &query); - context.insert("count", &count.count); - context.insert("total_cost", &count.total_cost); - context.insert("users", &users); - context.insert("categories", &categories); + let context = minijinja::context!( + header => templates::Header::Payments, + connected_user => wallet.user, + payments => payments, + page => page, + max_page => max_page, + query => query, + count => count.count, + total_cost => count.total_cost, + users => users, + categories => categories + ); utils::template( &wallet.assets, @@ -63,14 +63,15 @@ async fn create_form_feedback( let users = db::users::list(&wallet.pool).await; let categories = db::categories::list(&wallet.pool).await; - let mut context = Context::new(); - context.insert("header", &templates::Header::Payments); - context.insert("connected_user", &wallet.user); - context.insert("users", &users); - context.insert("categories", &categories); - context.insert("query", &query); - context.insert("form", &form); - context.insert("error", &error); + let context = minijinja::context!( + header => templates::Header::Payments, + connected_user => wallet.user, + users => users, + categories => categories, + query => query, + form => form, + error => error + ); utils::template( &wallet.assets, @@ -142,16 +143,17 @@ async fn update_form_feedback( let users = db::users::list(&wallet.pool).await; let categories = db::categories::list(&wallet.pool).await; - let mut context = Context::new(); - context.insert("header", &templates::Header::Payments); - context.insert("connected_user", &wallet.user); - context.insert("id", &id); - context.insert("payment", &payment); - context.insert("users", &users); - context.insert("categories", &categories); - context.insert("query", &query); - context.insert("form", &form); - context.insert("error", &error); + let context = minijinja::context!( + header => templates::Header::Payments, + connected_user => wallet.user, + id => id, + payment => payment, + users => users, + categories => categories, + query => query, + form => form, + error => error + ); utils::template( &wallet.assets, diff --git a/src/controller/statistics.rs b/src/controller/statistics.rs index eb1e704..e57e2be 100644 --- a/src/controller/statistics.rs +++ b/src/controller/statistics.rs @@ -1,7 +1,6 @@ use http_body_util::Full; use hyper::body::Bytes; use hyper::Response; -use tera::Context; use crate::controller::utils; use crate::controller::wallet::Wallet; @@ -13,15 +12,13 @@ pub async fn get(wallet: &Wallet) -> Response<Full<Bytes>> { let payments = db::payments::list_for_stats(&wallet.pool).await; let incomes = db::incomes::total_each_month(&wallet.pool).await; - let mut context = Context::new(); - context.insert("header", &templates::Header::Statistics); - context.insert("connected_user", &wallet.user); - context.insert( - "json_categories", - &serde_json::to_string(&categories).unwrap(), + let context = minijinja::context!( + header => templates::Header::Statistics, + connected_user => wallet.user, + json_categories => serde_json::to_string(&categories).unwrap(), + json_payments => serde_json::to_string(&payments).unwrap(), + json_incomes => serde_json::to_string(&incomes).unwrap() ); - context.insert("json_payments", &serde_json::to_string(&payments).unwrap()); - context.insert("json_incomes", &serde_json::to_string(&incomes).unwrap()); utils::template( &wallet.assets, diff --git a/src/controller/utils.rs b/src/controller/utils.rs index 1b58c68..340a5c7 100644 --- a/src/controller/utils.rs +++ b/src/controller/utils.rs @@ -5,7 +5,6 @@ use hyper::header::{ }; use hyper::{Response, StatusCode}; use std::collections::HashMap; -use tera::{Context, Tera}; use crate::controller::error; @@ -23,29 +22,45 @@ pub fn with_headers( pub fn template( assets: &HashMap<String, String>, - templates: &Tera, + templates: &minijinja::Environment<'_>, path: &str, - context: Context, + context: minijinja::Value, ) -> Response<Full<Bytes>> { - let mut context = context; - context.insert("assets", assets); + let context = minijinja::context! { ..context, ..minijinja::context! { + assets => assets + }}; - match templates.render(path, &context) { + match render_template(templates, path, context) { Ok(template) => with_headers( Response::new(template.into()), vec![(CONTENT_TYPE, "text/html"), (CACHE_CONTROL, "no-cache")], ), - Err(err) => server_error( - assets, - templates, - &format!("Erreur lors de la préparation de la page : {:?}", err), - ), + Err(err) => { + log::error!("ERROR template rendering {}\n{:?}", path, err); + server_error( + assets, + templates, + &format!( + "Erreur lors de la préparation de la page : {:?}", + err + ), + ) + } } } +fn render_template( + templates: &minijinja::Environment<'_>, + name: &str, + context: minijinja::Value, +) -> Result<String, minijinja::Error> { + let template = templates.get_template(name)?; + template.render(context) +} + fn server_error( assets: &HashMap<String, String>, - templates: &Tera, + templates: &minijinja::Environment<'_>, msg: &str, ) -> Response<Full<Bytes>> { with_headers( diff --git a/src/controller/wallet.rs b/src/controller/wallet.rs index 2a4a593..7537406 100644 --- a/src/controller/wallet.rs +++ b/src/controller/wallet.rs @@ -1,6 +1,5 @@ use sqlx::sqlite::SqlitePool; use std::collections::HashMap; -use tera::Tera; use crate::model::user::User; @@ -8,6 +7,6 @@ use crate::model::user::User; pub struct Wallet { pub pool: SqlitePool, pub assets: HashMap<String, String>, - pub templates: Tera, + pub templates: minijinja::Environment<'static>, pub user: User, } diff --git a/src/db/jobs.rs b/src/db/jobs.rs index a80ef68..7d9386a 100644 --- a/src/db/jobs.rs +++ b/src/db/jobs.rs @@ -48,6 +48,8 @@ WHERE match res { Ok(_) => (), - Err(err) => log::error!("Error actualizing job last execution: {:?}", err), + Err(err) => { + log::error!("Error actualizing job last execution: {:?}", err) + } } } diff --git a/src/db/payments.rs b/src/db/payments.rs index b415a28..25b10f4 100644 --- a/src/db/payments.rs +++ b/src/db/payments.rs @@ -495,7 +495,8 @@ ORDER BY Err(err) => { log::error!( "Error looking for the category of {}: {:?}", - payment_name, err + payment_name, + err ); None } @@ -523,7 +524,8 @@ LIMIT Err(err) => { log::error!( "Error looking if category {} is used: {:?}", - category_id, err + category_id, + err ); false } diff --git a/src/jobs/mod.rs b/src/jobs/mod.rs index 17df58c..a718d93 100644 --- a/src/jobs/mod.rs +++ b/src/jobs/mod.rs @@ -1,14 +1,17 @@ mod weekly_report; use sqlx::sqlite::SqlitePool; -use tera::Tera; use tokio::time::{sleep, Duration}; use crate::db; use crate::model::config::Config; use crate::model::job::Job; -pub async fn start(config: Config, pool: SqlitePool, templates: Tera) { +pub async fn start( + config: Config, + pool: SqlitePool, + templates: minijinja::Environment<'_>, +) { loop { if db::jobs::should_run(&pool, Job::WeeklyReport).await { log::info!("Starting weekly report job"); diff --git a/src/jobs/weekly_report.rs b/src/jobs/weekly_report.rs index 0c10143..5058c52 100644 --- a/src/jobs/weekly_report.rs +++ b/src/jobs/weekly_report.rs @@ -1,6 +1,5 @@ use sqlx::sqlite::SqlitePool; use std::collections::HashMap; -use tera::{Context, Tera}; use crate::db; use crate::mail; @@ -10,9 +9,9 @@ use crate::payer; pub async fn send( config: &Config, pool: &SqlitePool, - templates: &Tera, + env: &minijinja::Environment<'_>, ) -> bool { - match get_weekly_report(pool, templates).await { + match get_weekly_report(pool, env).await { Ok(report) => { let users = db::users::list(pool).await; mail::send( @@ -30,7 +29,10 @@ pub async fn send( .await } Err(err) => { - log::error!("Error preparing weekly report from template: {:?}", err); + log::error!( + "Error preparing weekly report from template: {:?}", + err + ); false } } @@ -38,8 +40,8 @@ pub async fn send( async fn get_weekly_report( pool: &SqlitePool, - templates: &Tera, -) -> Result<String, tera::Error> { + env: &minijinja::Environment<'_>, +) -> Result<String, minijinja::Error> { let users = db::users::list(pool).await; let incomes_from = db::incomes::defined_for_all(pool).await; let user_incomes = match incomes_from { @@ -53,10 +55,11 @@ async fn get_weekly_report( let last_week_payments = db::payments::last_week(pool).await; let last_week_incomes = db::incomes::last_week(pool).await; - let mut context = Context::new(); - context.insert("exceeding_payers", &exceeding_payers); - context.insert("payments", &last_week_payments); - context.insert("incomes", &last_week_incomes); - - templates.render("report/report.j2", &context) + let template = env.get_template("report/report.j2")?; + template.render(minijinja::context!( + name => "John", + exceeding_payers => exceeding_payers, + payments => last_week_payments, + incomes => last_week_incomes + )) } diff --git a/src/mail.rs b/src/mail.rs index c77e2ad..b6db0cd 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -56,7 +56,9 @@ pub async fn send( true } else { match String::from_utf8(output.stderr) { - Ok(error) => log::error!("Error sending email: {}", error), + Ok(error) => { + log::error!("Error sending email: {}", error) + } _ => log::error!("Error sending email"), }; false diff --git a/src/main.rs b/src/main.rs index 2b3aebd..5fe8a94 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,6 @@ use model::config; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { - env_logger::init(); let config = config::from_env() @@ -34,7 +33,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { let assets = assets::get(); - let templates = templates::get(); + let templates = templates::get()?; tokio::spawn(jobs::start(config.clone(), pool.clone(), templates.clone())); diff --git a/src/routes.rs b/src/routes.rs index ae87d39..5f17ca5 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -5,7 +5,6 @@ use serde::Deserialize; use sqlx::sqlite::SqlitePool; use std::collections::HashMap; use std::convert::Infallible; -use tera::Tera; use url::form_urlencoded; use crate::controller; @@ -20,7 +19,7 @@ pub async fn routes( config: Config, pool: SqlitePool, assets: HashMap<String, String>, - templates: Tera, + templates: minijinja::Environment<'static>, request: Request<Incoming>, ) -> Result<Response<Full<Bytes>>, Infallible> { let method = request.method(); diff --git a/src/templates.rs b/src/templates.rs index 1f86717..c9a750b 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -1,9 +1,5 @@ use serde::Serialize; -use serde_json::json; -use serde_json::value::Value; -use std::collections::HashMap; -use tera::Tera; -use tera::{Error, Result}; +use std::fs; use crate::queries; @@ -16,71 +12,123 @@ pub enum Header { Statistics, } -pub fn get() -> Tera { - let mut tera = match Tera::new("templates/**/*") { - Ok(t) => t, - Err(e) => { - log::error!("Parsing error(s): {}", e); - ::std::process::exit(1); - } +pub fn get() -> Result<minijinja::Environment<'static>, 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<std::path::Path>, +) -> Vec<std::path::PathBuf> { + let Ok(entries) = fs::read_dir(path) else { + return vec![]; }; - tera.register_function("payments_params", payments_params); - tera.register_filter("numeric", numeric); - tera.register_filter("euros", euros); - tera + 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(args: &HashMap<String, Value>) -> Result<Value> { - let q = json!({ - "page": args.get("page"), - "name": args.get("name"), - "cost": args.get("cost"), - "frequency": args.get("frequency"), - "highlight": args.get("highlight"), - "user": args.get("user"), - "category": args.get("category"), - "start_date": args.get("start_date"), - "end_date": args.get("end_date"), - }); - - match serde_json::from_value(q) { - Ok(q) => Ok(json!(queries::payments_url(q))), - Err(msg) => Err(Error::msg(msg)), +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 euros(value: &Value, _: &HashMap<String, Value>) -> Result<Value> { - match value { - Value::Number(n) => { - if let Some(n) = n.as_i64() { - let str = rgrouped(n.abs().to_string(), 3).join(" "); - let sign = if n < 0 { "-" } else { "" }; - Ok(json!(format!("{}{} €", sign, str))) - } else if let Some(n) = n.as_f64() { - Ok(json!(format!("{} €", n))) - } else { - Err(Error::msg("Error parsing number")) - } - } - _ => Err(Error::msg(format!("{:?} should be a number", value))), +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 numeric(value: &Value, _: &HashMap<String, Value>) -> Result<Value> { - match value { - Value::Number(n) => { - if let Some(n) = n.as_i64() { - let str = rgrouped(n.abs().to_string(), 3).join(" "); - let sign = if n < 0 { "-" } else { "" }; - Ok(json!(format!("{}{}", sign, str))) - } else if let Some(n) = n.as_f64() { - Ok(json!(format!("{}", n))) - } else { - Err(Error::msg("Error parsing number")) +fn filter( + xs: Vec<minijinja::Value>, + key: &str, + value: String, +) -> Vec<minijinja::Value> { + 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); + } } } - _ => Err(Error::msg(format!("{:?} should be a number", value))), } + res } fn rgrouped(str: String, n: usize) -> Vec<String> { |