From 9f3b590206e3dfe33b7129b1c8ff010f60318cf2 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sun, 27 Aug 2017 22:10:42 +0200 Subject: Move most of the code into new askama_shared crate This makes it possible to share code between askama and askama_derive. --- Cargo.toml | 2 +- askama/Cargo.toml | 6 +- askama/src/filters/json.rs | 30 -- askama/src/filters/mod.rs | 130 ------ askama/src/lib.rs | 34 +- askama_derive/Cargo.toml | 7 +- askama_derive/src/generator.rs | 715 -------------------------------- askama_derive/src/lib.rs | 16 +- askama_derive/src/parser.rs | 471 --------------------- askama_derive/src/path.rs | 86 ---- askama_derive/templates/a.html | 1 - askama_derive/templates/sub/b.html | 1 - askama_derive/templates/sub/c.html | 1 - askama_derive/templates/sub/sub1/d.html | 1 - askama_shared/Cargo.toml | 19 + askama_shared/src/filters/json.rs | 30 ++ askama_shared/src/filters/mod.rs | 130 ++++++ askama_shared/src/generator.rs | 715 ++++++++++++++++++++++++++++++++ askama_shared/src/lib.rs | 29 ++ askama_shared/src/parser.rs | 471 +++++++++++++++++++++ askama_shared/src/path.rs | 85 ++++ askama_shared/templates/a.html | 1 + askama_shared/templates/sub/b.html | 1 + askama_shared/templates/sub/c.html | 1 + askama_shared/templates/sub/sub1/d.html | 1 + 25 files changed, 1500 insertions(+), 1484 deletions(-) delete mode 100644 askama/src/filters/json.rs delete mode 100644 askama/src/filters/mod.rs delete mode 100644 askama_derive/src/generator.rs delete mode 100644 askama_derive/src/parser.rs delete mode 100644 askama_derive/src/path.rs delete mode 100644 askama_derive/templates/a.html delete mode 100644 askama_derive/templates/sub/b.html delete mode 100644 askama_derive/templates/sub/c.html delete mode 100644 askama_derive/templates/sub/sub1/d.html create mode 100644 askama_shared/Cargo.toml create mode 100644 askama_shared/src/filters/json.rs create mode 100644 askama_shared/src/filters/mod.rs create mode 100644 askama_shared/src/generator.rs create mode 100644 askama_shared/src/lib.rs create mode 100644 askama_shared/src/parser.rs create mode 100644 askama_shared/src/path.rs create mode 100644 askama_shared/templates/a.html create mode 100644 askama_shared/templates/sub/b.html create mode 100644 askama_shared/templates/sub/c.html create mode 100644 askama_shared/templates/sub/sub1/d.html diff --git a/Cargo.toml b/Cargo.toml index a63c3e1..cce1ee3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,2 @@ [workspace] -members = ["askama", "askama_derive", "testing"] +members = ["askama", "askama_derive", "askama_shared", "testing"] diff --git a/askama/Cargo.toml b/askama/Cargo.toml index 7e90e85..cf3a50e 100644 --- a/askama/Cargo.toml +++ b/askama/Cargo.toml @@ -17,17 +17,15 @@ travis-ci = { repository = "djc/askama" } [features] default = [] -serde-json = ["serde", "serde_json"] +serde-json = ["askama_shared/serde-json"] with-iron = ["iron", "askama_derive/iron"] with-rocket = ["rocket", "askama_derive/rocket"] [dependencies] askama_derive = { path = "../askama_derive", version = "0.3.4" } -error-chain = "0.10" +askama_shared = { version = "0.3.4", path = "../askama_shared" } iron = { version = "0.5", optional = true } rocket = { version = "0.3", optional = true } -serde = { version = "1.0", optional = true } -serde_json = { version = "1.0", optional = true } [package.metadata.docs.rs] features = [ "serde-json" ] diff --git a/askama/src/filters/json.rs b/askama/src/filters/json.rs deleted file mode 100644 index 4443fb4..0000000 --- a/askama/src/filters/json.rs +++ /dev/null @@ -1,30 +0,0 @@ -use serde::Serialize; -use serde_json; -use errors::{Error, Result}; - -/// Serialize to JSON (requires `serde-json` feature) -/// -/// ## Errors -/// -/// This will panic if `S`'s implementation of `Serialize` decides to fail, -/// or if `T` contains a map with non-string keys. -pub fn json(s: &S) -> Result { - serde_json::to_string_pretty(s).map_err(Error::from) -} - - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_json() { - assert_eq!(json(&true).unwrap(), "true"); - assert_eq!(json(&"foo").unwrap(), r#""foo""#); - assert_eq!(json(&vec!["foo", "bar"]).unwrap(), -r#"[ - "foo", - "bar" -]"#); - } -} diff --git a/askama/src/filters/mod.rs b/askama/src/filters/mod.rs deleted file mode 100644 index 73b311d..0000000 --- a/askama/src/filters/mod.rs +++ /dev/null @@ -1,130 +0,0 @@ -//! Module for built-in filter functions -//! -//! Contains all the built-in filter functions for use in templates. -//! Currently, there is no way to define filters outside this module. -// -// WHEN ADDING FILTERS, DON'T FORGET TO UPDATE `BUILT_IN_FILTERS` in askama_derive::generator. - -#[cfg(feature = "serde-json")] -mod json; - -#[cfg(feature = "serde-json")] -pub use self::json::json; - -use std::fmt; -use super::Result; - - -fn escapable(b: &u8) -> bool { - *b == b'<' || *b == b'>' || *b == b'&' -} - -/// Escapes `&`, `<` and `>` in strings -pub fn escape(s: &fmt::Display) -> Result { - let s = format!("{}", s); - let mut found = Vec::new(); - for (i, b) in s.as_bytes().iter().enumerate() { - if escapable(b) { - found.push(i); - } - } - if found.is_empty() { - return Ok(s); - } - - let bytes = s.as_bytes(); - let max_len = bytes.len() + found.len() * 3; - let mut res = Vec::::with_capacity(max_len); - let mut start = 0; - for idx in &found { - if start < *idx { - res.extend(&bytes[start..*idx]); - } - start = *idx + 1; - match bytes[*idx] { - b'<' => { res.extend(b"<"); }, - b'>' => { res.extend(b">"); }, - b'&' => { res.extend(b"&"); }, - _ => panic!("incorrect indexing"), - } - } - if start < bytes.len() - 1 { - res.extend(&bytes[start..]); - } - - Ok(String::from_utf8(res).unwrap()) -} - -/// Alias for the `escape()` filter -pub fn e(s: &fmt::Display) -> Result { - escape(s) -} - -/// Formats arguments according to the specified format -/// -/// The first argument to this filter must be a string literal (as in normal -/// Rust). All arguments are passed through to the `format!()` -/// [macro](https://doc.rust-lang.org/stable/std/macro.format.html) by -/// the Askama code generator. -pub fn format() { } - -/// Converts to lowercase. -pub fn lower(s: &fmt::Display) -> Result { - let s = format!("{}", s); - Ok(s.to_lowercase()) -} - -/// Alias for the `lower()` filter. -pub fn lowercase(s: &fmt::Display) -> Result { - lower(s) -} - -/// Converts to uppercase. -pub fn upper(s: &fmt::Display) -> Result { - let s = format!("{}", s); - Ok(s.to_uppercase()) -} - -/// Alias for the `upper()` filter. -pub fn uppercase(s: &fmt::Display) -> Result { - upper(s) -} - -/// Strip leading and trailing whitespace. -pub fn trim(s: &fmt::Display) -> Result { - let s = format!("{}", s); - Ok(s.trim().to_owned()) -} - -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn test_escape() { - assert_eq!(escape(&"").unwrap(), ""); - assert_eq!(escape(&"<&>").unwrap(), "<&>"); - assert_eq!(escape(&"bla&").unwrap(), "bla&"); - assert_eq!(escape(&" PathBuf { - let mut path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - path.push("templates"); - path -} - fn visit_dirs(dir: &Path, cb: &Fn(&DirEntry)) -> io::Result<()> { if dir.is_dir() { for entry in try!(fs::read_dir(dir)) { @@ -290,16 +277,7 @@ fn visit_dirs(dir: &Path, cb: &Fn(&DirEntry)) -> io::Result<()> { /// that have templates, to make sure the crate gets rebuilt when template /// source code changes. pub fn rerun_if_templates_changed() { - visit_dirs(&template_dir(), &|e: &DirEntry| { + visit_dirs(&path::template_dir(), &|e: &DirEntry| { println!("cargo:rerun-if-changed={}", e.path().to_str().unwrap()); }).unwrap(); } - -mod errors { - error_chain! { - foreign_links { - Fmt(::std::fmt::Error); - Json(::serde_json::Error) #[cfg(feature = "serde-json")]; - } - } -} diff --git a/askama_derive/Cargo.toml b/askama_derive/Cargo.toml index 61263be..66610ea 100644 --- a/askama_derive/Cargo.toml +++ b/askama_derive/Cargo.toml @@ -13,10 +13,9 @@ proc-macro = true [features] default = [] -iron = [] -rocket = [] +iron = ["askama_shared/iron"] +rocket = ["askama_shared/rocket"] [dependencies] -nom = "3" -quote = "0.3" +askama_shared = { version = "0.3.4", path = "../askama_shared" } syn = "0.11" diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs deleted file mode 100644 index 82f3994..0000000 --- a/askama_derive/src/generator.rs +++ /dev/null @@ -1,715 +0,0 @@ -use parser::{self, Cond, Expr, Node, Target, WS}; -use path; - -use quote::{Tokens, ToTokens}; - -use std::{cmp, hash, str}; -use std::path::Path; -use std::collections::{HashMap, HashSet}; - -use syn; - -pub fn generate(ast: &syn::DeriveInput, path: &Path, mut nodes: Vec) -> String { - let mut base: Option = None; - let mut blocks = Vec::new(); - let mut content = Vec::new(); - let mut macros = HashMap::new(); - for n in nodes.drain(..) { - match n { - Node::Extends(path) => { - match base { - Some(_) => panic!("multiple extend blocks found"), - None => { base = Some(path); }, - } - }, - Node::BlockDef(ws1, name, _, ws2) => { - blocks.push(n); - content.push(Node::Block(ws1, name, ws2)); - }, - Node::Macro(ws1, name, params, contents, ws2) => { - macros.insert(name, (ws1, name, params, contents, ws2)); - }, - _ => { content.push(n); }, - } - } - - let mut gen = Generator::default(¯os); - if !blocks.is_empty() { - let trait_name = trait_name_for_path(&base, path); - if base.is_none() { - gen.define_trait(&trait_name, &blocks); - } else { - let parent_type = get_parent_type(ast) - .expect("expected field '_parent' in extending template struct"); - gen.deref_to_parent(ast, &parent_type); - } - - let trait_nodes = if base.is_none() { Some(&content[..]) } else { None }; - gen.impl_trait(ast, &trait_name, &blocks, trait_nodes); - gen.impl_template_for_trait(ast, base.is_some()); - } else { - gen.impl_template(ast, &content); - } - gen.impl_display(ast); - if cfg!(feature = "iron") { - gen.impl_modifier_response(ast); - } - if cfg!(feature = "rocket") { - gen.impl_responder(ast, path); - } - gen.result() -} - -fn trait_name_for_path(base: &Option, path: &Path) -> String { - let rooted_path = match *base { - Some(Expr::StrLit(user_path)) => { - path::find_template_from_path(user_path, Some(path)) - }, - _ => path.to_path_buf(), - }; - - let mut res = String::new(); - res.push_str("TraitFrom"); - for c in rooted_path.to_string_lossy().chars() { - if c.is_alphanumeric() { - res.push(c); - } else { - res.push_str(&format!("{:x}", c as u32)); - } - } - res -} - -fn get_parent_type(ast: &syn::DeriveInput) -> Option<&syn::Ty> { - match ast.body { - syn::Body::Struct(ref data) => { - data.fields().iter().filter_map(|f| { - f.ident.as_ref().and_then(|name| { - if name.as_ref() == "_parent" { - Some(&f.ty) - } else { - None - } - }) - }) - }, - _ => panic!("derive(Template) only works for struct items"), - }.next() -} - -struct Generator<'a> { - buf: String, - indent: u8, - start: bool, - locals: SetChain<'a, &'a str>, - next_ws: Option<&'a str>, - skip_ws: bool, - macros: &'a MacroMap<'a>, -} - -impl<'a> Generator<'a> { - - fn new<'n>(macros: &'n MacroMap, locals: SetChain<'n, &'n str>, indent: u8) -> Generator<'n> { - Generator { - buf: String::new(), - indent: indent, - start: true, - locals: locals, - next_ws: None, - skip_ws: false, - macros: macros, - } - } - - fn default<'n>(macros: &'n MacroMap) -> Generator<'n> { - Self::new(macros, SetChain::new(), 0) - } - - fn child<'n>(&'n mut self) -> Generator<'n> { - let locals = SetChain::with_parent(&self.locals); - Self::new(self.macros, locals, self.indent) - } - - /* Helper methods for writing to internal buffer */ - - fn indent(&mut self) { - self.indent += 1; - } - - fn dedent(&mut self) { - self.indent -= 1; - } - - fn write(&mut self, s: &str) { - if self.start { - for _ in 0..(self.indent * 4) { - self.buf.push(' '); - } - self.start = false; - } - self.buf.push_str(s); - } - - fn writeln(&mut self, s: &str) { - if s.is_empty() { - return; - } - if s == "}" { - self.dedent(); - } - self.write(s); - if s.ends_with('{') { - self.indent(); - } - self.buf.push('\n'); - self.start = true; - } - - /* Helper methods for dealing with whitespace nodes */ - - fn flush_ws(&mut self, ws: &WS) { - if self.next_ws.is_some() && !ws.0 { - let val = self.next_ws.unwrap(); - if !val.is_empty() { - self.writeln(&format!("writer.write_str({:#?})?;", - val)); - } - } - self.next_ws = None; - } - - fn prepare_ws(&mut self, ws: &WS) { - self.skip_ws = ws.1; - } - - fn handle_ws(&mut self, ws: &WS) { - self.flush_ws(ws); - self.prepare_ws(ws); - } - - /* Visitor methods for expression types */ - - fn visit_num_lit(&mut self, s: &str) { - self.write(s); - } - - fn visit_str_lit(&mut self, s: &str) { - self.write(&format!("\"{}\"", s)); - } - - fn visit_var(&mut self, s: &str) { - if self.locals.contains(s) { - self.write(s); - } else { - self.write(&format!("self.{}", s)); - } - } - - fn visit_attr(&mut self, obj: &Expr, attr: &str) { - if let Expr::Var(name) = *obj { - if name == "loop" { - self.write("_loop_index"); - if attr == "index" { - self.write(" + 1"); - return; - } else if attr == "index0" { - return; - } else { - panic!("unknown loop variable"); - } - } - } - self.visit_expr(obj); - self.write(&format!(".{}", attr)); - } - - fn visit_filter(&mut self, name: &str, args: &[Expr]) { - if name == "format" { - self.write("format!("); - } else if BUILT_IN_FILTERS.contains(&name) { - self.write(&format!("::askama::filters::{}(&", name)); - } else { - self.write(&format!("filters::{}(&", name)); - } - - for (i, arg) in args.iter().enumerate() { - if i > 0 { - self.write(", &"); - } - self.visit_expr(arg); - } - self.write(")"); - if name != "format" { - self.write("?"); - } - } - - fn visit_binop(&mut self, op: &str, left: &Expr, right: &Expr) { - self.visit_expr(left); - self.write(&format!(" {} ", op)); - self.visit_expr(right); - } - - fn visit_group(&mut self, inner: &Expr) { - self.write("("); - self.visit_expr(inner); - self.write(")"); - } - - fn visit_method_call(&mut self, obj: &Expr, method: &str, args: &[Expr]) { - self.visit_expr(obj); - self.write(&format!(".{}(", method)); - for (i, arg) in args.iter().enumerate() { - if i > 0 { - self.write(", "); - } - self.visit_expr(arg); - } - self.write(")"); - } - - fn visit_expr(&mut self, expr: &Expr) { - match *expr { - Expr::NumLit(s) => self.visit_num_lit(s), - Expr::StrLit(s) => self.visit_str_lit(s), - Expr::Var(s) => self.visit_var(s), - Expr::Attr(ref obj, name) => self.visit_attr(obj, name), - Expr::Filter(name, ref args) => self.visit_filter(name, args), - Expr::BinOp(op, ref left, ref right) => - self.visit_binop(op, left, right), - Expr::Group(ref inner) => self.visit_group(inner), - Expr::MethodCall(ref obj, method, ref args) => - self.visit_method_call(obj, method, args), - } - } - - fn visit_target_single<'t>(&mut self, name: &'t str) -> Vec<&'t str> { - vec![name] - } - - fn visit_target<'t>(&mut self, target: &'t Target) -> Vec<&'t str> { - match *target { - Target::Name(s) => { self.visit_target_single(s) }, - } - } - - /* Helper methods for handling node types */ - - fn write_lit(&mut self, lws: &'a str, val: &str, rws: &'a str) { - assert!(self.next_ws.is_none()); - if !lws.is_empty() { - if self.skip_ws { - self.skip_ws = false; - } else if val.is_empty() { - assert!(rws.is_empty()); - self.next_ws = Some(lws); - } else { - self.writeln(&format!("writer.write_str({:#?})?;", - lws)); - } - } - if !val.is_empty() { - self.writeln(&format!("writer.write_str({:#?})?;", val)); - } - if !rws.is_empty() { - self.next_ws = Some(rws); - } - } - - fn write_expr(&mut self, ws: &WS, s: &Expr) { - self.handle_ws(ws); - self.write("writer.write_fmt(format_args!(\"{}\", "); - self.visit_expr(s); - self.writeln("))?;"); - } - - fn write_call(&mut self, ws: &WS, name: &str, args: &[Expr]) { - self.handle_ws(ws); - let def = self.macros.get(name).expect(&format!("macro '{}' not found", name)); - self.locals.push(); - self.writeln("{"); - self.prepare_ws(&def.0); - for (i, arg) in def.2.iter().enumerate() { - self.write(&format!("let {} = &", arg)); - self.locals.insert(arg); - self.visit_expr(&args.get(i) - .expect(&format!("macro '{}' takes more than {} arguments", name, i))); - self.writeln(";"); - } - self.handle(&def.3); - self.flush_ws(&def.4); - self.writeln("}"); - self.locals.pop(); - } - - fn write_let_decl(&mut self, ws: &WS, var: &'a Target) { - self.handle_ws(ws); - self.write("let "); - match *var { - Target::Name(name) => { - self.locals.insert(name); - self.write(name); - }, - } - self.writeln(";"); - } - - fn write_let(&mut self, ws: &WS, var: &'a Target, val: &Expr) { - self.handle_ws(ws); - match *var { - Target::Name(name) => { - if !self.locals.contains(name) { - self.write("let "); - self.locals.insert(name); - } - self.write(name); - }, - } - self.write(" = "); - self.visit_expr(val); - self.writeln(";"); - } - - fn write_cond(&mut self, conds: &'a [Cond], ws: &WS) { - for (i, &(ref cws, ref cond, ref nodes)) in conds.iter().enumerate() { - self.handle_ws(cws); - match *cond { - Some(ref expr) => { - if i == 0 { - self.write("if "); - } else { - self.dedent(); - self.write("} else if "); - } - self.visit_expr(expr); - }, - None => { - self.dedent(); - self.write("} else"); - }, - } - self.writeln(" {"); - self.locals.push(); - self.handle(nodes); - self.locals.pop(); - } - self.handle_ws(ws); - self.writeln("}"); - } - - fn write_loop(&mut self, ws1: &WS, var: &'a Target, iter: &Expr, - body: &'a [Node], ws2: &WS) { - self.handle_ws(ws1); - self.locals.push(); - self.write("for (_loop_index, "); - let targets = self.visit_target(var); - for name in &targets { - self.locals.insert(name); - self.write(name); - } - self.write(") in (&"); - self.visit_expr(iter); - self.writeln(").into_iter().enumerate() {"); - - self.handle(body); - self.handle_ws(ws2); - self.writeln("}"); - self.locals.pop(); - } - - fn write_block(&mut self, ws1: &WS, name: &str, ws2: &WS) { - self.flush_ws(ws1); - self.writeln(&format!("timpl.render_block_{}_into(writer)?;", name)); - self.prepare_ws(ws2); - } - - fn write_block_def(&mut self, ws1: &WS, name: &str, nodes: &'a [Node], - ws2: &WS) { - self.writeln("#[allow(unused_variables)]"); - self.writeln(&format!( - "fn render_block_{}_into(&self, writer: &mut ::std::fmt::Write) \ - -> ::askama::Result<()> {{", - name)); - self.prepare_ws(ws1); - - self.locals.push(); - self.handle(nodes); - self.locals.pop(); - - self.flush_ws(ws2); - self.writeln("Ok(())"); - self.writeln("}"); - } - - fn handle_include(&mut self, ws: &WS, path: &str) { - self.prepare_ws(ws); - let path = path::find_template_from_path(&path, None); - let src = path::get_template_source(&path); - let nodes = parser::parse(&src); - let nested = { - let mut gen = self.child(); - gen.handle(&nodes); - gen.result() - }; - self.buf.push_str(&nested); - self.flush_ws(ws); - } - - fn handle(&mut self, nodes: &'a [Node]) { - for n in nodes { - match *n { - Node::Lit(lws, val, rws) => { self.write_lit(lws, val, rws); } - Node::Comment() => {}, - Node::Expr(ref ws, ref val) => { self.write_expr(ws, val); }, - Node::LetDecl(ref ws, ref var) => { self.write_let_decl(ws, var); }, - Node::Let(ref ws, ref var, ref val) => { self.write_let(ws, var, val); }, - Node::Cond(ref conds, ref ws) => { - self.write_cond(conds, ws); - }, - Node::Loop(ref ws1, ref var, ref iter, ref body, ref ws2) => { - self.write_loop(ws1, var, iter, body, ws2); - }, - Node::Block(ref ws1, name, ref ws2) => { - self.write_block(ws1, name, ws2); - }, - Node::BlockDef(ref ws1, name, ref block_nodes, ref ws2) => { - self.write_block_def(ws1, name, block_nodes, ws2); - } - Node::Include(ref ws, ref path) => { - self.handle_include(ws, path); - }, - Node::Call(ref ws, name, ref args) => self.write_call(ws, name, args), - Node::Macro(_, _, _, _, _) | - Node::Extends(_) => { - panic!("no extends or macros allowed in content"); - }, - } - } - } - - // Writes header for the `impl` for `TraitFromPathName` or `Template` - // for the given context struct. - fn write_header(&mut self, ast: &syn::DeriveInput, target: &str, extra_anno: &[&str]) { - let mut full_anno = Tokens::new(); - let mut orig_anno = Tokens::new(); - let need_anno = ast.generics.lifetimes.len() > 0 || - ast.generics.ty_params.len() > 0 || - extra_anno.len() > 0; - if need_anno { - full_anno.append("<"); - orig_anno.append("<"); - } - - let (mut full_sep, mut orig_sep) = (false, false); - for lt in &ast.generics.lifetimes { - if full_sep { - full_anno.append(","); - } - if orig_sep { - orig_anno.append(","); - } - lt.to_tokens(&mut full_anno); - lt.to_tokens(&mut orig_anno); - full_sep = true; - orig_sep = true; - } - - for anno in extra_anno { - if full_sep { - full_anno.append(","); - } - full_anno.append(anno); - full_sep = true; - } - - for param in &ast.generics.ty_params { - if full_sep { - full_anno.append(","); - } - if orig_sep { - orig_anno.append(","); - } - let mut impl_param = param.clone(); - impl_param.default = None; - impl_param.to_tokens(&mut full_anno); - param.ident.to_tokens(&mut orig_anno); - full_sep = true; - orig_sep = true; - } - - if need_anno { - full_anno.append(">"); - orig_anno.append(">"); - } - - let mut where_clause = Tokens::new(); - ast.generics.where_clause.to_tokens(&mut where_clause); - self.writeln(&format!("impl{} {} for {}{}{} {{", - full_anno.as_str(), target, ast.ident.as_ref(), - orig_anno.as_str(), where_clause.as_str())); - } - - // Implement `Template` for the given context struct. - fn impl_template(&mut self, ast: &syn::DeriveInput, nodes: &'a [Node]) { - self.write_header(ast, "::askama::Template", &vec![]); - self.writeln("fn render_into(&self, writer: &mut ::std::fmt::Write) -> \ - ::askama::Result<()> {"); - self.handle(nodes); - self.flush_ws(&WS(false, false)); - self.writeln("Ok(())"); - self.writeln("}"); - self.writeln("}"); - } - - // Implement `Display` for the given context struct. - fn impl_display(&mut self, ast: &syn::DeriveInput) { - self.write_header(ast, "::std::fmt::Display", &vec![]); - self.writeln("fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {"); - self.writeln("self.render_into(f).map_err(|_| ::std::fmt::Error {})"); - self.writeln("}"); - self.writeln("}"); - } - - // Implement `Deref` for an inheriting context struct. - fn deref_to_parent(&mut self, ast: &syn::DeriveInput, parent_type: &syn::Ty) { - self.write_header(ast, "::std::ops::Deref", &vec![]); - let mut tokens = Tokens::new(); - parent_type.to_tokens(&mut tokens); - self.writeln(&format!("type Target = {};", tokens.as_str())); - self.writeln("fn deref(&self) -> &Self::Target {"); - self.writeln("&self._parent"); - self.writeln("}"); - self.writeln("}"); - } - - // Implement `TraitFromPathName` for the given context struct. - fn impl_trait(&mut self, ast: &syn::DeriveInput, trait_name: &str, - blocks: &'a [Node], nodes: Option<&'a [Node]>) { - self.write_header(ast, &trait_name, &vec![]); - self.handle(blocks); - - self.writeln("#[allow(unused_variables)]"); - self.writeln(&format!( - "fn render_trait_into(&self, timpl: &{}, writer: &mut ::std::fmt::Write) \ - -> ::askama::Result<()> {{", - trait_name)); - - if let Some(nodes) = nodes { - self.handle(nodes); - self.flush_ws(&WS(false, false)); - } else { - self.writeln("self._parent.render_trait_into(self, writer)?;"); - } - - self.writeln("Ok(())"); - self.writeln("}"); - self.flush_ws(&WS(false, false)); - self.writeln("}"); - } - - // Implement `Template` for templates that implement a template trait. - fn impl_template_for_trait(&mut self, ast: &syn::DeriveInput, derived: bool) { - self.write_header(ast, "::askama::Template", &vec![]); - self.writeln("fn render_into(&self, writer: &mut ::std::fmt::Write) \ - -> ::askama::Result<()> {"); - if derived { - self.writeln("self._parent.render_trait_into(self, writer)?;"); - } else { - self.writeln("self.render_trait_into(self, writer)?;"); - } - self.writeln("Ok(())"); - self.writeln("}"); - self.writeln("}"); - } - - // Defines the `TraitFromPathName` trait. - fn define_trait(&mut self, trait_name: &str, blocks: &'a [Node]) { - self.writeln(&format!("trait {} {{", &trait_name)); - self.handle(blocks); - self.writeln(&format!( - "fn render_trait_into(&self, timpl: &{}, writer: &mut ::std::fmt::Write) \ - -> ::askama::Result<()>;", - trait_name)); - self.writeln("}"); - } - - // Implement iron's Modifier if enabled - fn impl_modifier_response(&mut self, ast: &syn::DeriveInput) { - self.write_header(ast, "::askama::iron::Modifier<::askama::iron::Response>", &vec![]); - self.writeln("fn modify(self, res: &mut ::askama::iron::Response) {"); - self.writeln("res.body = Some(Box::new(self.render().unwrap().into_bytes()));"); - self.writeln("}"); - self.writeln("}"); - } - - // Implement Rocket's `Responder`. - fn impl_responder(&mut self, ast: &syn::DeriveInput, path: &Path) { - self.write_header(ast, "::askama::rocket::Responder<'r>", &vec!["'r"]); - self.writeln("fn respond_to(self, _: &::askama::rocket::Request) \ - -> ::std::result::Result<\ - ::askama::rocket::Response<'r>, ::askama::rocket::Status> {"); - let ext = match path.extension() { - Some(s) => s.to_str().unwrap(), - None => "txt", - }; - self.writeln("::askama::rocket::Response::build()"); - self.indent(); - self.writeln(&format!(".header(::askama::rocket::ContentType::from_extension({:?})\ - .unwrap())", ext)); - self.writeln(".sized_body(::std::io::Cursor::new(self.render().unwrap()))"); - self.writeln(".ok()"); - self.dedent(); - self.writeln("}"); - self.writeln("}"); - } - - fn result(self) -> String { - self.buf - } - -} - -struct SetChain<'a, T: 'a> where T: cmp::Eq + hash::Hash { - parent: Option<&'a SetChain<'a, T>>, - scopes: Vec>, -} - -impl<'a, T: 'a> SetChain<'a, T> where T: cmp::Eq + hash::Hash { - fn new() -> SetChain<'a, T> { - SetChain { parent: None, scopes: vec![HashSet::new()] } - } - fn with_parent<'p>(parent: &'p SetChain) -> SetChain<'p, T> { - SetChain { parent: Some(parent), scopes: vec![HashSet::new()] } - } - fn contains(&self, val: T) -> bool { - self.scopes.iter().rev().any(|set| set.contains(&val)) || - match self.parent { - Some(set) => set.contains(val), - None => false, - } - } - fn insert(&mut self, val: T) { - self.scopes.last_mut().unwrap().insert(val); - } - fn push(&mut self) { - self.scopes.push(HashSet::new()); - } - fn pop(&mut self) { - self.scopes.pop().unwrap(); - assert!(self.scopes.len() > 0); - } -} - -type MacroMap<'a> = HashMap<&'a str, (WS, &'a str, Vec<&'a str>, Vec>, WS)>; - -const BUILT_IN_FILTERS: [&str; 9] = [ - "e", - "escape", - "format", - "lower", - "lowercase", - "trim", - "upper", - "uppercase", - "json", // Optional feature; reserve the name anyway -]; diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index 0564ccc..0333c06 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -1,7 +1,5 @@ -#[macro_use] -extern crate nom; +extern crate askama_shared as shared; extern crate proc_macro; -extern crate quote; extern crate syn; use proc_macro::TokenStream; @@ -9,10 +7,6 @@ use proc_macro::TokenStream; use std::borrow::Cow; use std::path::PathBuf; -mod generator; -mod parser; -mod path; - #[proc_macro_derive(Template, attributes(template))] pub fn derive_template(input: TokenStream) -> TokenStream { let ast = syn::parse_derive_input(&input.to_string()).unwrap(); @@ -35,16 +29,16 @@ fn build_template(ast: &syn::DeriveInput) -> String { let (path, src) = match meta.source { Source::Source(s) => (PathBuf::new(), Cow::Borrowed(s)), Source::Path(s) => { - let path = path::find_template_from_path(&s, None); - let src = path::get_template_source(&path); + let path = shared::path::find_template_from_path(&s, None); + let src = shared::path::get_template_source(&path); (path, Cow::Owned(src)) }, }; - let nodes = parser::parse(src.as_ref()); + let nodes = shared::parse(src.as_ref()); if meta.print == Print::Ast || meta.print == Print::All { println!("{:?}", nodes); } - let code = generator::generate(ast, &path, nodes); + let code = shared::generate(ast, &path, nodes); if meta.print == Print::Code || meta.print == Print::All { println!("{}", code); } diff --git a/askama_derive/src/parser.rs b/askama_derive/src/parser.rs deleted file mode 100644 index 8732f0e..0000000 --- a/askama_derive/src/parser.rs +++ /dev/null @@ -1,471 +0,0 @@ -use nom::{self, IResult}; -use std::str; - -#[derive(Debug)] -pub enum Expr<'a> { - NumLit(&'a str), - StrLit(&'a str), - Var(&'a str), - Attr(Box>, &'a str), - Filter(&'a str, Vec>), - BinOp(&'a str, Box>, Box>), - Group(Box>), - MethodCall(Box>, &'a str, Vec>), -} - -#[derive(Debug)] -pub enum Target<'a> { - Name(&'a str), -} - -#[derive(Clone, Copy, Debug)] -pub struct WS(pub bool, pub bool); - -#[derive(Debug)] -pub enum Node<'a> { - Lit(&'a str, &'a str, &'a str), - Comment(), - Expr(WS, Expr<'a>), - Call(WS, &'a str, Vec>), - LetDecl(WS, Target<'a>), - Let(WS, Target<'a>, Expr<'a>), - Cond(Vec<(WS, Option>, Vec>)>, WS), - Loop(WS, Target<'a>, Expr<'a>, Vec>, WS), - Extends(Expr<'a>), - BlockDef(WS, &'a str, Vec>, WS), - Block(WS, &'a str, WS), - Include(WS, &'a str), - Macro(WS, &'a str, Vec<&'a str>, Vec>, WS), -} - -pub type Cond<'a> = (WS, Option>, Vec>); - -fn split_ws_parts(s: &[u8]) -> Node { - if s.is_empty() { - let rs = str::from_utf8(s).unwrap(); - return Node::Lit(rs, rs, rs); - } - let is_ws = |c: &u8| { - *c != b' ' && *c != b'\t' && *c != b'\r' && *c != b'\n' - }; - let start = s.iter().position(&is_ws); - let res = if start.is_none() { - (s, &s[0..0], &s[0..0]) - } else { - let start = start.unwrap(); - let end = s.iter().rposition(&is_ws); - if end.is_none() { - (&s[..start], &s[start..], &s[0..0]) - } else { - let end = end.unwrap(); - (&s[..start], &s[start..end + 1], &s[end + 1..]) - } - }; - Node::Lit(str::from_utf8(res.0).unwrap(), - str::from_utf8(res.1).unwrap(), - str::from_utf8(res.2).unwrap()) -} - -enum ContentState { - Any, - Brace(usize), - End(usize), -} - -fn take_content(i: &[u8]) -> IResult<&[u8], Node> { - use parser::ContentState::*; - let mut state = Any; - for (idx, c) in i.iter().enumerate() { - state = match (state, *c) { - (Any, b'{') => Brace(idx), - (Any, _) => Any, - (Brace(start), b'{') | - (Brace(start), b'%') | - (Brace(start), b'#') => End(start), - (Brace(_), _) => Any, - (End(_), _) => panic!("cannot happen"), - }; - if let End(_) = state { - break; - } - } - match state { - Any | - Brace(_) => IResult::Done(&i[..0], split_ws_parts(i)), - End(0) => IResult::Error(nom::ErrorKind::Custom(0)), - End(start) => IResult::Done(&i[start..], split_ws_parts(&i[..start])), - } -} - -fn identifier(input: &[u8]) -> IResult<&[u8], &str> { - if !nom::is_alphabetic(input[0]) && input[0] != b'_' { - return IResult::Error(nom::ErrorKind::Custom(0)); - } - for (i, ch) in input.iter().enumerate() { - if i == 0 || nom::is_alphanumeric(*ch) || *ch == b'_' { - continue; - } - return IResult::Done(&input[i..], - str::from_utf8(&input[..i]).unwrap()); - } - IResult::Done(&input[1..], str::from_utf8(&input[..1]).unwrap()) -} - -named!(expr_num_lit, map!(nom::digit, - |s| Expr::NumLit(str::from_utf8(s).unwrap()) -)); - -named!(expr_str_lit, map!( - delimited!(char!('"'), is_not!("\""), char!('"')), - |s| Expr::StrLit(str::from_utf8(s).unwrap()) -)); - -named!(expr_var, map!(identifier, - |s| Expr::Var(s)) -); - -named!(target_single, map!(identifier, - |s| Target::Name(s) -)); - -named!(arguments>, do_parse!( - tag_s!("(") >> - args: opt!(do_parse!( - arg0: ws!(expr_any) >> - args: many0!(do_parse!( - tag_s!(",") >> - argn: ws!(expr_any) >> - (argn) - )) >> - ({ - let mut res = vec![arg0]; - res.extend(args); - res - }) - )) >> - tag_s!(")") >> - (args.unwrap_or(Vec::new())) -)); - -named!(parameters>, do_parse!( - tag_s!("(") >> - vals: opt!(do_parse!( - arg0: ws!(identifier) >> - args: many0!(do_parse!( - tag_s!(",") >> - argn: ws!(identifier) >> - (argn) - )) >> - ({ - let mut res = vec![arg0]; - res.extend(args); - res - }) - )) >> - tag_s!(")") >> - (vals.unwrap_or(Vec::new())) -)); - -named!(expr_group, map!( - delimited!(char!('('), expr_any, char!(')')), - |s| Expr::Group(Box::new(s)) -)); - -named!(expr_single, alt!( - expr_num_lit | - expr_str_lit | - expr_var | - expr_group -)); - -named!(attr<(&str, Option>)>, do_parse!( - tag_s!(".") >> - attr: identifier >> - args: opt!(arguments) >> - (attr, args) -)); - -named!(expr_attr, do_parse!( - obj: expr_single >> - attrs: many0!(attr) >> - ({ - let mut res = obj; - for (aname, args) in attrs { - res = if args.is_some() { - Expr::MethodCall(Box::new(res), aname, args.unwrap()) - } else { - Expr::Attr(Box::new(res), aname) - }; - } - res - }) -)); - -named!(filter<(&str, Option>)>, do_parse!( - tag_s!("|") >> - fname: identifier >> - args: opt!(arguments) >> - (fname, args) -)); - -named!(expr_filtered, do_parse!( - obj: expr_attr >> - filters: many0!(filter) >> - ({ - let mut res = obj; - for (fname, args) in filters { - res = Expr::Filter(fname, { - let mut args = match args { - Some(inner) => inner, - None => Vec::new(), - }; - args.insert(0, res); - args - }); - } - res - }) -)); - -macro_rules! expr_prec_layer { - ( $name:ident, $inner:ident, $( $op:expr ),* ) => { - named!($name, alt!( - do_parse!( - left: $inner >> - op: ws!(alt!($( tag_s!($op) )|*)) >> - right: $inner >> - (Expr::BinOp(str::from_utf8(op).unwrap(), - Box::new(left), Box::new(right))) - ) | $inner - )); - } -} - -expr_prec_layer!(expr_muldivmod, expr_filtered, "*", "/", "%"); -expr_prec_layer!(expr_addsub, expr_muldivmod, "+", "-"); -expr_prec_layer!(expr_shifts, expr_addsub, ">>", "<<"); -expr_prec_layer!(expr_band, expr_shifts, "&"); -expr_prec_layer!(expr_bxor, expr_band, "^"); -expr_prec_layer!(expr_bor, expr_bxor, "|"); -expr_prec_layer!(expr_compare, expr_bor, - "==", "!=", ">=", ">", "<=", "<" -); -expr_prec_layer!(expr_and, expr_compare, "&&"); -expr_prec_layer!(expr_any, expr_and, "||"); - -named!(expr_node, do_parse!( - tag_s!("{{") >> - pws: opt!(tag_s!("-")) >> - expr: ws!(expr_any) >> - nws: opt!(tag_s!("-")) >> - tag_s!("}}") >> - (Node::Expr(WS(pws.is_some(), nws.is_some()), expr)) -)); - -named!(block_call, do_parse!( - pws: opt!(tag_s!("-")) >> - ws!(tag_s!("call")) >> - name: ws!(identifier) >> - args: ws!(arguments) >> - nws: opt!(tag_s!("-")) >> - (Node::Call(WS(pws.is_some(), nws.is_some()), name, args)) -)); - -named!(cond_if, do_parse!( - ws!(tag_s!("if")) >> - cond: ws!(expr_any) >> - (cond) -)); - -named!(cond_block, do_parse!( - tag_s!("{%") >> - pws: opt!(tag_s!("-")) >> - ws!(tag_s!("else")) >> - cond: opt!(cond_if) >> - nws: opt!(tag_s!("-")) >> - tag_s!("%}") >> - block: parse_template >> - (WS(pws.is_some(), nws.is_some()), cond, block) -)); - -named!(block_if, do_parse!( - pws1: opt!(tag_s!("-")) >> - cond: ws!(cond_if) >> - nws1: opt!(tag_s!("-")) >> - tag_s!("%}") >> - block: parse_template >> - elifs: many0!(cond_block) >> - tag_s!("{%") >> - pws2: opt!(tag_s!("-")) >> - ws!(tag_s!("endif")) >> - nws2: opt!(tag_s!("-")) >> - ({ - let mut res = Vec::new(); - res.push((WS(pws1.is_some(), nws1.is_some()), Some(cond), block)); - res.extend(elifs); - Node::Cond(res, WS(pws2.is_some(), nws2.is_some())) - }) -)); - -named!(block_let, do_parse!( - pws: opt!(tag_s!("-")) >> - ws!(tag_s!("let")) >> - var: ws!(target_single) >> - val: opt!(do_parse!( - ws!(tag_s!("=")) >> - val: ws!(expr_any) >> - (val) - )) >> - nws: opt!(tag_s!("-")) >> - (if val.is_some() { - Node::Let(WS(pws.is_some(), nws.is_some()), var, val.unwrap()) - } else { - Node::LetDecl(WS(pws.is_some(), nws.is_some()), var) - }) -)); - -named!(block_for, do_parse!( - pws1: opt!(tag_s!("-")) >> - ws!(tag_s!("for")) >> - var: ws!(target_single) >> - ws!(tag_s!("in")) >> - iter: ws!(expr_any) >> - nws1: opt!(tag_s!("-")) >> - tag_s!("%}") >> - block: parse_template >> - tag_s!("{%") >> - pws2: opt!(tag_s!("-")) >> - ws!(tag_s!("endfor")) >> - nws2: opt!(tag_s!("-")) >> - (Node::Loop(WS(pws1.is_some(), nws1.is_some()), - var, iter, block, - WS(pws2.is_some(), pws2.is_some()))) -)); - -named!(block_extends, do_parse!( - ws!(tag_s!("extends")) >> - name: ws!(expr_str_lit) >> - (Node::Extends(name)) -)); - -named!(block_block, do_parse!( - pws1: opt!(tag_s!("-")) >> - ws!(tag_s!("block")) >> - name: ws!(identifier) >> - nws1: opt!(tag_s!("-")) >> - tag_s!("%}") >> - contents: parse_template >> - tag_s!("{%") >> - pws2: opt!(tag_s!("-")) >> - ws!(tag_s!("endblock")) >> - opt!(ws!(tag_s!(name))) >> - nws2: opt!(tag_s!("-")) >> - (Node::BlockDef(WS(pws1.is_some(), nws1.is_some()), - name, contents, - WS(pws2.is_some(), pws2.is_some()))) -)); - -named!(block_include, do_parse!( - pws: opt!(tag_s!("-")) >> - ws!(tag_s!("include")) >> - name: ws!(expr_str_lit) >> - nws: opt!(tag_s!("-")) >> - (Node::Include(WS(pws.is_some(), nws.is_some()), match name { - Expr::StrLit(s) => s, - _ => panic!("include path must be a string literal"), - })) -)); - -named!(block_macro, do_parse!( - pws1: opt!(tag_s!("-")) >> - ws!(tag_s!("macro")) >> - name: ws!(identifier) >> - params: ws!(parameters) >> - nws1: opt!(tag_s!("-")) >> - tag_s!("%}") >> - contents: parse_template >> - tag_s!("{%") >> - pws2: opt!(tag_s!("-")) >> - ws!(tag_s!("endmacro")) >> - nws2: opt!(tag_s!("-")) >> - (Node::Macro( - WS(pws1.is_some(), nws1.is_some()), - name, - params, - contents, - WS(pws2.is_some(), nws2.is_some()) - )) -)); - -named!(block_node, do_parse!( - tag_s!("{%") >> - contents: alt!( - block_call | - block_let | - block_if | - block_for | - block_extends | - block_include | - block_block | - block_macro - ) >> - tag_s!("%}") >> - (contents) -)); - -named!(block_comment, do_parse!( - tag_s!("{#") >> - take_until_s!("#}") >> - tag_s!("#}") >> - (Node::Comment()) -)); - -named!(parse_template>>, many0!(alt!( - take_content | - block_comment | - expr_node | - block_node -))); - -pub fn parse(src: &str) -> Vec { - match parse_template(src.as_bytes()) { - IResult::Done(left, res) => { - if left.len() > 0 { - let s = str::from_utf8(left).unwrap(); - panic!("unable to parse template:\n\n{:?}", s); - } else { - res - } - }, - IResult::Error(err) => panic!("problems parsing template source: {}", err), - IResult::Incomplete(_) => panic!("parsing incomplete"), - } -} - -#[cfg(test)] -mod tests { - fn check_ws_split(s: &str, res: &(&str, &str, &str)) { - let node = super::split_ws_parts(s.as_bytes()); - match node { - super::Node::Lit(lws, s, rws) => { - assert_eq!(lws, res.0); - assert_eq!(s, res.1); - assert_eq!(rws, res.2); - }, - _ => { panic!("fail"); }, - } - } - #[test] - fn test_ws_splitter() { - check_ws_split("", &("", "", "")); - check_ws_split("a", &("", "a", "")); - check_ws_split("\ta", &("\t", "a", "")); - check_ws_split("b\n", &("", "b", "\n")); - check_ws_split(" \t\r\n", &(" \t\r\n", "", "")); - } - #[test] - #[should_panic] - fn test_invalid_block() { - super::parse("{% extend \"blah\" %}"); - } -} diff --git a/askama_derive/src/path.rs b/askama_derive/src/path.rs deleted file mode 100644 index 3c04965..0000000 --- a/askama_derive/src/path.rs +++ /dev/null @@ -1,86 +0,0 @@ -use std::env; -use std::fs::File; -use std::io::Read; -use std::path::{Path, PathBuf}; - -pub fn get_template_source(tpl_path: &Path) -> String { - let mut path = template_dir(); - path.push(tpl_path); - let mut f = match File::open(&path) { - Err(_) => { - let msg = format!("unable to open template file '{}'", - &path.to_str().unwrap()); - panic!(msg); - }, - Ok(f) => f, - }; - let mut s = String::new(); - f.read_to_string(&mut s).unwrap(); - if s.ends_with('\n') { - let _ = s.pop(); - } - s -} - -pub fn find_template_from_path<'a>(path: &str, start_at: Option<&Path>) -> PathBuf { - let root = template_dir(); - if let Some(rel) = start_at { - let mut fs_rel_path = root.clone(); - fs_rel_path.push(rel); - fs_rel_path = fs_rel_path.with_file_name(path); - if fs_rel_path.exists() { - return fs_rel_path.strip_prefix(&root).unwrap().to_owned(); - } - } - - let mut fs_abs_path = root.clone(); - let path = Path::new(path); - fs_abs_path.push(Path::new(path)); - if fs_abs_path.exists() { - path.to_owned() - } else { - panic!(format!("template '{:?}' not found", path.to_str())); - } -} - -// Duplicated in askama -fn template_dir() -> PathBuf { - let mut path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - path.push("templates"); - path -} - -#[cfg(test)] -mod tests { - use super::{find_template_from_path, get_template_source}; - use super::Path; - - #[test] - fn get_source() { - assert_eq!(get_template_source(Path::new("sub/b.html")), "bar"); - } - - #[test] - fn find_absolute() { - let path = find_template_from_path("sub/b.html", Some(Path::new("a.html"))); - assert_eq!(path, Path::new("sub/b.html")); - } - - #[test] - #[should_panic] - fn find_relative_nonexistent() { - find_template_from_path("b.html", Some(Path::new("a.html"))); - } - - #[test] - fn find_relative() { - let path = find_template_from_path("c.html", Some(Path::new("sub/b.html"))); - assert_eq!(path, Path::new("sub/c.html")); - } - - #[test] - fn find_relative_sub() { - let path = find_template_from_path("sub1/d.html", Some(Path::new("sub/b.html"))); - assert_eq!(path, Path::new("sub/sub1/d.html")); - } -} diff --git a/askama_derive/templates/a.html b/askama_derive/templates/a.html deleted file mode 100644 index 257cc56..0000000 --- a/askama_derive/templates/a.html +++ /dev/null @@ -1 +0,0 @@ -foo diff --git a/askama_derive/templates/sub/b.html b/askama_derive/templates/sub/b.html deleted file mode 100644 index 5716ca5..0000000 --- a/askama_derive/templates/sub/b.html +++ /dev/null @@ -1 +0,0 @@ -bar diff --git a/askama_derive/templates/sub/c.html b/askama_derive/templates/sub/c.html deleted file mode 100644 index 7601807..0000000 --- a/askama_derive/templates/sub/c.html +++ /dev/null @@ -1 +0,0 @@ -baz diff --git a/askama_derive/templates/sub/sub1/d.html b/askama_derive/templates/sub/sub1/d.html deleted file mode 100644 index fa11a6a..0000000 --- a/askama_derive/templates/sub/sub1/d.html +++ /dev/null @@ -1 +0,0 @@ -echo diff --git a/askama_shared/Cargo.toml b/askama_shared/Cargo.toml new file mode 100644 index 0000000..6ad2bff --- /dev/null +++ b/askama_shared/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "askama_shared" +version = "0.3.4" +authors = ["Dirkjan Ochtman "] +workspace = ".." + +[features] +default = [] +serde-json = ["serde", "serde_json"] +iron = [] +rocket = [] + +[dependencies] +error-chain = "0.10" +nom = "3" +quote = "0.3" +serde = { version = "1.0", optional = true } +serde_json = { version = "1.0", optional = true } +syn = "0.11" diff --git a/askama_shared/src/filters/json.rs b/askama_shared/src/filters/json.rs new file mode 100644 index 0000000..4443fb4 --- /dev/null +++ b/askama_shared/src/filters/json.rs @@ -0,0 +1,30 @@ +use serde::Serialize; +use serde_json; +use errors::{Error, Result}; + +/// Serialize to JSON (requires `serde-json` feature) +/// +/// ## Errors +/// +/// This will panic if `S`'s implementation of `Serialize` decides to fail, +/// or if `T` contains a map with non-string keys. +pub fn json(s: &S) -> Result { + serde_json::to_string_pretty(s).map_err(Error::from) +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_json() { + assert_eq!(json(&true).unwrap(), "true"); + assert_eq!(json(&"foo").unwrap(), r#""foo""#); + assert_eq!(json(&vec!["foo", "bar"]).unwrap(), +r#"[ + "foo", + "bar" +]"#); + } +} diff --git a/askama_shared/src/filters/mod.rs b/askama_shared/src/filters/mod.rs new file mode 100644 index 0000000..73b311d --- /dev/null +++ b/askama_shared/src/filters/mod.rs @@ -0,0 +1,130 @@ +//! Module for built-in filter functions +//! +//! Contains all the built-in filter functions for use in templates. +//! Currently, there is no way to define filters outside this module. +// +// WHEN ADDING FILTERS, DON'T FORGET TO UPDATE `BUILT_IN_FILTERS` in askama_derive::generator. + +#[cfg(feature = "serde-json")] +mod json; + +#[cfg(feature = "serde-json")] +pub use self::json::json; + +use std::fmt; +use super::Result; + + +fn escapable(b: &u8) -> bool { + *b == b'<' || *b == b'>' || *b == b'&' +} + +/// Escapes `&`, `<` and `>` in strings +pub fn escape(s: &fmt::Display) -> Result { + let s = format!("{}", s); + let mut found = Vec::new(); + for (i, b) in s.as_bytes().iter().enumerate() { + if escapable(b) { + found.push(i); + } + } + if found.is_empty() { + return Ok(s); + } + + let bytes = s.as_bytes(); + let max_len = bytes.len() + found.len() * 3; + let mut res = Vec::::with_capacity(max_len); + let mut start = 0; + for idx in &found { + if start < *idx { + res.extend(&bytes[start..*idx]); + } + start = *idx + 1; + match bytes[*idx] { + b'<' => { res.extend(b"<"); }, + b'>' => { res.extend(b">"); }, + b'&' => { res.extend(b"&"); }, + _ => panic!("incorrect indexing"), + } + } + if start < bytes.len() - 1 { + res.extend(&bytes[start..]); + } + + Ok(String::from_utf8(res).unwrap()) +} + +/// Alias for the `escape()` filter +pub fn e(s: &fmt::Display) -> Result { + escape(s) +} + +/// Formats arguments according to the specified format +/// +/// The first argument to this filter must be a string literal (as in normal +/// Rust). All arguments are passed through to the `format!()` +/// [macro](https://doc.rust-lang.org/stable/std/macro.format.html) by +/// the Askama code generator. +pub fn format() { } + +/// Converts to lowercase. +pub fn lower(s: &fmt::Display) -> Result { + let s = format!("{}", s); + Ok(s.to_lowercase()) +} + +/// Alias for the `lower()` filter. +pub fn lowercase(s: &fmt::Display) -> Result { + lower(s) +} + +/// Converts to uppercase. +pub fn upper(s: &fmt::Display) -> Result { + let s = format!("{}", s); + Ok(s.to_uppercase()) +} + +/// Alias for the `upper()` filter. +pub fn uppercase(s: &fmt::Display) -> Result { + upper(s) +} + +/// Strip leading and trailing whitespace. +pub fn trim(s: &fmt::Display) -> Result { + let s = format!("{}", s); + Ok(s.trim().to_owned()) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_escape() { + assert_eq!(escape(&"").unwrap(), ""); + assert_eq!(escape(&"<&>").unwrap(), "<&>"); + assert_eq!(escape(&"bla&").unwrap(), "bla&"); + assert_eq!(escape(&") -> String { + let mut base: Option = None; + let mut blocks = Vec::new(); + let mut content = Vec::new(); + let mut macros = HashMap::new(); + for n in nodes.drain(..) { + match n { + Node::Extends(path) => { + match base { + Some(_) => panic!("multiple extend blocks found"), + None => { base = Some(path); }, + } + }, + Node::BlockDef(ws1, name, _, ws2) => { + blocks.push(n); + content.push(Node::Block(ws1, name, ws2)); + }, + Node::Macro(ws1, name, params, contents, ws2) => { + macros.insert(name, (ws1, name, params, contents, ws2)); + }, + _ => { content.push(n); }, + } + } + + let mut gen = Generator::default(¯os); + if !blocks.is_empty() { + let trait_name = trait_name_for_path(&base, path); + if base.is_none() { + gen.define_trait(&trait_name, &blocks); + } else { + let parent_type = get_parent_type(ast) + .expect("expected field '_parent' in extending template struct"); + gen.deref_to_parent(ast, &parent_type); + } + + let trait_nodes = if base.is_none() { Some(&content[..]) } else { None }; + gen.impl_trait(ast, &trait_name, &blocks, trait_nodes); + gen.impl_template_for_trait(ast, base.is_some()); + } else { + gen.impl_template(ast, &content); + } + gen.impl_display(ast); + if cfg!(feature = "iron") { + gen.impl_modifier_response(ast); + } + if cfg!(feature = "rocket") { + gen.impl_responder(ast, path); + } + gen.result() +} + +fn trait_name_for_path(base: &Option, path: &Path) -> String { + let rooted_path = match *base { + Some(Expr::StrLit(user_path)) => { + path::find_template_from_path(user_path, Some(path)) + }, + _ => path.to_path_buf(), + }; + + let mut res = String::new(); + res.push_str("TraitFrom"); + for c in rooted_path.to_string_lossy().chars() { + if c.is_alphanumeric() { + res.push(c); + } else { + res.push_str(&format!("{:x}", c as u32)); + } + } + res +} + +fn get_parent_type(ast: &syn::DeriveInput) -> Option<&syn::Ty> { + match ast.body { + syn::Body::Struct(ref data) => { + data.fields().iter().filter_map(|f| { + f.ident.as_ref().and_then(|name| { + if name.as_ref() == "_parent" { + Some(&f.ty) + } else { + None + } + }) + }) + }, + _ => panic!("derive(Template) only works for struct items"), + }.next() +} + +struct Generator<'a> { + buf: String, + indent: u8, + start: bool, + locals: SetChain<'a, &'a str>, + next_ws: Option<&'a str>, + skip_ws: bool, + macros: &'a MacroMap<'a>, +} + +impl<'a> Generator<'a> { + + fn new<'n>(macros: &'n MacroMap, locals: SetChain<'n, &'n str>, indent: u8) -> Generator<'n> { + Generator { + buf: String::new(), + indent: indent, + start: true, + locals: locals, + next_ws: None, + skip_ws: false, + macros: macros, + } + } + + fn default<'n>(macros: &'n MacroMap) -> Generator<'n> { + Self::new(macros, SetChain::new(), 0) + } + + fn child<'n>(&'n mut self) -> Generator<'n> { + let locals = SetChain::with_parent(&self.locals); + Self::new(self.macros, locals, self.indent) + } + + /* Helper methods for writing to internal buffer */ + + fn indent(&mut self) { + self.indent += 1; + } + + fn dedent(&mut self) { + self.indent -= 1; + } + + fn write(&mut self, s: &str) { + if self.start { + for _ in 0..(self.indent * 4) { + self.buf.push(' '); + } + self.start = false; + } + self.buf.push_str(s); + } + + fn writeln(&mut self, s: &str) { + if s.is_empty() { + return; + } + if s == "}" { + self.dedent(); + } + self.write(s); + if s.ends_with('{') { + self.indent(); + } + self.buf.push('\n'); + self.start = true; + } + + /* Helper methods for dealing with whitespace nodes */ + + fn flush_ws(&mut self, ws: &WS) { + if self.next_ws.is_some() && !ws.0 { + let val = self.next_ws.unwrap(); + if !val.is_empty() { + self.writeln(&format!("writer.write_str({:#?})?;", + val)); + } + } + self.next_ws = None; + } + + fn prepare_ws(&mut self, ws: &WS) { + self.skip_ws = ws.1; + } + + fn handle_ws(&mut self, ws: &WS) { + self.flush_ws(ws); + self.prepare_ws(ws); + } + + /* Visitor methods for expression types */ + + fn visit_num_lit(&mut self, s: &str) { + self.write(s); + } + + fn visit_str_lit(&mut self, s: &str) { + self.write(&format!("\"{}\"", s)); + } + + fn visit_var(&mut self, s: &str) { + if self.locals.contains(s) { + self.write(s); + } else { + self.write(&format!("self.{}", s)); + } + } + + fn visit_attr(&mut self, obj: &Expr, attr: &str) { + if let Expr::Var(name) = *obj { + if name == "loop" { + self.write("_loop_index"); + if attr == "index" { + self.write(" + 1"); + return; + } else if attr == "index0" { + return; + } else { + panic!("unknown loop variable"); + } + } + } + self.visit_expr(obj); + self.write(&format!(".{}", attr)); + } + + fn visit_filter(&mut self, name: &str, args: &[Expr]) { + if name == "format" { + self.write("format!("); + } else if BUILT_IN_FILTERS.contains(&name) { + self.write(&format!("::askama::filters::{}(&", name)); + } else { + self.write(&format!("filters::{}(&", name)); + } + + for (i, arg) in args.iter().enumerate() { + if i > 0 { + self.write(", &"); + } + self.visit_expr(arg); + } + self.write(")"); + if name != "format" { + self.write("?"); + } + } + + fn visit_binop(&mut self, op: &str, left: &Expr, right: &Expr) { + self.visit_expr(left); + self.write(&format!(" {} ", op)); + self.visit_expr(right); + } + + fn visit_group(&mut self, inner: &Expr) { + self.write("("); + self.visit_expr(inner); + self.write(")"); + } + + fn visit_method_call(&mut self, obj: &Expr, method: &str, args: &[Expr]) { + self.visit_expr(obj); + self.write(&format!(".{}(", method)); + for (i, arg) in args.iter().enumerate() { + if i > 0 { + self.write(", "); + } + self.visit_expr(arg); + } + self.write(")"); + } + + fn visit_expr(&mut self, expr: &Expr) { + match *expr { + Expr::NumLit(s) => self.visit_num_lit(s), + Expr::StrLit(s) => self.visit_str_lit(s), + Expr::Var(s) => self.visit_var(s), + Expr::Attr(ref obj, name) => self.visit_attr(obj, name), + Expr::Filter(name, ref args) => self.visit_filter(name, args), + Expr::BinOp(op, ref left, ref right) => + self.visit_binop(op, left, right), + Expr::Group(ref inner) => self.visit_group(inner), + Expr::MethodCall(ref obj, method, ref args) => + self.visit_method_call(obj, method, args), + } + } + + fn visit_target_single<'t>(&mut self, name: &'t str) -> Vec<&'t str> { + vec![name] + } + + fn visit_target<'t>(&mut self, target: &'t Target) -> Vec<&'t str> { + match *target { + Target::Name(s) => { self.visit_target_single(s) }, + } + } + + /* Helper methods for handling node types */ + + fn write_lit(&mut self, lws: &'a str, val: &str, rws: &'a str) { + assert!(self.next_ws.is_none()); + if !lws.is_empty() { + if self.skip_ws { + self.skip_ws = false; + } else if val.is_empty() { + assert!(rws.is_empty()); + self.next_ws = Some(lws); + } else { + self.writeln(&format!("writer.write_str({:#?})?;", + lws)); + } + } + if !val.is_empty() { + self.writeln(&format!("writer.write_str({:#?})?;", val)); + } + if !rws.is_empty() { + self.next_ws = Some(rws); + } + } + + fn write_expr(&mut self, ws: &WS, s: &Expr) { + self.handle_ws(ws); + self.write("writer.write_fmt(format_args!(\"{}\", "); + self.visit_expr(s); + self.writeln("))?;"); + } + + fn write_call(&mut self, ws: &WS, name: &str, args: &[Expr]) { + self.handle_ws(ws); + let def = self.macros.get(name).expect(&format!("macro '{}' not found", name)); + self.locals.push(); + self.writeln("{"); + self.prepare_ws(&def.0); + for (i, arg) in def.2.iter().enumerate() { + self.write(&format!("let {} = &", arg)); + self.locals.insert(arg); + self.visit_expr(&args.get(i) + .expect(&format!("macro '{}' takes more than {} arguments", name, i))); + self.writeln(";"); + } + self.handle(&def.3); + self.flush_ws(&def.4); + self.writeln("}"); + self.locals.pop(); + } + + fn write_let_decl(&mut self, ws: &WS, var: &'a Target) { + self.handle_ws(ws); + self.write("let "); + match *var { + Target::Name(name) => { + self.locals.insert(name); + self.write(name); + }, + } + self.writeln(";"); + } + + fn write_let(&mut self, ws: &WS, var: &'a Target, val: &Expr) { + self.handle_ws(ws); + match *var { + Target::Name(name) => { + if !self.locals.contains(name) { + self.write("let "); + self.locals.insert(name); + } + self.write(name); + }, + } + self.write(" = "); + self.visit_expr(val); + self.writeln(";"); + } + + fn write_cond(&mut self, conds: &'a [Cond], ws: &WS) { + for (i, &(ref cws, ref cond, ref nodes)) in conds.iter().enumerate() { + self.handle_ws(cws); + match *cond { + Some(ref expr) => { + if i == 0 { + self.write("if "); + } else { + self.dedent(); + self.write("} else if "); + } + self.visit_expr(expr); + }, + None => { + self.dedent(); + self.write("} else"); + }, + } + self.writeln(" {"); + self.locals.push(); + self.handle(nodes); + self.locals.pop(); + } + self.handle_ws(ws); + self.writeln("}"); + } + + fn write_loop(&mut self, ws1: &WS, var: &'a Target, iter: &Expr, + body: &'a [Node], ws2: &WS) { + self.handle_ws(ws1); + self.locals.push(); + self.write("for (_loop_index, "); + let targets = self.visit_target(var); + for name in &targets { + self.locals.insert(name); + self.write(name); + } + self.write(") in (&"); + self.visit_expr(iter); + self.writeln(").into_iter().enumerate() {"); + + self.handle(body); + self.handle_ws(ws2); + self.writeln("}"); + self.locals.pop(); + } + + fn write_block(&mut self, ws1: &WS, name: &str, ws2: &WS) { + self.flush_ws(ws1); + self.writeln(&format!("timpl.render_block_{}_into(writer)?;", name)); + self.prepare_ws(ws2); + } + + fn write_block_def(&mut self, ws1: &WS, name: &str, nodes: &'a [Node], + ws2: &WS) { + self.writeln("#[allow(unused_variables)]"); + self.writeln(&format!( + "fn render_block_{}_into(&self, writer: &mut ::std::fmt::Write) \ + -> ::askama::Result<()> {{", + name)); + self.prepare_ws(ws1); + + self.locals.push(); + self.handle(nodes); + self.locals.pop(); + + self.flush_ws(ws2); + self.writeln("Ok(())"); + self.writeln("}"); + } + + fn handle_include(&mut self, ws: &WS, path: &str) { + self.prepare_ws(ws); + let path = path::find_template_from_path(&path, None); + let src = path::get_template_source(&path); + let nodes = parser::parse(&src); + let nested = { + let mut gen = self.child(); + gen.handle(&nodes); + gen.result() + }; + self.buf.push_str(&nested); + self.flush_ws(ws); + } + + fn handle(&mut self, nodes: &'a [Node]) { + for n in nodes { + match *n { + Node::Lit(lws, val, rws) => { self.write_lit(lws, val, rws); } + Node::Comment() => {}, + Node::Expr(ref ws, ref val) => { self.write_expr(ws, val); }, + Node::LetDecl(ref ws, ref var) => { self.write_let_decl(ws, var); }, + Node::Let(ref ws, ref var, ref val) => { self.write_let(ws, var, val); }, + Node::Cond(ref conds, ref ws) => { + self.write_cond(conds, ws); + }, + Node::Loop(ref ws1, ref var, ref iter, ref body, ref ws2) => { + self.write_loop(ws1, var, iter, body, ws2); + }, + Node::Block(ref ws1, name, ref ws2) => { + self.write_block(ws1, name, ws2); + }, + Node::BlockDef(ref ws1, name, ref block_nodes, ref ws2) => { + self.write_block_def(ws1, name, block_nodes, ws2); + } + Node::Include(ref ws, ref path) => { + self.handle_include(ws, path); + }, + Node::Call(ref ws, name, ref args) => self.write_call(ws, name, args), + Node::Macro(_, _, _, _, _) | + Node::Extends(_) => { + panic!("no extends or macros allowed in content"); + }, + } + } + } + + // Writes header for the `impl` for `TraitFromPathName` or `Template` + // for the given context struct. + fn write_header(&mut self, ast: &syn::DeriveInput, target: &str, extra_anno: &[&str]) { + let mut full_anno = Tokens::new(); + let mut orig_anno = Tokens::new(); + let need_anno = ast.generics.lifetimes.len() > 0 || + ast.generics.ty_params.len() > 0 || + extra_anno.len() > 0; + if need_anno { + full_anno.append("<"); + orig_anno.append("<"); + } + + let (mut full_sep, mut orig_sep) = (false, false); + for lt in &ast.generics.lifetimes { + if full_sep { + full_anno.append(","); + } + if orig_sep { + orig_anno.append(","); + } + lt.to_tokens(&mut full_anno); + lt.to_tokens(&mut orig_anno); + full_sep = true; + orig_sep = true; + } + + for anno in extra_anno { + if full_sep { + full_anno.append(","); + } + full_anno.append(anno); + full_sep = true; + } + + for param in &ast.generics.ty_params { + if full_sep { + full_anno.append(","); + } + if orig_sep { + orig_anno.append(","); + } + let mut impl_param = param.clone(); + impl_param.default = None; + impl_param.to_tokens(&mut full_anno); + param.ident.to_tokens(&mut orig_anno); + full_sep = true; + orig_sep = true; + } + + if need_anno { + full_anno.append(">"); + orig_anno.append(">"); + } + + let mut where_clause = Tokens::new(); + ast.generics.where_clause.to_tokens(&mut where_clause); + self.writeln(&format!("impl{} {} for {}{}{} {{", + full_anno.as_str(), target, ast.ident.as_ref(), + orig_anno.as_str(), where_clause.as_str())); + } + + // Implement `Template` for the given context struct. + fn impl_template(&mut self, ast: &syn::DeriveInput, nodes: &'a [Node]) { + self.write_header(ast, "::askama::Template", &vec![]); + self.writeln("fn render_into(&self, writer: &mut ::std::fmt::Write) -> \ + ::askama::Result<()> {"); + self.handle(nodes); + self.flush_ws(&WS(false, false)); + self.writeln("Ok(())"); + self.writeln("}"); + self.writeln("}"); + } + + // Implement `Display` for the given context struct. + fn impl_display(&mut self, ast: &syn::DeriveInput) { + self.write_header(ast, "::std::fmt::Display", &vec![]); + self.writeln("fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {"); + self.writeln("self.render_into(f).map_err(|_| ::std::fmt::Error {})"); + self.writeln("}"); + self.writeln("}"); + } + + // Implement `Deref` for an inheriting context struct. + fn deref_to_parent(&mut self, ast: &syn::DeriveInput, parent_type: &syn::Ty) { + self.write_header(ast, "::std::ops::Deref", &vec![]); + let mut tokens = Tokens::new(); + parent_type.to_tokens(&mut tokens); + self.writeln(&format!("type Target = {};", tokens.as_str())); + self.writeln("fn deref(&self) -> &Self::Target {"); + self.writeln("&self._parent"); + self.writeln("}"); + self.writeln("}"); + } + + // Implement `TraitFromPathName` for the given context struct. + fn impl_trait(&mut self, ast: &syn::DeriveInput, trait_name: &str, + blocks: &'a [Node], nodes: Option<&'a [Node]>) { + self.write_header(ast, &trait_name, &vec![]); + self.handle(blocks); + + self.writeln("#[allow(unused_variables)]"); + self.writeln(&format!( + "fn render_trait_into(&self, timpl: &{}, writer: &mut ::std::fmt::Write) \ + -> ::askama::Result<()> {{", + trait_name)); + + if let Some(nodes) = nodes { + self.handle(nodes); + self.flush_ws(&WS(false, false)); + } else { + self.writeln("self._parent.render_trait_into(self, writer)?;"); + } + + self.writeln("Ok(())"); + self.writeln("}"); + self.flush_ws(&WS(false, false)); + self.writeln("}"); + } + + // Implement `Template` for templates that implement a template trait. + fn impl_template_for_trait(&mut self, ast: &syn::DeriveInput, derived: bool) { + self.write_header(ast, "::askama::Template", &vec![]); + self.writeln("fn render_into(&self, writer: &mut ::std::fmt::Write) \ + -> ::askama::Result<()> {"); + if derived { + self.writeln("self._parent.render_trait_into(self, writer)?;"); + } else { + self.writeln("self.render_trait_into(self, writer)?;"); + } + self.writeln("Ok(())"); + self.writeln("}"); + self.writeln("}"); + } + + // Defines the `TraitFromPathName` trait. + fn define_trait(&mut self, trait_name: &str, blocks: &'a [Node]) { + self.writeln(&format!("trait {} {{", &trait_name)); + self.handle(blocks); + self.writeln(&format!( + "fn render_trait_into(&self, timpl: &{}, writer: &mut ::std::fmt::Write) \ + -> ::askama::Result<()>;", + trait_name)); + self.writeln("}"); + } + + // Implement iron's Modifier if enabled + fn impl_modifier_response(&mut self, ast: &syn::DeriveInput) { + self.write_header(ast, "::askama::iron::Modifier<::askama::iron::Response>", &vec![]); + self.writeln("fn modify(self, res: &mut ::askama::iron::Response) {"); + self.writeln("res.body = Some(Box::new(self.render().unwrap().into_bytes()));"); + self.writeln("}"); + self.writeln("}"); + } + + // Implement Rocket's `Responder`. + fn impl_responder(&mut self, ast: &syn::DeriveInput, path: &Path) { + self.write_header(ast, "::askama::rocket::Responder<'r>", &vec!["'r"]); + self.writeln("fn respond_to(self, _: &::askama::rocket::Request) \ + -> ::std::result::Result<\ + ::askama::rocket::Response<'r>, ::askama::rocket::Status> {"); + let ext = match path.extension() { + Some(s) => s.to_str().unwrap(), + None => "txt", + }; + self.writeln("::askama::rocket::Response::build()"); + self.indent(); + self.writeln(&format!(".header(::askama::rocket::ContentType::from_extension({:?})\ + .unwrap())", ext)); + self.writeln(".sized_body(::std::io::Cursor::new(self.render().unwrap()))"); + self.writeln(".ok()"); + self.dedent(); + self.writeln("}"); + self.writeln("}"); + } + + fn result(self) -> String { + self.buf + } + +} + +struct SetChain<'a, T: 'a> where T: cmp::Eq + hash::Hash { + parent: Option<&'a SetChain<'a, T>>, + scopes: Vec>, +} + +impl<'a, T: 'a> SetChain<'a, T> where T: cmp::Eq + hash::Hash { + fn new() -> SetChain<'a, T> { + SetChain { parent: None, scopes: vec![HashSet::new()] } + } + fn with_parent<'p>(parent: &'p SetChain) -> SetChain<'p, T> { + SetChain { parent: Some(parent), scopes: vec![HashSet::new()] } + } + fn contains(&self, val: T) -> bool { + self.scopes.iter().rev().any(|set| set.contains(&val)) || + match self.parent { + Some(set) => set.contains(val), + None => false, + } + } + fn insert(&mut self, val: T) { + self.scopes.last_mut().unwrap().insert(val); + } + fn push(&mut self) { + self.scopes.push(HashSet::new()); + } + fn pop(&mut self) { + self.scopes.pop().unwrap(); + assert!(self.scopes.len() > 0); + } +} + +type MacroMap<'a> = HashMap<&'a str, (WS, &'a str, Vec<&'a str>, Vec>, WS)>; + +const BUILT_IN_FILTERS: [&str; 9] = [ + "e", + "escape", + "format", + "lower", + "lowercase", + "trim", + "upper", + "uppercase", + "json", // Optional feature; reserve the name anyway +]; diff --git a/askama_shared/src/lib.rs b/askama_shared/src/lib.rs new file mode 100644 index 0000000..1ee19cf --- /dev/null +++ b/askama_shared/src/lib.rs @@ -0,0 +1,29 @@ +#[macro_use] +extern crate error_chain; +#[macro_use] +extern crate nom; +extern crate quote; +extern crate syn; + +#[cfg(feature = "serde-json")] +extern crate serde; +#[cfg(feature = "serde-json")] +extern crate serde_json; + +pub use errors::Result; +pub mod filters; +pub mod path; +pub use parser::parse; +pub use generator::generate; + +mod generator; +mod parser; + +mod errors { + error_chain! { + foreign_links { + Fmt(::std::fmt::Error); + Json(::serde_json::Error) #[cfg(feature = "serde-json")]; + } + } +} diff --git a/askama_shared/src/parser.rs b/askama_shared/src/parser.rs new file mode 100644 index 0000000..8732f0e --- /dev/null +++ b/askama_shared/src/parser.rs @@ -0,0 +1,471 @@ +use nom::{self, IResult}; +use std::str; + +#[derive(Debug)] +pub enum Expr<'a> { + NumLit(&'a str), + StrLit(&'a str), + Var(&'a str), + Attr(Box>, &'a str), + Filter(&'a str, Vec>), + BinOp(&'a str, Box>, Box>), + Group(Box>), + MethodCall(Box>, &'a str, Vec>), +} + +#[derive(Debug)] +pub enum Target<'a> { + Name(&'a str), +} + +#[derive(Clone, Copy, Debug)] +pub struct WS(pub bool, pub bool); + +#[derive(Debug)] +pub enum Node<'a> { + Lit(&'a str, &'a str, &'a str), + Comment(), + Expr(WS, Expr<'a>), + Call(WS, &'a str, Vec>), + LetDecl(WS, Target<'a>), + Let(WS, Target<'a>, Expr<'a>), + Cond(Vec<(WS, Option>, Vec>)>, WS), + Loop(WS, Target<'a>, Expr<'a>, Vec>, WS), + Extends(Expr<'a>), + BlockDef(WS, &'a str, Vec>, WS), + Block(WS, &'a str, WS), + Include(WS, &'a str), + Macro(WS, &'a str, Vec<&'a str>, Vec>, WS), +} + +pub type Cond<'a> = (WS, Option>, Vec>); + +fn split_ws_parts(s: &[u8]) -> Node { + if s.is_empty() { + let rs = str::from_utf8(s).unwrap(); + return Node::Lit(rs, rs, rs); + } + let is_ws = |c: &u8| { + *c != b' ' && *c != b'\t' && *c != b'\r' && *c != b'\n' + }; + let start = s.iter().position(&is_ws); + let res = if start.is_none() { + (s, &s[0..0], &s[0..0]) + } else { + let start = start.unwrap(); + let end = s.iter().rposition(&is_ws); + if end.is_none() { + (&s[..start], &s[start..], &s[0..0]) + } else { + let end = end.unwrap(); + (&s[..start], &s[start..end + 1], &s[end + 1..]) + } + }; + Node::Lit(str::from_utf8(res.0).unwrap(), + str::from_utf8(res.1).unwrap(), + str::from_utf8(res.2).unwrap()) +} + +enum ContentState { + Any, + Brace(usize), + End(usize), +} + +fn take_content(i: &[u8]) -> IResult<&[u8], Node> { + use parser::ContentState::*; + let mut state = Any; + for (idx, c) in i.iter().enumerate() { + state = match (state, *c) { + (Any, b'{') => Brace(idx), + (Any, _) => Any, + (Brace(start), b'{') | + (Brace(start), b'%') | + (Brace(start), b'#') => End(start), + (Brace(_), _) => Any, + (End(_), _) => panic!("cannot happen"), + }; + if let End(_) = state { + break; + } + } + match state { + Any | + Brace(_) => IResult::Done(&i[..0], split_ws_parts(i)), + End(0) => IResult::Error(nom::ErrorKind::Custom(0)), + End(start) => IResult::Done(&i[start..], split_ws_parts(&i[..start])), + } +} + +fn identifier(input: &[u8]) -> IResult<&[u8], &str> { + if !nom::is_alphabetic(input[0]) && input[0] != b'_' { + return IResult::Error(nom::ErrorKind::Custom(0)); + } + for (i, ch) in input.iter().enumerate() { + if i == 0 || nom::is_alphanumeric(*ch) || *ch == b'_' { + continue; + } + return IResult::Done(&input[i..], + str::from_utf8(&input[..i]).unwrap()); + } + IResult::Done(&input[1..], str::from_utf8(&input[..1]).unwrap()) +} + +named!(expr_num_lit, map!(nom::digit, + |s| Expr::NumLit(str::from_utf8(s).unwrap()) +)); + +named!(expr_str_lit, map!( + delimited!(char!('"'), is_not!("\""), char!('"')), + |s| Expr::StrLit(str::from_utf8(s).unwrap()) +)); + +named!(expr_var, map!(identifier, + |s| Expr::Var(s)) +); + +named!(target_single, map!(identifier, + |s| Target::Name(s) +)); + +named!(arguments>, do_parse!( + tag_s!("(") >> + args: opt!(do_parse!( + arg0: ws!(expr_any) >> + args: many0!(do_parse!( + tag_s!(",") >> + argn: ws!(expr_any) >> + (argn) + )) >> + ({ + let mut res = vec![arg0]; + res.extend(args); + res + }) + )) >> + tag_s!(")") >> + (args.unwrap_or(Vec::new())) +)); + +named!(parameters>, do_parse!( + tag_s!("(") >> + vals: opt!(do_parse!( + arg0: ws!(identifier) >> + args: many0!(do_parse!( + tag_s!(",") >> + argn: ws!(identifier) >> + (argn) + )) >> + ({ + let mut res = vec![arg0]; + res.extend(args); + res + }) + )) >> + tag_s!(")") >> + (vals.unwrap_or(Vec::new())) +)); + +named!(expr_group, map!( + delimited!(char!('('), expr_any, char!(')')), + |s| Expr::Group(Box::new(s)) +)); + +named!(expr_single, alt!( + expr_num_lit | + expr_str_lit | + expr_var | + expr_group +)); + +named!(attr<(&str, Option>)>, do_parse!( + tag_s!(".") >> + attr: identifier >> + args: opt!(arguments) >> + (attr, args) +)); + +named!(expr_attr, do_parse!( + obj: expr_single >> + attrs: many0!(attr) >> + ({ + let mut res = obj; + for (aname, args) in attrs { + res = if args.is_some() { + Expr::MethodCall(Box::new(res), aname, args.unwrap()) + } else { + Expr::Attr(Box::new(res), aname) + }; + } + res + }) +)); + +named!(filter<(&str, Option>)>, do_parse!( + tag_s!("|") >> + fname: identifier >> + args: opt!(arguments) >> + (fname, args) +)); + +named!(expr_filtered, do_parse!( + obj: expr_attr >> + filters: many0!(filter) >> + ({ + let mut res = obj; + for (fname, args) in filters { + res = Expr::Filter(fname, { + let mut args = match args { + Some(inner) => inner, + None => Vec::new(), + }; + args.insert(0, res); + args + }); + } + res + }) +)); + +macro_rules! expr_prec_layer { + ( $name:ident, $inner:ident, $( $op:expr ),* ) => { + named!($name, alt!( + do_parse!( + left: $inner >> + op: ws!(alt!($( tag_s!($op) )|*)) >> + right: $inner >> + (Expr::BinOp(str::from_utf8(op).unwrap(), + Box::new(left), Box::new(right))) + ) | $inner + )); + } +} + +expr_prec_layer!(expr_muldivmod, expr_filtered, "*", "/", "%"); +expr_prec_layer!(expr_addsub, expr_muldivmod, "+", "-"); +expr_prec_layer!(expr_shifts, expr_addsub, ">>", "<<"); +expr_prec_layer!(expr_band, expr_shifts, "&"); +expr_prec_layer!(expr_bxor, expr_band, "^"); +expr_prec_layer!(expr_bor, expr_bxor, "|"); +expr_prec_layer!(expr_compare, expr_bor, + "==", "!=", ">=", ">", "<=", "<" +); +expr_prec_layer!(expr_and, expr_compare, "&&"); +expr_prec_layer!(expr_any, expr_and, "||"); + +named!(expr_node, do_parse!( + tag_s!("{{") >> + pws: opt!(tag_s!("-")) >> + expr: ws!(expr_any) >> + nws: opt!(tag_s!("-")) >> + tag_s!("}}") >> + (Node::Expr(WS(pws.is_some(), nws.is_some()), expr)) +)); + +named!(block_call, do_parse!( + pws: opt!(tag_s!("-")) >> + ws!(tag_s!("call")) >> + name: ws!(identifier) >> + args: ws!(arguments) >> + nws: opt!(tag_s!("-")) >> + (Node::Call(WS(pws.is_some(), nws.is_some()), name, args)) +)); + +named!(cond_if, do_parse!( + ws!(tag_s!("if")) >> + cond: ws!(expr_any) >> + (cond) +)); + +named!(cond_block, do_parse!( + tag_s!("{%") >> + pws: opt!(tag_s!("-")) >> + ws!(tag_s!("else")) >> + cond: opt!(cond_if) >> + nws: opt!(tag_s!("-")) >> + tag_s!("%}") >> + block: parse_template >> + (WS(pws.is_some(), nws.is_some()), cond, block) +)); + +named!(block_if, do_parse!( + pws1: opt!(tag_s!("-")) >> + cond: ws!(cond_if) >> + nws1: opt!(tag_s!("-")) >> + tag_s!("%}") >> + block: parse_template >> + elifs: many0!(cond_block) >> + tag_s!("{%") >> + pws2: opt!(tag_s!("-")) >> + ws!(tag_s!("endif")) >> + nws2: opt!(tag_s!("-")) >> + ({ + let mut res = Vec::new(); + res.push((WS(pws1.is_some(), nws1.is_some()), Some(cond), block)); + res.extend(elifs); + Node::Cond(res, WS(pws2.is_some(), nws2.is_some())) + }) +)); + +named!(block_let, do_parse!( + pws: opt!(tag_s!("-")) >> + ws!(tag_s!("let")) >> + var: ws!(target_single) >> + val: opt!(do_parse!( + ws!(tag_s!("=")) >> + val: ws!(expr_any) >> + (val) + )) >> + nws: opt!(tag_s!("-")) >> + (if val.is_some() { + Node::Let(WS(pws.is_some(), nws.is_some()), var, val.unwrap()) + } else { + Node::LetDecl(WS(pws.is_some(), nws.is_some()), var) + }) +)); + +named!(block_for, do_parse!( + pws1: opt!(tag_s!("-")) >> + ws!(tag_s!("for")) >> + var: ws!(target_single) >> + ws!(tag_s!("in")) >> + iter: ws!(expr_any) >> + nws1: opt!(tag_s!("-")) >> + tag_s!("%}") >> + block: parse_template >> + tag_s!("{%") >> + pws2: opt!(tag_s!("-")) >> + ws!(tag_s!("endfor")) >> + nws2: opt!(tag_s!("-")) >> + (Node::Loop(WS(pws1.is_some(), nws1.is_some()), + var, iter, block, + WS(pws2.is_some(), pws2.is_some()))) +)); + +named!(block_extends, do_parse!( + ws!(tag_s!("extends")) >> + name: ws!(expr_str_lit) >> + (Node::Extends(name)) +)); + +named!(block_block, do_parse!( + pws1: opt!(tag_s!("-")) >> + ws!(tag_s!("block")) >> + name: ws!(identifier) >> + nws1: opt!(tag_s!("-")) >> + tag_s!("%}") >> + contents: parse_template >> + tag_s!("{%") >> + pws2: opt!(tag_s!("-")) >> + ws!(tag_s!("endblock")) >> + opt!(ws!(tag_s!(name))) >> + nws2: opt!(tag_s!("-")) >> + (Node::BlockDef(WS(pws1.is_some(), nws1.is_some()), + name, contents, + WS(pws2.is_some(), pws2.is_some()))) +)); + +named!(block_include, do_parse!( + pws: opt!(tag_s!("-")) >> + ws!(tag_s!("include")) >> + name: ws!(expr_str_lit) >> + nws: opt!(tag_s!("-")) >> + (Node::Include(WS(pws.is_some(), nws.is_some()), match name { + Expr::StrLit(s) => s, + _ => panic!("include path must be a string literal"), + })) +)); + +named!(block_macro, do_parse!( + pws1: opt!(tag_s!("-")) >> + ws!(tag_s!("macro")) >> + name: ws!(identifier) >> + params: ws!(parameters) >> + nws1: opt!(tag_s!("-")) >> + tag_s!("%}") >> + contents: parse_template >> + tag_s!("{%") >> + pws2: opt!(tag_s!("-")) >> + ws!(tag_s!("endmacro")) >> + nws2: opt!(tag_s!("-")) >> + (Node::Macro( + WS(pws1.is_some(), nws1.is_some()), + name, + params, + contents, + WS(pws2.is_some(), nws2.is_some()) + )) +)); + +named!(block_node, do_parse!( + tag_s!("{%") >> + contents: alt!( + block_call | + block_let | + block_if | + block_for | + block_extends | + block_include | + block_block | + block_macro + ) >> + tag_s!("%}") >> + (contents) +)); + +named!(block_comment, do_parse!( + tag_s!("{#") >> + take_until_s!("#}") >> + tag_s!("#}") >> + (Node::Comment()) +)); + +named!(parse_template>>, many0!(alt!( + take_content | + block_comment | + expr_node | + block_node +))); + +pub fn parse(src: &str) -> Vec { + match parse_template(src.as_bytes()) { + IResult::Done(left, res) => { + if left.len() > 0 { + let s = str::from_utf8(left).unwrap(); + panic!("unable to parse template:\n\n{:?}", s); + } else { + res + } + }, + IResult::Error(err) => panic!("problems parsing template source: {}", err), + IResult::Incomplete(_) => panic!("parsing incomplete"), + } +} + +#[cfg(test)] +mod tests { + fn check_ws_split(s: &str, res: &(&str, &str, &str)) { + let node = super::split_ws_parts(s.as_bytes()); + match node { + super::Node::Lit(lws, s, rws) => { + assert_eq!(lws, res.0); + assert_eq!(s, res.1); + assert_eq!(rws, res.2); + }, + _ => { panic!("fail"); }, + } + } + #[test] + fn test_ws_splitter() { + check_ws_split("", &("", "", "")); + check_ws_split("a", &("", "a", "")); + check_ws_split("\ta", &("\t", "a", "")); + check_ws_split("b\n", &("", "b", "\n")); + check_ws_split(" \t\r\n", &(" \t\r\n", "", "")); + } + #[test] + #[should_panic] + fn test_invalid_block() { + super::parse("{% extend \"blah\" %}"); + } +} diff --git a/askama_shared/src/path.rs b/askama_shared/src/path.rs new file mode 100644 index 0000000..86bf6d7 --- /dev/null +++ b/askama_shared/src/path.rs @@ -0,0 +1,85 @@ +use std::env; +use std::fs::File; +use std::io::Read; +use std::path::{Path, PathBuf}; + +pub fn get_template_source(tpl_path: &Path) -> String { + let mut path = template_dir(); + path.push(tpl_path); + let mut f = match File::open(&path) { + Err(_) => { + let msg = format!("unable to open template file '{}'", + &path.to_str().unwrap()); + panic!(msg); + }, + Ok(f) => f, + }; + let mut s = String::new(); + f.read_to_string(&mut s).unwrap(); + if s.ends_with('\n') { + let _ = s.pop(); + } + s +} + +pub fn find_template_from_path<'a>(path: &str, start_at: Option<&Path>) -> PathBuf { + let root = template_dir(); + if let Some(rel) = start_at { + let mut fs_rel_path = root.clone(); + fs_rel_path.push(rel); + fs_rel_path = fs_rel_path.with_file_name(path); + if fs_rel_path.exists() { + return fs_rel_path.strip_prefix(&root).unwrap().to_owned(); + } + } + + let mut fs_abs_path = root.clone(); + let path = Path::new(path); + fs_abs_path.push(Path::new(path)); + if fs_abs_path.exists() { + path.to_owned() + } else { + panic!(format!("template '{:?}' not found", path.to_str())); + } +} + +pub fn template_dir() -> PathBuf { + let mut path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + path.push("templates"); + path +} + +#[cfg(test)] +mod tests { + use super::{find_template_from_path, get_template_source}; + use super::Path; + + #[test] + fn get_source() { + assert_eq!(get_template_source(Path::new("sub/b.html")), "bar"); + } + + #[test] + fn find_absolute() { + let path = find_template_from_path("sub/b.html", Some(Path::new("a.html"))); + assert_eq!(path, Path::new("sub/b.html")); + } + + #[test] + #[should_panic] + fn find_relative_nonexistent() { + find_template_from_path("b.html", Some(Path::new("a.html"))); + } + + #[test] + fn find_relative() { + let path = find_template_from_path("c.html", Some(Path::new("sub/b.html"))); + assert_eq!(path, Path::new("sub/c.html")); + } + + #[test] + fn find_relative_sub() { + let path = find_template_from_path("sub1/d.html", Some(Path::new("sub/b.html"))); + assert_eq!(path, Path::new("sub/sub1/d.html")); + } +} diff --git a/askama_shared/templates/a.html b/askama_shared/templates/a.html new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/askama_shared/templates/a.html @@ -0,0 +1 @@ +foo diff --git a/askama_shared/templates/sub/b.html b/askama_shared/templates/sub/b.html new file mode 100644 index 0000000..5716ca5 --- /dev/null +++ b/askama_shared/templates/sub/b.html @@ -0,0 +1 @@ +bar diff --git a/askama_shared/templates/sub/c.html b/askama_shared/templates/sub/c.html new file mode 100644 index 0000000..7601807 --- /dev/null +++ b/askama_shared/templates/sub/c.html @@ -0,0 +1 @@ +baz diff --git a/askama_shared/templates/sub/sub1/d.html b/askama_shared/templates/sub/sub1/d.html new file mode 100644 index 0000000..fa11a6a --- /dev/null +++ b/askama_shared/templates/sub/sub1/d.html @@ -0,0 +1 @@ +echo -- cgit