rhombus 0.2.21

Next generation extendable CTF framework with batteries included
Documentation
use std::{
    collections::HashMap,
    sync::{Arc, Mutex},
};

use rust_embed::RustEmbed;

#[derive(RustEmbed)]
#[folder = "templates"]
struct EmbeddedTemplates;

pub struct Templates {
    pub plugin_map: Arc<Mutex<HashMap<String, String>>>,
    pub core_map: Arc<Mutex<HashMap<String, String>>>,
}

impl Default for Templates {
    fn default() -> Self {
        Self::new()
    }
}

impl Templates {
    pub fn new() -> Self {
        let mut map = HashMap::new();

        for file in EmbeddedTemplates::iter() {
            let mut bytes = EmbeddedTemplates::get(&file).unwrap().data.to_vec();
            let cfg = &minify_html_onepass::Cfg {
                minify_js: true,
                minify_css: true,
            };
            if file.ends_with(".html") {
                minify_html_onepass::truncate(&mut bytes, cfg).unwrap();
            }
            let content = String::from_utf8(bytes).unwrap();

            map.insert(file.to_string(), content);
        }

        Self {
            core_map: Arc::new(Mutex::new(map)),
            plugin_map: Arc::new(Mutex::new(HashMap::new())),
        }
    }

    pub fn add_template(&mut self, name: &str, content: &str) {
        let content = if name.ends_with(".html") {
            let mut bytes = content.as_bytes().to_vec();
            let cfg = &minify_html_onepass::Cfg {
                minify_js: true,
                minify_css: true,
            };
            minify_html_onepass::truncate(&mut bytes, cfg).unwrap();
            String::from_utf8(bytes).unwrap()
        } else {
            content.to_string()
        };

        self.plugin_map
            .lock()
            .unwrap()
            .entry(name.to_string())
            .and_modify(|previous_content| {
                *previous_content = format!("{}\n{}", previous_content, content);
            })
            .or_insert(content);
    }

    pub fn set_template(&mut self, name: &str, content: &str) {
        let content = if name.ends_with(".html") {
            let mut bytes = content.as_bytes().to_vec();
            let cfg = &minify_html_onepass::Cfg {
                minify_js: true,
                minify_css: true,
            };
            minify_html_onepass::truncate(&mut bytes, cfg).unwrap();
            String::from_utf8(bytes).unwrap()
        } else {
            content.to_string()
        };

        self.plugin_map
            .lock()
            .unwrap()
            .insert(name.to_string(), content);
    }

    pub fn build(&self) -> minijinja::Environment<'static> {
        let mut jinja = minijinja::Environment::new();
        let core_map = self.core_map.clone();
        let plugin_map = self.plugin_map.clone();

        jinja.set_loader(move |name| {
            if let Some(s) = plugin_map.lock().unwrap().remove(name) {
                Ok(Some(s))
            } else if let Some(s) = core_map.lock().unwrap().remove(name) {
                Ok(Some(s))
            } else {
                Ok(None)
            }
        });

        jinja
    }
}