use crate::config::Config;
use crate::data::Context;
use crate::errors::Result;
use crate::site::layout::LayoutContext;
use crate::site::markdown::SyntaxTheme;
use crate::site::{link, markdown};
use include_dir::{include_dir, Dir};
use minijinja::value::Value;
use minijinja::{context, AutoEscape, Environment, Template};
use std::collections::HashMap;
const TEMPLATE_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/templates/site");
pub struct Templates<'a> {
pub env: Environment<'a>,
pub layout: LayoutContext,
}
impl<'a> Templates<'a> {
pub fn new(config: &Config, context: Option<&Context>) -> Result<Self> {
let mut env = Environment::new();
let mut files = HashMap::new();
Self::load_files(&TEMPLATE_DIR, &mut files)
.expect("failed to load jinja2 templates from binary");
for (path, contents) in files {
env.add_template_owned(path, contents)
.expect("failed to add jinja2 template");
}
env.add_filter("generate_link", Self::generate_link);
env.add_filter("syntax_highlight", Self::syntax_highlight);
env.set_auto_escape_callback(|_| AutoEscape::None);
let layout = LayoutContext::new(config, context)?;
Ok(Self { env, layout })
}
pub fn new_for_workspace_index(workspace_config: &Config) -> Result<Self> {
let mut env = Environment::new();
let mut files = HashMap::new();
let dir = &TEMPLATE_DIR
.get_dir("workspace_index")
.expect("workspace_index directory not found");
Self::load_files(dir, &mut files).expect("failed to load jinja2 templates from binary");
for (path, contents) in files {
env.add_template_owned(path, contents)
.expect("failed to add jinja2 template");
}
env.add_filter("generate_link", Self::generate_link);
let layout = LayoutContext::new_for_workspace_index(workspace_config)?;
Ok(Self { env, layout })
}
pub fn get(&self, name: &str) -> Result<Template> {
Ok(self.env.get_template(name)?)
}
pub fn render_to_string(&self, name: &str, context: Value) -> Result<String> {
let context_with_layout = context!(layout => self.layout, page => context);
let template = self.env.get_template(name)?;
Ok(template.render(context_with_layout)?)
}
fn load_files(dir: &Dir, files: &mut HashMap<String, String>) -> Result<()> {
for entry in dir.entries() {
if let Some(file) = entry.as_file() {
let file_path = file.path();
let file_name = file_path.with_extension("");
files.insert(
file_name.display().to_string(),
file.contents_utf8()
.expect("non-utf8 jinja2 template")
.to_string(),
);
}
if let Some(dir) = entry.as_dir() {
Self::load_files(dir, files).expect("failed to load jinja2 templates from binary");
}
}
Ok(())
}
fn generate_link(base: String, path_prefix: String) -> String {
let path_prefix = if path_prefix == "none" {
None
} else {
Some(path_prefix)
};
link::generate_relative(&path_prefix, &base)
}
fn syntax_highlight(code: String, lang: String, _syntax_theme: String) -> String {
let syntax_theme = SyntaxTheme::MaterialTheme;
match markdown::syntax_highlight(Some(&lang), &code, &syntax_theme) {
Ok(res) => res,
Err(_) => format!("<code class='inline-code'>{code}</code>"),
}
}
}