pub mod conditional;
use anyhow::Result;
use include_dir::{include_dir, Dir};
use std::collections::HashSet;
use tera::{Context, Tera};
static TEMPLATES_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/templates");
pub struct TemplateEngine {
tera: Tera,
features: HashSet<String>,
}
impl TemplateEngine {
pub fn new() -> Result<Self> {
Self::with_features(vec![])
}
pub fn with_features(features: Vec<String>) -> Result<Self> {
let mut tera = Tera::default();
Self::load_embedded_templates(&mut tera)?;
Ok(Self {
tera,
features: features.into_iter().collect(),
})
}
pub fn render(&self, template_name: &str, context: &Context) -> Result<String> {
let mut context = context.clone();
context.insert(
"features",
&self.features.iter().cloned().collect::<Vec<_>>(),
);
for feature in &self.features {
context.insert(&format!("has_{}", feature), &true);
}
let rendered = self.tera.render(template_name, &context)?;
Ok(rendered)
}
pub fn render_with_context(&self, template_name: &str, context: &Context) -> Result<String> {
self.render(template_name, context)
}
fn load_embedded_templates(tera: &mut Tera) -> Result<()> {
Self::load_directory_templates(tera, &TEMPLATES_DIR, "")?;
Ok(())
}
fn load_directory_templates(tera: &mut Tera, dir: &Dir<'_>, prefix: &str) -> Result<()> {
for file in dir.files() {
if let Some(file_name) = file.path().file_name() {
if let Some(file_name_str) = file_name.to_str() {
if file_name_str.ends_with(".tera") {
let template_name = if prefix.is_empty() {
file_name_str.to_string()
} else {
format!("{}/{}", prefix, file_name_str)
};
if let Some(contents) = file.contents_utf8() {
tera.add_raw_template(&template_name, contents)?;
}
}
}
}
}
for subdir in dir.dirs() {
if let Some(dir_name) = subdir.path().file_name() {
if let Some(dir_name_str) = dir_name.to_str() {
let new_prefix = if prefix.is_empty() {
dir_name_str.to_string()
} else {
format!("{}/{}", prefix, dir_name_str)
};
Self::load_directory_templates(tera, subdir, &new_prefix)?;
}
}
}
Ok(())
}
pub fn list_templates(&self) -> Vec<String> {
let mut templates = Vec::new();
Self::collect_template_names(&TEMPLATES_DIR, "", &mut templates);
templates.sort();
templates
}
fn collect_template_names(dir: &Dir<'_>, prefix: &str, templates: &mut Vec<String>) {
for file in dir.files() {
if let Some(file_name) = file.path().file_name() {
if let Some(file_name_str) = file_name.to_str() {
if file_name_str.ends_with(".tera") {
let template_name = if prefix.is_empty() {
file_name_str.to_string()
} else {
format!("{}/{}", prefix, file_name_str)
};
templates.push(template_name);
}
}
}
}
for subdir in dir.dirs() {
if let Some(dir_name) = subdir.path().file_name() {
if let Some(dir_name_str) = dir_name.to_str() {
let new_prefix = if prefix.is_empty() {
dir_name_str.to_string()
} else {
format!("{}/{}", prefix, dir_name_str)
};
Self::collect_template_names(subdir, &new_prefix, templates);
}
}
}
}
pub fn get_feature_templates(&self, features: &[String]) -> Vec<String> {
let mut templates = Vec::new();
templates.extend(self.list_templates());
for feature in features {
let feature_prefix = format!("features/{}/", feature);
templates.extend(
self.list_templates()
.into_iter()
.filter(|t| t.starts_with(&feature_prefix)),
);
}
templates.sort();
templates.dedup();
templates
}
pub fn has_feature(&self, feature: &str) -> bool {
self.features.contains(feature)
}
pub fn add_feature(&mut self, feature: String) {
self.features.insert(feature);
}
pub fn get_features(&self) -> Vec<String> {
self.features.iter().cloned().collect()
}
}