aboutsummaryrefslogtreecommitdiffstats
path: root/askama_derive/src/lib.rs
blob: 0564ccca14f5b60fb85bbbb8ee2863b91e800f41 (plain) (blame)
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#[macro_use]
extern crate nom;
extern crate proc_macro;
extern crate quote;
extern crate syn;

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();
    match ast.body {
        syn::Body::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 meta = get_template_meta(ast);
    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);
            (path, Cow::Owned(src))
        },
    };
    let nodes = parser::parse(src.as_ref());
    if meta.print == Print::Ast || meta.print == Print::All {
        println!("{:?}", nodes);
    }
    let code = generator::generate(ast, &path, nodes);
    if meta.print == Print::Code || meta.print == Print::All {
        println!("{}", code);
    }
    code
}

// 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<'a>(ast: &'a syn::DeriveInput) -> TemplateMeta<'a> {
    let attr = ast.attrs.iter().find(|a| a.name() == "template");
    if attr.is_none() {
        let msg = format!("'template' attribute not found on struct '{}'",
                          ast.ident.as_ref());
        panic!(msg);
    }

    let attr = attr.unwrap();
    let mut source = None;
    let mut print = Print::None;
    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 {
                            source = Some(Source::Path(s.as_ref()));
                        } else {
                            panic!("template path must be string literal");
                        },
                        "source" => if let syn::Lit::Str(ref s, _) = *val {
                            source = Some(Source::Source(s.as_ref()));
                        } else {
                            panic!("template source must be string literal");
                        },
                        "print" => if let syn::Lit::Str(ref s, _) = *val {
                            print = s.into();
                        } else {
                            panic!("print value must be string literal");
                        },
                        _ => { panic!("unsupported annotation key found") }
                    }
                }
            }
        }
    }

    match source {
        Some(s) => TemplateMeta { source: s, print },
        None => panic!("template path or source not found in struct attributes"),
    }
}

// Holds metadata for the template, based on the `template()` attribute.
struct TemplateMeta<'a> {
    source: Source<'a>,
    print: Print,
}

enum Source<'a> {
    Path(&'a str),
    Source(&'a str),
}

#[derive(PartialEq)]
enum Print {
    All,
    Ast,
    Code,
    None,
}

impl<'a> From<&'a String> for Print {
    fn from(s: &'a String) -> Print {
        use Print::*;
        match s.as_ref() {
            "all" => All,
            "ast" => Ast,
            "code" => Code,
            "none" => None,
            v => panic!("invalid value for print option: {}", v),
        }
    }
}