use std::{fs, path::Path};
use once_cell::sync::Lazy;
use regex::Regex;
use roxy_core::roxy::Parse;
const DEFAULT_CONTEXT: Lazy<tera::Context> = Lazy::new(|| tera::Context::default());
const EXPANSION_RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r#"\{% (extends|include|import) "?(.+?)"? .*?%\}"#).expect("couldn't load regex")
});
#[derive(Debug)]
pub struct TeraParserOptions {
auto_load_layouts: bool,
}
impl Default for TeraParserOptions {
fn default() -> Self {
Self {
auto_load_layouts: true,
}
}
}
#[derive(Debug)]
pub struct TeraParser<'a> {
pub tera: &'a mut tera::Tera,
context: Option<&'a tera::Context>,
options: TeraParserOptions,
}
impl<'a> TeraParser<'a> {
pub fn new(tera: &'a mut tera::Tera, options: TeraParserOptions) -> Self {
Self {
tera,
context: None,
options,
}
}
pub fn add_context(&mut self, context: &'a tera::Context) {
self.context = Some(context);
}
fn load_template<P: AsRef<Path>>(&mut self, path: &P, src: &[u8]) -> Result<(), tera::Error> {
let path = path.as_ref().canonicalize()?;
let str = String::from_utf8_lossy(src).to_string();
for (_, [_, layout_path]) in EXPANSION_RE
.captures_iter(&str.as_str())
.map(|c| c.extract())
{
let path = path
.canonicalize()?
.parent()
.map(|p| p.join(layout_path))
.unwrap();
let next_template = fs::read(&path)?;
self.load_template(&path, &next_template)?;
self.tera.add_template_file(&path, Some(layout_path))?;
}
Ok(())
}
}
impl<'a> Parse for TeraParser<'a> {
fn parse(
&mut self,
path: &str,
src: &[u8],
dst: &mut Vec<u8>,
) -> Result<(), roxy_core::error::Error> {
let err = |e: tera::Error| {
println!("{e:?}");
std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())
};
if self.options.auto_load_layouts {
self.load_template(&path, src).map_err(err)?;
}
let template = String::from_utf8_lossy(src).to_string();
self.tera
.add_raw_template(path, template.as_str())
.map_err(err)?;
self.tera
.render_to(path, self.context.unwrap_or(&DEFAULT_CONTEXT), dst)
.map_err(err)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::fs;
use roxy_core::roxy::Parse;
use crate::{TeraParser, TeraParserOptions};
#[test]
fn capture_test() {
let mut tera = tera::Tera::default();
let mut parser = TeraParser::new(&mut tera, TeraParserOptions::default());
let data = fs::read("./tests/index.md").unwrap();
let mut dst = Vec::new();
parser.parse("./tests/index.md", &data, &mut dst).unwrap();
let expect = [
10, 32, 32, 10, 32, 32, 10, 60, 112, 62, 104, 105, 32, 102, 114, 111, 109, 32, 97, 32,
109, 97, 99, 114, 111, 33, 32, 110, 119, 110, 60, 47, 112, 62, 10, 10, 32, 32, 60, 112,
62, 102, 114, 111, 109, 32, 97, 110, 32, 105, 110, 99, 108, 117, 100, 101, 33, 33, 33,
60, 47, 112, 62, 10, 10, 10, 10, 32, 32, 10, 32, 32, 32, 32, 10, 32, 32, 10, 32, 32,
32, 32, 58, 51, 10, 10, 10, 10,
];
assert_eq!(expect, dst.as_slice());
}
}