1#![deny(elided_lifetimes_in_paths)]
2#![deny(unreachable_pub)]
3
4use std::fmt;
5use std::{borrow::Cow, collections::HashMap};
6
7use proc_macro::TokenStream;
8use proc_macro2::Span;
9
10use parser::ParseError;
11
12mod config;
13use config::Config;
14mod generator;
15use generator::{Generator, MapChain};
16mod heritage;
17use heritage::{Context, Heritage};
18mod input;
19use input::{Print, TemplateArgs, TemplateInput};
20
21#[proc_macro_derive(Template, attributes(template))]
22pub fn derive_template(input: TokenStream) -> TokenStream {
23 let ast: DeriveInput = syn::parse::<syn::DeriveInput>(tokens:input).unwrap();
24 match build_template(&ast) {
25 Ok(source: String) => source.parse().unwrap(),
26 Err(e: CompileError) => e.into_compile_error(),
27 }
28}
29
30/// Takes a `syn::DeriveInput` and generates source code for it
31///
32/// Reads the metadata from the `template()` attribute to get the template
33/// metadata, then fetches the source from the filesystem. The source is
34/// parsed, and the parse tree is fed to the code generator. Will print
35/// the parse tree and/or generated source according to the `print` key's
36/// value as passed to the `template()` attribute.
37pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result<String, CompileError> {
38 let template_args = TemplateArgs::new(ast)?;
39 let toml = template_args.config()?;
40 let config = Config::new(&toml, template_args.whitespace.as_deref())?;
41 let input = TemplateInput::new(ast, &config, &template_args)?;
42
43 let mut templates = HashMap::new();
44 input.find_used_templates(&mut templates)?;
45
46 let mut contexts = HashMap::new();
47 for (path, parsed) in &templates {
48 contexts.insert(
49 path.as_path(),
50 Context::new(input.config, path, parsed.nodes())?,
51 );
52 }
53
54 let ctx = &contexts[input.path.as_path()];
55 let heritage = if !ctx.blocks.is_empty() || ctx.extends.is_some() {
56 Some(Heritage::new(ctx, &contexts))
57 } else {
58 None
59 };
60
61 if input.print == Print::Ast || input.print == Print::All {
62 eprintln!("{:?}", templates[input.path.as_path()].nodes());
63 }
64
65 let code = Generator::new(&input, &contexts, heritage.as_ref(), MapChain::default())
66 .build(&contexts[input.path.as_path()])?;
67 if input.print == Print::Code || input.print == Print::All {
68 eprintln!("{code}");
69 }
70 Ok(code)
71}
72
73#[derive(Debug, Clone)]
74struct CompileError {
75 msg: Cow<'static, str>,
76 span: Span,
77}
78
79impl CompileError {
80 fn new<S: Into<Cow<'static, str>>>(s: S, span: Span) -> Self {
81 Self {
82 msg: s.into(),
83 span,
84 }
85 }
86
87 fn into_compile_error(self) -> TokenStream {
88 synTokenStream::Error::new(self.span, self.msg)
89 .to_compile_error()
90 .into()
91 }
92}
93
94impl std::error::Error for CompileError {}
95
96impl fmt::Display for CompileError {
97 #[inline]
98 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
99 fmt.write_str(&self.msg)
100 }
101}
102
103impl From<ParseError> for CompileError {
104 #[inline]
105 fn from(e: ParseError) -> Self {
106 Self::new(s:e.to_string(), Span::call_site())
107 }
108}
109
110impl From<&'static str> for CompileError {
111 #[inline]
112 fn from(s: &'static str) -> Self {
113 Self::new(s, Span::call_site())
114 }
115}
116
117impl From<String> for CompileError {
118 #[inline]
119 fn from(s: String) -> Self {
120 Self::new(s, Span::call_site())
121 }
122}
123
124// This is used by the code generator to decide whether a named filter is part of
125// Askama or should refer to a local `filters` module. It should contain all the
126// filters shipped with Askama, even the optional ones (since optional inclusion
127// in the const vector based on features seems impossible right now).
128const BUILT_IN_FILTERS: &[&str] = &[
129 "abs",
130 "capitalize",
131 "center",
132 "e",
133 "escape",
134 "filesizeformat",
135 "fmt",
136 "format",
137 "indent",
138 "into_f64",
139 "into_isize",
140 "join",
141 "linebreaks",
142 "linebreaksbr",
143 "paragraphbreaks",
144 "lower",
145 "lowercase",
146 "safe",
147 "trim",
148 "truncate",
149 "upper",
150 "uppercase",
151 "urlencode",
152 "urlencode_strict",
153 "wordcount",
154 // optional features, reserve the names anyway:
155 "json",
156 "markdown",
157 "yaml",
158];
159