1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
extern crate askama_shared as shared;
#[macro_use]
extern crate nom;
extern crate proc_macro;
#[macro_use]
extern crate quote;
#[macro_use(parse_quote)]
extern crate syn;

mod input;
mod generator;
mod parser;

use input::Print;
use parser::{Macro, Node};
use proc_macro::TokenStream;
use shared::path;

use std::borrow::Cow;
use std::collections::HashMap;
use std::path::Path;

#[proc_macro_derive(Template, attributes(template))]
pub fn derive_template(input: TokenStream) -> TokenStream {
    let ast: syn::DeriveInput = syn::parse(input).unwrap();
    match ast.data {
        syn::Data::Struct(ref data) => data,
        _ => panic!("#[derive(Template)] can only be used with structs"),
    };
    build_template(&ast).parse().unwrap()
}

/// 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 data = input::TemplateInput::new(ast);
    let nodes = parser::parse(data.source.as_ref());
    let imports = Imports::new(&nodes, &data.path);
    if data.meta.print == Print::Ast || data.meta.print == Print::All {
        println!("{:?}", nodes);
    }
    let code = generator::generate(&data, &nodes, &imports.macro_map());
    if data.meta.print == Print::Code || data.meta.print == Print::All {
        println!("{}", code);
    }
    code
}

struct Imports<'a> {
    sources: HashMap<&'a str, Cow<'a, str>>,
}

impl<'a> Imports<'a> {
    fn new(parent_nodes: &'a [Node], parent_path: &'a Path) -> Imports<'a> {
        let sources = parent_nodes.iter().filter_map(|n| {
            match *n {
                Node::Import(_, import_path, scope) => {
                    let path = path::find_template_from_path(import_path, Some(parent_path));
                    let src = path::get_template_source(&path);
                    Some((scope, Cow::Owned(src)))
                },
                _ => None,
            }
        }).collect();
        Imports { sources }
    }

    fn macro_map(&'a self) -> HashMap<(&'a str, &'a str), Macro<'a>> {
        let mut macro_map = HashMap::new();
        for (scope, s) in &self.sources {
            for n in parser::parse(s.as_ref()) {
                match n {
                    Node::Macro(name, m) => macro_map.insert((*scope, name), m),
                    _ => None,
                };
            }
        }
        macro_map
    }
}