use crate::app_config::AppConfig;
use crate::file_cache::AsyncFromStrWithState;
use crate::template_helpers::register_all_helpers;
use crate::{AppState, FileCache, TEMPLATES_DIR};
use async_trait::async_trait;
use handlebars::{Handlebars, Template, template::TemplateElement};
use include_dir::{Dir, include_dir};
use std::path::{Path, PathBuf};
use std::sync::Arc;
pub struct SplitTemplate {
pub before_list: Template,
pub list_content: Template,
pub after_list: Template,
}
impl SplitTemplate {
#[must_use]
pub fn name(&self) -> Option<&str> {
self.before_list.name.as_deref()
}
}
pub fn split_template(mut original: Template) -> SplitTemplate {
let mut elements_after = Vec::new();
let mut mapping_after = Vec::new();
let mut items_template = None;
let found = original.elements.iter().position(is_template_list_item);
if let Some(idx) = found {
elements_after = original.elements.split_off(idx + 1);
mapping_after = original.mapping.split_off(idx + 1);
if let Some(TemplateElement::HelperBlock(tpl)) = original.elements.pop() {
original.mapping.pop();
items_template = tpl.template;
}
}
let mut before_list = original.clone();
let mut list_content = items_template.unwrap_or_default();
let mut after_list = Template::new();
let original_name = original.name.unwrap_or_default();
before_list.name = Some(format!("{original_name} before each block"));
list_content.name = Some(format!("{original_name} each block"));
after_list.name = Some(format!("{original_name} after each block"));
after_list.elements = elements_after;
after_list.mapping = mapping_after;
SplitTemplate {
before_list,
list_content,
after_list,
}
}
#[async_trait(? Send)]
impl AsyncFromStrWithState for SplitTemplate {
async fn from_str_with_state(
_app_state: &AppState,
source: &str,
source_path: &Path,
) -> anyhow::Result<Self> {
log::debug!("Compiling template \"{}\"", source_path.display());
let tpl = Template::compile_with_name(source, "SQLPage component".to_string())?;
Ok(split_template(tpl))
}
}
fn is_template_list_item(element: &TemplateElement) -> bool {
use Parameter::Name;
use handlebars::template::Parameter;
matches!(element,
TemplateElement::HelperBlock(tpl)
if matches!(&tpl.name, Name(name) if name == "each_row"))
}
#[allow(clippy::module_name_repetitions)]
pub struct AllTemplates {
pub handlebars: Handlebars<'static>,
split_templates: FileCache<SplitTemplate>,
}
const STATIC_TEMPLATES: Dir = include_dir!("$CARGO_MANIFEST_DIR/sqlpage/templates");
impl AllTemplates {
pub fn init(config: &AppConfig) -> anyhow::Result<Self> {
let mut handlebars = Handlebars::new();
register_all_helpers(&mut handlebars, config);
let mut this = Self {
handlebars,
split_templates: FileCache::new(),
};
this.preregister_static_templates()?;
Ok(this)
}
pub fn preregister_static_templates(&mut self) -> anyhow::Result<()> {
for file in STATIC_TEMPLATES.files() {
let mut path = PathBuf::from(TEMPLATES_DIR);
path.push(file.path());
let name = file
.path()
.file_stem()
.unwrap()
.to_string_lossy()
.to_string();
let source = String::from_utf8_lossy(file.contents());
let tpl = Template::compile_with_name(&source, name)?;
let split_template = split_template(tpl);
self.split_templates.add_static(path, split_template);
}
Ok(())
}
fn template_path(name: &str) -> PathBuf {
let mut path: PathBuf =
PathBuf::with_capacity(TEMPLATES_DIR.len() + 1 + name.len() + ".handlebars".len());
path.push(TEMPLATES_DIR);
path.push(name);
path.set_extension("handlebars");
path
}
pub async fn get_template(
&self,
app_state: &AppState,
name: &str,
) -> anyhow::Result<Arc<SplitTemplate>> {
use anyhow::Context;
let path = Self::template_path(name);
self.split_templates
.get(app_state, &path)
.await
.with_context(|| format!("Unable to get the component '{name}'"))
}
pub fn get_static_template(&self, name: &str) -> anyhow::Result<Arc<SplitTemplate>> {
let path = Self::template_path(name);
self.split_templates.get_static(&path)
}
}
#[test]
fn test_split_template() {
let template = Template::compile(
"Hello {{name}} ! \
{{#each_row}}<li>{{this}}</li>{{/each_row}}\
end",
)
.unwrap();
let split = split_template(template);
assert_eq!(
split.before_list.elements,
Template::compile("Hello {{name}} ! ").unwrap().elements
);
assert_eq!(
split.list_content.elements,
Template::compile("<li>{{this}}</li>").unwrap().elements
);
assert_eq!(
split.after_list.elements,
Template::compile("end").unwrap().elements
);
}