167 lines
4.9 KiB
Rust
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)
|
|
}
|