diff options
author | Dirkjan Ochtman <dirkjan@ochtman.nl> | 2017-01-03 10:01:16 +0100 |
---|---|---|
committer | Dirkjan Ochtman <dirkjan@ochtman.nl> | 2017-01-03 10:01:16 +0100 |
commit | 4c8c773c84a48963e892c72f38f37bcb99b6eb74 (patch) | |
tree | 7ea8dd95e81bd40c1b24bb9143cbe865005b1ae0 /askama_derive | |
parent | 3e7983d7af8d19393507e50778b818f8dcf24b91 (diff) | |
download | askama-4c8c773c84a48963e892c72f38f37bcb99b6eb74.tar.gz askama-4c8c773c84a48963e892c72f38f37bcb99b6eb74.tar.bz2 askama-4c8c773c84a48963e892c72f38f37bcb99b6eb74.zip |
Rename askama_codegen to askama_derive
This appears to be best practice for crates using macros 1.1.
Diffstat (limited to 'askama_derive')
-rw-r--r-- | askama_derive/Cargo.toml | 12 | ||||
-rw-r--r-- | askama_derive/src/generator.rs | 101 | ||||
-rw-r--r-- | askama_derive/src/lib.rs | 72 | ||||
-rw-r--r-- | askama_derive/src/parser.rs | 43 |
4 files changed, 228 insertions, 0 deletions
diff --git a/askama_derive/Cargo.toml b/askama_derive/Cargo.toml new file mode 100644 index 0000000..9f525f4 --- /dev/null +++ b/askama_derive/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "askama_derive" +version = "0.1.0" +authors = ["Dirkjan Ochtman <dirkjan@ochtman.nl>"] + +[dependencies] +syn = "0.10" +quote = "0.3" +nom = "2.0" + +[lib] +proc-macro = true diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs new file mode 100644 index 0000000..b1babce --- /dev/null +++ b/askama_derive/src/generator.rs @@ -0,0 +1,101 @@ +use parser::{Expr, Node}; +use std::str; + +struct Generator { + buf: String, + indent: u8, + start: bool, +} + +impl Generator { + + fn new() -> Generator { + Generator { buf: String::new(), indent: 0, start: true } + } + + fn init(&mut self, name: &str) { + self.write("impl askama::Template for "); + self.write(name); + self.writeln(" {"); + self.indent(); + self.writeln("fn render(&self) -> String {"); + self.indent(); + self.writeln("let mut buf = String::new();"); + } + + 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; + } + self.write(s); + self.buf.push('\n'); + self.start = true; + } + + fn visit_lit(&mut self, s: &[u8]) { + self.write("buf.push_str("); + self.write(&format!("{:#?}", str::from_utf8(s).unwrap())); + self.writeln(");"); + } + + fn visit_var(&mut self, s: &[u8]) { + let var_name = str::from_utf8(s).unwrap(); + let code = format!("std::fmt::Write::write_fmt(\ + &mut buf, format_args!(\"{{}}\", self.{})).unwrap();", var_name); + self.writeln(&code); + } + + fn visit_expr(&mut self, expr: &Expr) { + match expr { + &Expr::Var(s) => self.visit_var(s), + } + } + + fn handle(&mut self, tokens: &Vec<Node>) { + for n in tokens { + match n { + &Node::Lit(val) => { self.visit_lit(val); }, + &Node::Expr(ref val) => { self.visit_expr(&val); }, + } + } + } + + fn finalize(&mut self) { + self.writeln("buf"); + self.dedent(); + self.writeln("}"); + self.dedent(); + self.writeln("}"); + } + + fn result(self) -> String { + self.buf + } + +} + +pub fn generate(ctx_name: &str, tokens: &Vec<Node>) -> String { + let mut gen = Generator::new(); + gen.init(ctx_name); + gen.handle(tokens); + gen.finalize(); + gen.result() +} diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs new file mode 100644 index 0000000..d923e19 --- /dev/null +++ b/askama_derive/src/lib.rs @@ -0,0 +1,72 @@ +#![feature(proc_macro, proc_macro_lib)] + +#[macro_use] +extern crate nom; +extern crate proc_macro; +#[macro_use] +extern crate quote; +extern crate syn; + +mod generator; +mod parser; + +use proc_macro::TokenStream; +use std::fs::File; +use std::io::Read; +use std::path::{Path, PathBuf}; + +fn get_path_from_attrs(attrs: &Vec<syn::Attribute>) -> String { + for attr in attrs { + if attr.name() == "template" { + match attr.value { + syn::MetaItem::List(_, ref inner) => { + match inner[0] { + syn::NestedMetaItem::MetaItem(ref item) => { + match item { + &syn::MetaItem::NameValue(ref key, ref val) => { + assert_eq!(key.as_ref(), "path"); + match val { + &syn::Lit::Str(ref s, _) => { return s.clone(); }, + _ => panic!("template path must be a string"), + } + }, + _ => panic!("template annotation must contain key/value pair"), + } + }, + _ => panic!("template annotation must contain item"), + } + }, + _ => panic!("template annotation must be of List type"), + } + } + } + panic!("template annotation not found"); +} + +fn get_template_source(tpl_file: &str) -> String { + let root = ::std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let mut path = PathBuf::from(root); + path.push("templates"); + 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 +} + +#[proc_macro_derive(Template, attributes(template))] +pub fn derive_template(input: TokenStream) -> TokenStream { + let source = input.to_string(); + + let ast = syn::parse_macro_input(&source).unwrap(); + let _ctx = match ast.body { + syn::Body::Struct(ref data) => data, + _ => panic!("#[derive(Template)] can only be used with structs"), + }; + + let name = &ast.ident; + let path = get_path_from_attrs(&ast.attrs); + let src = get_template_source(&path); + let tokens = parser::parse(&src); + generator::generate(name.as_ref(), &tokens).parse().unwrap() +} diff --git a/askama_derive/src/parser.rs b/askama_derive/src/parser.rs new file mode 100644 index 0000000..5f3f8c5 --- /dev/null +++ b/askama_derive/src/parser.rs @@ -0,0 +1,43 @@ +use nom::{self, IResult}; + +pub enum Expr<'a> { + Var(&'a [u8]), +} + +pub enum Node<'a> { + Lit(&'a [u8]), + Expr(Expr<'a>), +} + +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], Node::Lit(&i[..])); + } else if i[j + 1] == '{' as u8 { + return IResult::Done(&i[j..], Node::Lit(&i[..j])); + } else if i[j + 1] == '%' as u8 { + return IResult::Done(&i[j..], Node::Lit(&i[..j])); + } + } + } + IResult::Done(&i[..0], Node::Lit(&i[..])) +} + +named!(expr_var<Expr>, map!(ws!(nom::alphanumeric), Expr::Var)); + +named!(any_expr<Expr>, delimited!(tag!("{{"), expr_var, tag!("}}"))); + +named!(expr_node<Node>, map!(any_expr, Node::Expr)); + +named!(parse_template< Vec<Node> >, many1!(alt!(take_content | expr_node))); + +pub fn parse<'a>(src: &'a str) -> Vec<Node> { + match parse_template(src.as_bytes()) { + IResult::Done(_, res) => res, + _ => panic!("problems parsing template source"), + } +} |