From 664398b225fe916cc0b2b74047e8aea060ea9214 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 6 Mar 2017 22:40:04 +0100 Subject: Hide askama_derive dependency inside askama (fixes #2) --- Cargo.lock | 6 +- README.md | 3 +- askama/Cargo.toml | 3 +- askama/src/generator.rs | 452 ----------------------------------------- askama/src/lib.rs | 95 ++++----- askama/src/parser.rs | 349 ------------------------------- askama/src/path.rs | 49 ----- askama_derive/Cargo.toml | 2 +- askama_derive/src/generator.rs | 452 +++++++++++++++++++++++++++++++++++++++++ askama_derive/src/lib.rs | 73 ++++++- askama_derive/src/parser.rs | 349 +++++++++++++++++++++++++++++++ askama_derive/src/path.rs | 19 ++ testing/Cargo.toml | 1 - testing/tests/filters.rs | 1 - testing/tests/inheritance.rs | 3 +- testing/tests/operators.rs | 3 +- testing/tests/simple.rs | 1 - 17 files changed, 932 insertions(+), 929 deletions(-) delete mode 100644 askama/src/generator.rs delete mode 100644 askama/src/parser.rs delete mode 100644 askama/src/path.rs create mode 100644 askama_derive/src/generator.rs create mode 100644 askama_derive/src/parser.rs create mode 100644 askama_derive/src/path.rs diff --git a/Cargo.lock b/Cargo.lock index 9505659..98d0dbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,22 +3,20 @@ name = "askama_testing" version = "0.1.0" dependencies = [ "askama 0.2.1", - "askama_derive 0.2.1", ] [[package]] name = "askama" version = "0.2.1" dependencies = [ - "nom 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.11.8 (registry+https://github.com/rust-lang/crates.io-index)", + "askama_derive 0.2.1", ] [[package]] name = "askama_derive" version = "0.2.1" dependencies = [ - "askama 0.2.1", + "nom 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.11.8 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/README.md b/README.md index fbc80eb..ffcd40d 100644 --- a/README.md +++ b/README.md @@ -79,8 +79,7 @@ In any Rust file inside your crate, add the following: ```rust #[macro_use] -extern crate askama_derive; // for the custom derive implementation -extern crate askama; // for the Template trait +extern crate askama; // for the Template trait and custom derive macro use askama::Template; // bring trait in scope diff --git a/askama/Cargo.toml b/askama/Cargo.toml index d17aa4e..4ea7a09 100644 --- a/askama/Cargo.toml +++ b/askama/Cargo.toml @@ -16,5 +16,4 @@ readme = "../README.md" travis-ci = { repository = "djc/askama" } [dependencies] -nom = "2.1" -syn = "0.11" +askama_derive = { path = "../askama_derive", version = "0.2.1" } diff --git a/askama/src/generator.rs b/askama/src/generator.rs deleted file mode 100644 index 16209cd..0000000 --- a/askama/src/generator.rs +++ /dev/null @@ -1,452 +0,0 @@ -use parser::{Cond, Expr, Node, Target, WS}; -use std::str; -use std::collections::HashSet; -use syn; - -fn annotations(generics: &syn::Generics) -> String { - if generics.lifetimes.len() < 1 { - return String::new(); - } - let mut res = String::new(); - res.push('<'); - for lt in &generics.lifetimes { - res.push_str(lt.lifetime.ident.as_ref()); - } - res.push('>'); - res -} - -fn path_as_identifier(s: &str) -> String { - let mut res = String::new(); - for c in s.chars() { - if c.is_alphanumeric() { - res.push(c); - } else { - res.push_str(&format!("{:x}", c as u32)); - } - } - res -} - -struct Generator<'a> { - buf: String, - indent: u8, - start: bool, - locals: HashSet, - next_ws: Option<&'a str>, - skip_ws: bool, - loop_vars: bool, - loop_index: u8, -} - -impl<'a> Generator<'a> { - - fn new() -> Generator<'a> { - Generator { - buf: String::new(), - indent: 0, - start: true, - locals: HashSet::new(), - next_ws: None, - skip_ws: false, - loop_vars: false, - loop_index: 0, - } - } - - 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; - } - - 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({:#?}).unwrap();", - 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); - } - - 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_indexes[_loop_cur]"); - if attr == "index" { - return; - } else if attr == "index0" { - self.write(" - 1"); - 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 { - self.write(&format!("askama::filters::{}(&", name)); - } - - for (i, arg) in args.iter().enumerate() { - if i > 0 { - self.write(", &"); - } - self.visit_expr(arg); - } - 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_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::Call(ref obj, method, ref args) => - self.visit_call(obj, method, args), - } - } - - fn visit_target_single(&mut self, name: &str) -> Vec { - vec![name.to_string()] - } - - fn visit_target(&mut self, target: &Target) -> Vec { - match *target { - Target::Name(s) => { self.visit_target_single(s) }, - } - } - - 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({:#?}).unwrap();", - lws)); - } - } - if !val.is_empty() { - self.writeln(&format!("writer.write_str({:#?}).unwrap();", 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(")).unwrap();"); - } - - 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.handle(nodes); - } - self.handle_ws(ws); - self.writeln("}"); - } - - fn write_loop(&mut self, ws1: &WS, var: &Target, iter: &Expr, - body: &'a [Node], ws2: &WS) { - - self.handle_ws(ws1); - if !self.loop_vars { - self.writeln("let mut _loop_indexes = Vec::new();"); - self.writeln("let mut _loop_cur = 0;"); - self.loop_vars = true; - } - - self.writeln("_loop_indexes.push(0);"); - let cur_index = self.loop_index; - self.loop_index += 1; - self.writeln(&format!("_loop_cur = {};", cur_index)); - self.write("for "); - let targets = self.visit_target(var); - for name in &targets { - self.locals.insert(name.clone()); - self.write(name); - } - self.write(" in &"); - self.visit_expr(iter); - self.writeln(" {"); - - self.writeln("_loop_indexes[_loop_cur] += 1;"); - self.handle(body); - self.handle_ws(ws2); - self.writeln("}"); - self.loop_index -= 1; - self.writeln("_loop_indexes.pop();"); - for name in &targets { - self.locals.remove(name); - } - } - - fn write_block(&mut self, ws1: &WS, name: &str, ws2: &WS) { - self.flush_ws(ws1); - self.writeln(&format!("timpl.render_block_{}_to(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_{}_to(&self, writer: &mut std::fmt::Write) {{", - name)); - self.prepare_ws(ws1); - self.handle(nodes); - self.flush_ws(ws2); - self.writeln("}"); - } - - 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::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::Extends(_) => { - panic!("no extends or block definition allowed in content"); - }, - } - } - } - - fn impl_template(&mut self, ast: &syn::DeriveInput, nodes: &'a [Node]) { - let anno = annotations(&ast.generics); - self.writeln(&format!("impl{} askama::Template for {}{} {{", - anno, ast.ident.as_ref(), anno)); - - self.writeln("fn render_to(&self, writer: &mut std::fmt::Write) {"); - self.handle(nodes); - self.flush_ws(&WS(false, false)); - self.writeln("}"); - self.writeln("}"); - } - - fn impl_trait(&mut self, ast: &syn::DeriveInput, base: &str, - blocks: &'a [Node], nodes: Option<&'a [Node]>) { - let anno = annotations(&ast.generics); - self.writeln(&format!("impl{} TraitFrom{} for {}{} {{", - anno, path_as_identifier(base), - ast.ident.as_ref(), anno)); - self.handle(blocks); - - self.writeln("#[allow(unused_variables)]"); - let trait_name = format!("TraitFrom{}", path_as_identifier(base)); - self.writeln(&format!( - "fn render_trait_to(&self, timpl: &{}, writer: &mut std::fmt::Write) {{", - trait_name)); - - if let Some(nodes) = nodes { - self.handle(nodes); - self.flush_ws(&WS(false, false)); - } else { - self.writeln("self._parent.render_trait_to(self, writer);"); - } - - self.writeln("}"); - self.flush_ws(&WS(false, false)); - self.writeln("}"); - } - - fn impl_template_for_trait(&mut self, ast: &syn::DeriveInput, derived: bool) { - let anno = annotations(&ast.generics); - self.writeln(&format!("impl{} askama::Template for {}{} {{", - anno, ast.ident.as_ref(), anno)); - self.writeln("fn render_to(&self, writer: &mut std::fmt::Write) {"); - if derived { - self.writeln("self._parent.render_trait_to(self, writer);"); - } else { - self.writeln("self.render_trait_to(self, writer);"); - } - self.writeln("}"); - self.writeln("}"); - } - - fn define_trait(&mut self, path: &str, block_names: &[&str]) { - let trait_name = format!("TraitFrom{}", path_as_identifier(path)); - self.writeln(&format!("trait {} {{", &trait_name)); - - for bname in block_names { - self.writeln(&format!( - "fn render_block_{}_to(&self, writer: &mut std::fmt::Write);", - bname)); - } - self.writeln(&format!( - "fn render_trait_to(&self, timpl: &{}, writer: &mut std::fmt::Write);", - trait_name)); - - self.writeln("}"); - } - - fn result(self) -> String { - self.buf - } - -} - -pub fn generate(ast: &syn::DeriveInput, path: &str, mut nodes: Vec) -> String { - let mut base: Option = None; - let mut blocks = Vec::new(); - let mut block_names = Vec::new(); - let mut content = Vec::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); - block_names.push(name); - content.push(Node::Block(ws1, name, ws2)); - }, - _ => { content.push(n); }, - } - } - - let mut gen = Generator::new(); - if !blocks.is_empty() { - if base.is_none() { - gen.define_trait(path, &block_names); - } - let tmpl_path = match base { - Some(Expr::StrLit(base_path)) => { base_path }, - _ => { path }, - }; - let trait_nodes = if base.is_none() { Some(&content[..]) } else { None }; - gen.impl_trait(ast, tmpl_path, &blocks, trait_nodes); - gen.impl_template_for_trait(ast, base.is_some()); - } else { - gen.impl_template(ast, &content); - } - gen.result() -} diff --git a/askama/src/lib.rs b/askama/src/lib.rs index 8cfe79c..dff6a81 100644 --- a/askama/src/lib.rs +++ b/askama/src/lib.rs @@ -182,8 +182,12 @@ //! Expressions can be grouped using parentheses. #[macro_use] -extern crate nom; -extern crate syn; +extern crate askama_derive; + +use std::env; +use std::fs::{self, DirEntry}; +use std::io; +use std::path::{Path, PathBuf}; /// Main `Template` trait; implementations are generally derived pub trait Template { @@ -197,73 +201,42 @@ pub trait Template { } } -mod generator; -mod parser; -mod path; - pub mod filters; -pub use path::rerun_if_templates_changed; +pub use askama_derive::*; -// Holds metadata for the template, based on the `template()` attribute. -struct TemplateMeta { - path: String, - print: String, +// Duplicates askama_derive::path::template_dir() +fn template_dir() -> PathBuf { + let mut path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + path.push("templates"); + path } -// Returns a `TemplateMeta` based on the `template()` attribute data found -// in the parsed struct or enum. Will panic if it does not find the required -// template path, or if the `print` key has an unexpected value. -fn get_template_meta(ast: &syn::DeriveInput) -> TemplateMeta { - let mut path = None; - let mut print = "none".to_string(); - let attr = ast.attrs.iter().find(|a| a.name() == "template").unwrap(); - if let syn::MetaItem::List(_, ref inner) = attr.value { - for nm_item in inner { - if let syn::NestedMetaItem::MetaItem(ref item) = *nm_item { - if let syn::MetaItem::NameValue(ref key, ref val) = *item { - match key.as_ref() { - "path" => if let syn::Lit::Str(ref s, _) = *val { - path = Some(s.clone()); - } else { - panic!("template path must be string literal"); - }, - "print" => if let syn::Lit::Str(ref s, _) = *val { - print = s.clone(); - } else { - panic!("print value must be string literal"); - }, - _ => { panic!("unsupported annotation key found") } - } - } +fn visit_dirs(dir: &Path, cb: &Fn(&DirEntry)) -> io::Result<()> { + if dir.is_dir() { + for entry in try!(fs::read_dir(dir)) { + let entry = try!(entry); + let path = entry.path(); + if path.is_dir() { + try!(visit_dirs(&path, cb)); + } else { + cb(&entry); } } } - if path.is_none() { - panic!("template path not found in struct attributes"); - } - TemplateMeta { path: path.unwrap(), print: print } + Ok(()) } -/// Takes a `syn::DeriveInput` and generates source code for it +/// Build script helper to rebuild crates if contained templates have changed /// -/// Reads the metadata from the `template()` attribute to get the template -/// metadata, then fetches the source from the filesystem. The source is -/// parsed, and the parse tree is fed to the code generator. Will print -/// the parse tree and/or generated source according to the `print` key's -/// value as passed to the `template()` attribute. -pub fn build_template(ast: &syn::DeriveInput) -> String { - let meta = get_template_meta(ast); - let mut src = path::get_template_source(&meta.path); - if src.ends_with('\n') { - let _ = src.pop(); - } - let nodes = parser::parse(&src); - if meta.print == "ast" || meta.print == "all" { - println!("{:?}", nodes); - } - let code = generator::generate(ast, &meta.path, nodes); - if meta.print == "code" || meta.print == "all" { - println!("{}", code); - } - code +/// Iterates over all files in the template dir (`templates` in +/// `CARGO_MANIFEST_DIR`) and writes a `cargo:rerun-if-changed=` line for each +/// of them to stdout. +/// +/// This helper method can be used in build scripts (`build.rs`) in crates +/// 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| { + println!("cargo:rerun-if-changed={}", e.path().to_str().unwrap()); + }).unwrap(); } diff --git a/askama/src/parser.rs b/askama/src/parser.rs deleted file mode 100644 index ce13add..0000000 --- a/askama/src/parser.rs +++ /dev/null @@ -1,349 +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>), - Call(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>), - 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), -} - -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()) -} - -fn take_content(i: &[u8]) -> IResult<&[u8], Node> { - if i.len() < 1 || i[0] == b'{' { - return IResult::Error(error_position!(nom::ErrorKind::TakeUntil, i)); - } - for (j, c) in i.iter().enumerate() { - if *c == b'{' { - if i.len() < j + 2 { - return IResult::Done(&i[..0], split_ws_parts(&i[..])); - } else if i[j + 1] == b'{' || i[j + 1] == b'%' || i[j + 1] == b'#' { - return IResult::Done(&i[j..], split_ws_parts(&i[..j])); - } - } - } - IResult::Done(&i[..0], split_ws_parts(&i[..])) -} - -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>>, opt!( - do_parse!( - tag_s!("(") >> - arg0: ws!(opt!(expr_any)) >> - args: many0!(do_parse!( - tag_s!(",") >> - argn: ws!(expr_any) >> - (argn) - )) >> - tag_s!(")") >> - ({ - let mut res = Vec::new(); - if arg0.is_some() { - res.push(arg0.unwrap()); - } - res.extend(args); - res - }) - ) -)); - -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!(expr_attr, alt!( - do_parse!( - obj: expr_single >> - tag_s!(".") >> - attr: identifier >> - args: arguments >> - (if args.is_some() { - Expr::Call(Box::new(obj), attr, args.unwrap()) - } else { - Expr::Attr(Box::new(obj), attr) - }) - ) | expr_single -)); - -named!(filter<(&str, Option>)>, do_parse!( - tag_s!("|") >> - fname: identifier >> - args: 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!(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!( - tag_s!("{%") >> - 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!("-")) >> - 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_for, do_parse!( - tag_s!("{%") >> - 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!("-")) >> - 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!( - tag_s!("{%") >> - ws!(tag_s!("extends")) >> - name: ws!(expr_str_lit) >> - tag_s!("%}") >> - (Node::Extends(name)) -)); - -named!(block_block, do_parse!( - tag_s!("{%") >> - 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")) >> - nws2: opt!(tag_s!("-")) >> - tag_s!("%}") >> - (Node::BlockDef(WS(pws1.is_some(), nws1.is_some()), - name, contents, - WS(pws2.is_some(), pws2.is_some()))) -)); - -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_if | - block_for | - block_extends | - block_block -))); - -pub fn parse(src: &str) -> Vec { - match parse_template(src.as_bytes()) { - IResult::Done(_, res) => 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", "", "")); - } -} diff --git a/askama/src/path.rs b/askama/src/path.rs deleted file mode 100644 index fb030e5..0000000 --- a/askama/src/path.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::env; -use std::fs::{self, DirEntry, File}; -use std::io::{self, Read}; -use std::path::{Path, PathBuf}; - -fn template_dir() -> PathBuf { - let mut path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - path.push("templates"); - path -} - -pub fn get_template_source(tpl_file: &str) -> String { - let mut path = template_dir(); - path.push(Path::new(tpl_file)); - let mut f = File::open(path).unwrap(); - let mut s = String::new(); - f.read_to_string(&mut s).unwrap(); - s -} - -fn visit_dirs(dir: &Path, cb: &Fn(&DirEntry)) -> io::Result<()> { - if dir.is_dir() { - for entry in try!(fs::read_dir(dir)) { - let entry = try!(entry); - let path = entry.path(); - if path.is_dir() { - try!(visit_dirs(&path, cb)); - } else { - cb(&entry); - } - } - } - Ok(()) -} - -/// Build script helper to rebuild crates if contained templates have changed -/// -/// Iterates over all files in the template dir (`templates` in -/// `CARGO_MANIFEST_DIR`) and writes a `cargo:rerun-if-changed=` line for each -/// of them to stdout. -/// -/// This helper method can be used in build scripts (`build.rs`) in crates -/// 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| { - println!("cargo:rerun-if-changed={}", e.path().to_str().unwrap()); - }).unwrap(); -} diff --git a/askama_derive/Cargo.toml b/askama_derive/Cargo.toml index 5018beb..605c40f 100644 --- a/askama_derive/Cargo.toml +++ b/askama_derive/Cargo.toml @@ -12,5 +12,5 @@ workspace = ".." proc-macro = true [dependencies] -askama = { path = "../askama", version = "0.2.1" } +nom = "2.1" syn = "0.11" diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs new file mode 100644 index 0000000..16209cd --- /dev/null +++ b/askama_derive/src/generator.rs @@ -0,0 +1,452 @@ +use parser::{Cond, Expr, Node, Target, WS}; +use std::str; +use std::collections::HashSet; +use syn; + +fn annotations(generics: &syn::Generics) -> String { + if generics.lifetimes.len() < 1 { + return String::new(); + } + let mut res = String::new(); + res.push('<'); + for lt in &generics.lifetimes { + res.push_str(lt.lifetime.ident.as_ref()); + } + res.push('>'); + res +} + +fn path_as_identifier(s: &str) -> String { + let mut res = String::new(); + for c in s.chars() { + if c.is_alphanumeric() { + res.push(c); + } else { + res.push_str(&format!("{:x}", c as u32)); + } + } + res +} + +struct Generator<'a> { + buf: String, + indent: u8, + start: bool, + locals: HashSet, + next_ws: Option<&'a str>, + skip_ws: bool, + loop_vars: bool, + loop_index: u8, +} + +impl<'a> Generator<'a> { + + fn new() -> Generator<'a> { + Generator { + buf: String::new(), + indent: 0, + start: true, + locals: HashSet::new(), + next_ws: None, + skip_ws: false, + loop_vars: false, + loop_index: 0, + } + } + + 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; + } + + 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({:#?}).unwrap();", + 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); + } + + 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_indexes[_loop_cur]"); + if attr == "index" { + return; + } else if attr == "index0" { + self.write(" - 1"); + 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 { + self.write(&format!("askama::filters::{}(&", name)); + } + + for (i, arg) in args.iter().enumerate() { + if i > 0 { + self.write(", &"); + } + self.visit_expr(arg); + } + 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_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::Call(ref obj, method, ref args) => + self.visit_call(obj, method, args), + } + } + + fn visit_target_single(&mut self, name: &str) -> Vec { + vec![name.to_string()] + } + + fn visit_target(&mut self, target: &Target) -> Vec { + match *target { + Target::Name(s) => { self.visit_target_single(s) }, + } + } + + 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({:#?}).unwrap();", + lws)); + } + } + if !val.is_empty() { + self.writeln(&format!("writer.write_str({:#?}).unwrap();", 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(")).unwrap();"); + } + + 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.handle(nodes); + } + self.handle_ws(ws); + self.writeln("}"); + } + + fn write_loop(&mut self, ws1: &WS, var: &Target, iter: &Expr, + body: &'a [Node], ws2: &WS) { + + self.handle_ws(ws1); + if !self.loop_vars { + self.writeln("let mut _loop_indexes = Vec::new();"); + self.writeln("let mut _loop_cur = 0;"); + self.loop_vars = true; + } + + self.writeln("_loop_indexes.push(0);"); + let cur_index = self.loop_index; + self.loop_index += 1; + self.writeln(&format!("_loop_cur = {};", cur_index)); + self.write("for "); + let targets = self.visit_target(var); + for name in &targets { + self.locals.insert(name.clone()); + self.write(name); + } + self.write(" in &"); + self.visit_expr(iter); + self.writeln(" {"); + + self.writeln("_loop_indexes[_loop_cur] += 1;"); + self.handle(body); + self.handle_ws(ws2); + self.writeln("}"); + self.loop_index -= 1; + self.writeln("_loop_indexes.pop();"); + for name in &targets { + self.locals.remove(name); + } + } + + fn write_block(&mut self, ws1: &WS, name: &str, ws2: &WS) { + self.flush_ws(ws1); + self.writeln(&format!("timpl.render_block_{}_to(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_{}_to(&self, writer: &mut std::fmt::Write) {{", + name)); + self.prepare_ws(ws1); + self.handle(nodes); + self.flush_ws(ws2); + self.writeln("}"); + } + + 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::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::Extends(_) => { + panic!("no extends or block definition allowed in content"); + }, + } + } + } + + fn impl_template(&mut self, ast: &syn::DeriveInput, nodes: &'a [Node]) { + let anno = annotations(&ast.generics); + self.writeln(&format!("impl{} askama::Template for {}{} {{", + anno, ast.ident.as_ref(), anno)); + + self.writeln("fn render_to(&self, writer: &mut std::fmt::Write) {"); + self.handle(nodes); + self.flush_ws(&WS(false, false)); + self.writeln("}"); + self.writeln("}"); + } + + fn impl_trait(&mut self, ast: &syn::DeriveInput, base: &str, + blocks: &'a [Node], nodes: Option<&'a [Node]>) { + let anno = annotations(&ast.generics); + self.writeln(&format!("impl{} TraitFrom{} for {}{} {{", + anno, path_as_identifier(base), + ast.ident.as_ref(), anno)); + self.handle(blocks); + + self.writeln("#[allow(unused_variables)]"); + let trait_name = format!("TraitFrom{}", path_as_identifier(base)); + self.writeln(&format!( + "fn render_trait_to(&self, timpl: &{}, writer: &mut std::fmt::Write) {{", + trait_name)); + + if let Some(nodes) = nodes { + self.handle(nodes); + self.flush_ws(&WS(false, false)); + } else { + self.writeln("self._parent.render_trait_to(self, writer);"); + } + + self.writeln("}"); + self.flush_ws(&WS(false, false)); + self.writeln("}"); + } + + fn impl_template_for_trait(&mut self, ast: &syn::DeriveInput, derived: bool) { + let anno = annotations(&ast.generics); + self.writeln(&format!("impl{} askama::Template for {}{} {{", + anno, ast.ident.as_ref(), anno)); + self.writeln("fn render_to(&self, writer: &mut std::fmt::Write) {"); + if derived { + self.writeln("self._parent.render_trait_to(self, writer);"); + } else { + self.writeln("self.render_trait_to(self, writer);"); + } + self.writeln("}"); + self.writeln("}"); + } + + fn define_trait(&mut self, path: &str, block_names: &[&str]) { + let trait_name = format!("TraitFrom{}", path_as_identifier(path)); + self.writeln(&format!("trait {} {{", &trait_name)); + + for bname in block_names { + self.writeln(&format!( + "fn render_block_{}_to(&self, writer: &mut std::fmt::Write);", + bname)); + } + self.writeln(&format!( + "fn render_trait_to(&self, timpl: &{}, writer: &mut std::fmt::Write);", + trait_name)); + + self.writeln("}"); + } + + fn result(self) -> String { + self.buf + } + +} + +pub fn generate(ast: &syn::DeriveInput, path: &str, mut nodes: Vec) -> String { + let mut base: Option = None; + let mut blocks = Vec::new(); + let mut block_names = Vec::new(); + let mut content = Vec::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); + block_names.push(name); + content.push(Node::Block(ws1, name, ws2)); + }, + _ => { content.push(n); }, + } + } + + let mut gen = Generator::new(); + if !blocks.is_empty() { + if base.is_none() { + gen.define_trait(path, &block_names); + } + let tmpl_path = match base { + Some(Expr::StrLit(base_path)) => { base_path }, + _ => { path }, + }; + let trait_nodes = if base.is_none() { Some(&content[..]) } else { None }; + gen.impl_trait(ast, tmpl_path, &blocks, trait_nodes); + gen.impl_template_for_trait(ast, base.is_some()); + } else { + gen.impl_template(ast, &content); + } + gen.result() +} diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index 4b96e17..752978e 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -1,9 +1,78 @@ -extern crate askama; +#[macro_use] +extern crate nom; extern crate proc_macro; extern crate syn; use proc_macro::TokenStream; +mod generator; +mod parser; +mod path; + +// Holds metadata for the template, based on the `template()` attribute. +struct TemplateMeta { + path: String, + print: String, +} + +// Returns a `TemplateMeta` based on the `template()` attribute data found +// in the parsed struct or enum. Will panic if it does not find the required +// template path, or if the `print` key has an unexpected value. +fn get_template_meta(ast: &syn::DeriveInput) -> TemplateMeta { + let mut path = None; + let mut print = "none".to_string(); + let attr = ast.attrs.iter().find(|a| a.name() == "template").unwrap(); + if let syn::MetaItem::List(_, ref inner) = attr.value { + for nm_item in inner { + if let syn::NestedMetaItem::MetaItem(ref item) = *nm_item { + if let syn::MetaItem::NameValue(ref key, ref val) = *item { + match key.as_ref() { + "path" => if let syn::Lit::Str(ref s, _) = *val { + path = Some(s.clone()); + } else { + panic!("template path must be string literal"); + }, + "print" => if let syn::Lit::Str(ref s, _) = *val { + print = s.clone(); + } else { + panic!("print value must be string literal"); + }, + _ => { panic!("unsupported annotation key found") } + } + } + } + } + } + if path.is_none() { + panic!("template path not found in struct attributes"); + } + TemplateMeta { path: path.unwrap(), print: print } +} + +/// Takes a `syn::DeriveInput` and generates source code for it +/// +/// Reads the metadata from the `template()` attribute to get the template +/// metadata, then fetches the source from the filesystem. The source is +/// parsed, and the parse tree is fed to the code generator. Will print +/// the parse tree and/or generated source according to the `print` key's +/// value as passed to the `template()` attribute. +fn build_template(ast: &syn::DeriveInput) -> String { + let meta = get_template_meta(ast); + let mut src = path::get_template_source(&meta.path); + if src.ends_with('\n') { + let _ = src.pop(); + } + let nodes = parser::parse(&src); + if meta.print == "ast" || meta.print == "all" { + println!("{:?}", nodes); + } + let code = generator::generate(ast, &meta.path, nodes); + if meta.print == "code" || meta.print == "all" { + println!("{}", code); + } + code +} + #[proc_macro_derive(Template, attributes(template))] pub fn derive_template(input: TokenStream) -> TokenStream { let ast = syn::parse_derive_input(&input.to_string()).unwrap(); @@ -11,5 +80,5 @@ pub fn derive_template(input: TokenStream) -> TokenStream { syn::Body::Struct(ref data) => data, _ => panic!("#[derive(Template)] can only be used with structs"), }; - askama::build_template(&ast).parse().unwrap() + build_template(&ast).parse().unwrap() } diff --git a/askama_derive/src/parser.rs b/askama_derive/src/parser.rs new file mode 100644 index 0000000..ce13add --- /dev/null +++ b/askama_derive/src/parser.rs @@ -0,0 +1,349 @@ +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>), + Call(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>), + 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), +} + +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()) +} + +fn take_content(i: &[u8]) -> IResult<&[u8], Node> { + if i.len() < 1 || i[0] == b'{' { + return IResult::Error(error_position!(nom::ErrorKind::TakeUntil, i)); + } + for (j, c) in i.iter().enumerate() { + if *c == b'{' { + if i.len() < j + 2 { + return IResult::Done(&i[..0], split_ws_parts(&i[..])); + } else if i[j + 1] == b'{' || i[j + 1] == b'%' || i[j + 1] == b'#' { + return IResult::Done(&i[j..], split_ws_parts(&i[..j])); + } + } + } + IResult::Done(&i[..0], split_ws_parts(&i[..])) +} + +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>>, opt!( + do_parse!( + tag_s!("(") >> + arg0: ws!(opt!(expr_any)) >> + args: many0!(do_parse!( + tag_s!(",") >> + argn: ws!(expr_any) >> + (argn) + )) >> + tag_s!(")") >> + ({ + let mut res = Vec::new(); + if arg0.is_some() { + res.push(arg0.unwrap()); + } + res.extend(args); + res + }) + ) +)); + +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!(expr_attr, alt!( + do_parse!( + obj: expr_single >> + tag_s!(".") >> + attr: identifier >> + args: arguments >> + (if args.is_some() { + Expr::Call(Box::new(obj), attr, args.unwrap()) + } else { + Expr::Attr(Box::new(obj), attr) + }) + ) | expr_single +)); + +named!(filter<(&str, Option>)>, do_parse!( + tag_s!("|") >> + fname: identifier >> + args: 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!(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!( + tag_s!("{%") >> + 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!("-")) >> + 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_for, do_parse!( + tag_s!("{%") >> + 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!("-")) >> + 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!( + tag_s!("{%") >> + ws!(tag_s!("extends")) >> + name: ws!(expr_str_lit) >> + tag_s!("%}") >> + (Node::Extends(name)) +)); + +named!(block_block, do_parse!( + tag_s!("{%") >> + 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")) >> + nws2: opt!(tag_s!("-")) >> + tag_s!("%}") >> + (Node::BlockDef(WS(pws1.is_some(), nws1.is_some()), + name, contents, + WS(pws2.is_some(), pws2.is_some()))) +)); + +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_if | + block_for | + block_extends | + block_block +))); + +pub fn parse(src: &str) -> Vec { + match parse_template(src.as_bytes()) { + IResult::Done(_, res) => 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", "", "")); + } +} diff --git a/askama_derive/src/path.rs b/askama_derive/src/path.rs new file mode 100644 index 0000000..96ed8c1 --- /dev/null +++ b/askama_derive/src/path.rs @@ -0,0 +1,19 @@ +use std::env; +use std::fs::File; +use std::io::Read; +use std::path::{Path, PathBuf}; + +fn template_dir() -> PathBuf { + let mut path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + path.push("templates"); + path +} + +pub fn get_template_source(tpl_file: &str) -> String { + let mut path = template_dir(); + path.push(Path::new(tpl_file)); + let mut f = File::open(path).unwrap(); + let mut s = String::new(); + f.read_to_string(&mut s).unwrap(); + s +} diff --git a/testing/Cargo.toml b/testing/Cargo.toml index 8e7b1a7..bb6b534 100644 --- a/testing/Cargo.toml +++ b/testing/Cargo.toml @@ -7,7 +7,6 @@ build = "build.rs" [dependencies] askama = { path = "../askama", version = "*" } -askama_derive = { path = "../askama_derive", version = "*" } [build-dependencies] askama = { path = "../askama", version = "*" } diff --git a/testing/tests/filters.rs b/testing/tests/filters.rs index 5882bae..0b1b7c9 100644 --- a/testing/tests/filters.rs +++ b/testing/tests/filters.rs @@ -1,5 +1,4 @@ #[macro_use] -extern crate askama_derive; extern crate askama; use askama::Template; diff --git a/testing/tests/inheritance.rs b/testing/tests/inheritance.rs index b3add1c..459a5b2 100644 --- a/testing/tests/inheritance.rs +++ b/testing/tests/inheritance.rs @@ -1,6 +1,5 @@ -extern crate askama; #[macro_use] -extern crate askama_derive; +extern crate askama; use askama::Template; diff --git a/testing/tests/operators.rs b/testing/tests/operators.rs index 70d26b1..73b30b1 100644 --- a/testing/tests/operators.rs +++ b/testing/tests/operators.rs @@ -1,6 +1,5 @@ -extern crate askama; #[macro_use] -extern crate askama_derive; +extern crate askama; use askama::Template; diff --git a/testing/tests/simple.rs b/testing/tests/simple.rs index 60ecf05..a53257d 100644 --- a/testing/tests/simple.rs +++ b/testing/tests/simple.rs @@ -1,5 +1,4 @@ #[macro_use] -extern crate askama_derive; extern crate askama; use askama::Template; -- cgit