miniweb/src/service/templates.rs

167 lines
4.9 KiB
Rust

use crate::service::error::Error;
use axum::response::Html;
use minijinja::value::ValueKind;
use minijinja::{path_loader, Environment, Value};
use minijinja_autoreload::AutoReloader;
use pulldown_cmark::Event;
use serde::Deserializer;
use std::sync::Arc;
#[derive(Clone)]
pub struct Templates {
pub reloader: Arc<minijinja_autoreload::AutoReloader>,
}
impl Templates {
pub fn initialize() -> Result<Self, String> {
let reloader = AutoReloader::new(move |notifier| {
let mut environment = Environment::new();
let template_path = "templates";
environment.set_loader(path_loader(&template_path));
environment.add_filter("none", none);
environment.add_filter("markdown", markdown);
environment.add_filter("yesno", filter_yesno);
environment.add_filter("table_keys", collect_unique_keys);
environment.add_function("static", tpl_static);
environment.add_function("url", tpl_url);
environment.add_function("csrf_token", tpl_to_be_implemented);
environment.add_function("translate", tpl_translate);
notifier.watch_path(template_path, true);
Ok(environment)
});
Ok(Self {
reloader: Arc::new(reloader),
})
}
pub fn render_template<D: serde::Serialize>(
&self,
key: &str,
data: D,
) -> Result<String, Error> {
let env = self.reloader.acquire_env()?;
let template = env.get_template(key)?;
let rendered = template.render(&data)?;
Ok(rendered)
}
pub fn render_html<D: serde::Serialize>(
&self,
key: &str,
data: D,
) -> Result<Html<String>, Error> {
let result = self.render_template(key, data)?;
Ok(Html(result))
}
}
fn markdown(value: String) -> Value {
let mut options = pulldown_cmark::Options::empty();
options.insert(pulldown_cmark::Options::ENABLE_STRIKETHROUGH);
options.insert(pulldown_cmark::Options::ENABLE_TABLES);
options.insert(pulldown_cmark::Options::ENABLE_TASKLISTS);
options.insert(pulldown_cmark::Options::ENABLE_SMART_PUNCTUATION);
let parser = pulldown_cmark::Parser::new_ext(&value, options);
let parser: Box<dyn Iterator<Item = Event>> = Box::new(parser.into_iter());
let mut html = String::new();
pulldown_cmark::html::push_html(&mut html, parser);
Value::from_safe_string(html)
}
fn filter_yesno(value: String, yesno: String) -> String {
"".into()
}
fn tpl_url(value: String) -> Value {
Value::from_safe_string(value.into())
}
fn tpl_translate(value: String) -> Value {
Value::from_safe_string(value.into())
}
fn tpl_to_be_implemented(value: String) -> Value {
Value::from_safe_string("<!-- To Be Implemented --/>".into())
}
fn tpl_static(value: String) -> Value {
Value::from_safe_string(format!("/static/{}", value))
}
pub fn none(value: Value, other: Option<Value>) -> Value {
if value.is_undefined() || value.is_none() {
other.unwrap_or_else(|| Value::from(""))
} else {
value
}
}
fn tpl_table(values: Vec<Value>) -> Value {
Value::from_safe_string(format!(
"Output: {}",
values.first().expect("No values found.").kind()
))
}
mod helper {
use std::collections::HashSet;
pub struct OrderedSet<T> {
set: HashSet<T>,
vec: Vec<T>,
}
impl<T: Eq + std::hash::Hash + Clone> OrderedSet<T> {
pub fn new() -> Self {
OrderedSet {
set: HashSet::new(),
vec: Vec::new(),
}
}
pub fn insert(&mut self, value: T) {
if self.set.insert(value.clone()) {
self.vec.push(value);
}
}
pub fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
for item in iter {
self.insert(item);
}
}
pub fn iter(&self) -> std::slice::Iter<T> {
self.vec.iter()
}
}
impl<T> IntoIterator for OrderedSet<T> {
type Item = T;
type IntoIter = std::vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.vec.into_iter()
}
}
}
fn collect_unique_keys(values: Value) -> Value {
use helper::OrderedSet;
use serde::Deserialize;
let mut unique_keys: OrderedSet<String> = OrderedSet::new();
if let Ok(vec) = Vec::<serde_json::Value>::deserialize(values) {
for value in vec.iter() {
if let Some(dict) = value.as_object() {
let keys_vec: Vec<String> = dict.keys().map(|s| s.to_owned()).collect();
unique_keys.extend(keys_vec);
}
}
}
let keys_list = unique_keys.into_iter().collect::<Vec<_>>();
Value::from(keys_list)
}