diff --git a/Cargo.toml b/Cargo.toml index 64d5bd3..1bc1415 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +once_cell = "1.19.0" +regex = "1.10.3" roxy_core = { git = "https://fem.mint.lgbt/kitsunecafe/roxy-core.git" } tera = "1.19.1" diff --git a/src/lib.rs b/src/lib.rs index e02bf03..1145e07 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,61 @@ +use std::path::PathBuf; + +use once_cell::sync::Lazy; +use regex::Regex; use roxy_core::roxy::Parse; +const DEFAULT_CONTEXT: Lazy = Lazy::new(|| tera::Context::default()); +const EXPANSION_RE: Lazy = + Lazy::new(|| Regex::new("\\{% extends \"?(.+?)\"? %\\}").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: &'a tera::Context, + context: Option<&'a tera::Context>, + options: TeraParserOptions, } impl<'a> TeraParser<'a> { - pub fn new(tera: &'a mut tera::Tera, context: &'a tera::Context) -> Self { - Self { tera, context } + 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(&mut self, path: &str, src: &[u8]) -> Result<(), tera::Error> { + let str = String::from_utf8_lossy(src).to_string(); + if let Some(captures) = EXPANSION_RE.captures(&str.as_str()) { + if let Some(layout_path) = captures.get(1) { + let layout_path = layout_path.as_str(); + let path = PathBuf::from(path).parent().map(|p| p.join(layout_path)).unwrap(); + + self.tera.add_template_file( + &path.canonicalize().unwrap(), + Some(layout_path) + )?; + } + } + + Ok(()) } } @@ -20,19 +67,24 @@ impl<'a> Parse for TeraParser<'a> { 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, dst).map_err(err) + self.tera + .render_to(path, self.context.unwrap_or(&DEFAULT_CONTEXT), dst) + .map_err(err) } } #[cfg(test)] mod tests { #[test] - fn it_works() { - } + fn it_works() {} }