1 extern crate proc_macro;
2 
3 use askama_shared::heritage::{Context, Heritage};
4 use askama_shared::input::{Print, Source, TemplateInput};
5 use askama_shared::parser::{parse, Expr, Node};
6 use askama_shared::{
7     generator, get_template_source, read_config_file, CompileError, Config, Integrations,
8 };
9 use proc_macro::TokenStream;
10 use proc_macro2::Span;
11 
12 use std::collections::HashMap;
13 use std::path::PathBuf;
14 
15 #[proc_macro_derive(Template, attributes(template))]
derive_template(input: TokenStream) -> TokenStream16 pub fn derive_template(input: TokenStream) -> TokenStream {
17     let ast: syn::DeriveInput = syn::parse(input).unwrap();
18     match build_template(&ast) {
19         Ok(source) => source.parse().unwrap(),
20         Err(e) => syn::Error::new(Span::call_site(), e)
21             .to_compile_error()
22             .into(),
23     }
24 }
25 
26 /// Takes a `syn::DeriveInput` and generates source code for it
27 ///
28 /// Reads the metadata from the `template()` attribute to get the template
29 /// metadata, then fetches the source from the filesystem. The source is
30 /// parsed, and the parse tree is fed to the code generator. Will print
31 /// the parse tree and/or generated source according to the `print` key's
32 /// value as passed to the `template()` attribute.
build_template(ast: &syn::DeriveInput) -> Result<String, CompileError>33 fn build_template(ast: &syn::DeriveInput) -> Result<String, CompileError> {
34     let config_toml = read_config_file()?;
35     let config = Config::new(&config_toml)?;
36     let input = TemplateInput::new(ast, &config)?;
37     let source: String = match input.source {
38         Source::Source(ref s) => s.clone(),
39         Source::Path(_) => get_template_source(&input.path)?,
40     };
41 
42     let mut sources = HashMap::new();
43     find_used_templates(&input, &mut sources, source)?;
44 
45     let mut parsed = HashMap::new();
46     for (path, src) in &sources {
47         parsed.insert(path, parse(src, input.syntax)?);
48     }
49 
50     let mut contexts = HashMap::new();
51     for (path, nodes) in &parsed {
52         contexts.insert(*path, Context::new(&input.config, path, nodes)?);
53     }
54 
55     let ctx = &contexts[&input.path];
56     let heritage = if !ctx.blocks.is_empty() || ctx.extends.is_some() {
57         Some(Heritage::new(ctx, &contexts))
58     } else {
59         None
60     };
61 
62     if input.print == Print::Ast || input.print == Print::All {
63         eprintln!("{:?}", parsed[&input.path]);
64     }
65 
66     let code = generator::generate(&input, &contexts, &heritage, INTEGRATIONS)?;
67     if input.print == Print::Code || input.print == Print::All {
68         eprintln!("{}", code);
69     }
70     Ok(code)
71 }
72 
find_used_templates( input: &TemplateInput, map: &mut HashMap<PathBuf, String>, source: String, ) -> Result<(), CompileError>73 fn find_used_templates(
74     input: &TemplateInput,
75     map: &mut HashMap<PathBuf, String>,
76     source: String,
77 ) -> Result<(), CompileError> {
78     let mut check = vec![(input.path.clone(), source)];
79     while let Some((path, source)) = check.pop() {
80         for n in parse(&source, input.syntax)? {
81             match n {
82                 Node::Extends(Expr::StrLit(extends)) => {
83                     let extends = input.config.find_template(extends, Some(&path))?;
84                     let source = get_template_source(&extends)?;
85                     check.push((extends, source));
86                 }
87                 Node::Import(_, import, _) => {
88                     let import = input.config.find_template(import, Some(&path))?;
89                     let source = get_template_source(&import)?;
90                     check.push((import, source));
91                 }
92                 _ => {}
93             }
94         }
95         map.insert(path, source);
96     }
97     Ok(())
98 }
99 
100 const INTEGRATIONS: Integrations = Integrations {
101     actix: cfg!(feature = "actix-web"),
102     gotham: cfg!(feature = "gotham"),
103     iron: cfg!(feature = "iron"),
104     mendes: cfg!(feature = "mendes"),
105     rocket: cfg!(feature = "rocket"),
106     tide: cfg!(feature = "tide"),
107     warp: cfg!(feature = "warp"),
108 };
109