use proc_macro::TokenStream; use quote::{quote, ToTokens}; use syn::punctuated::Punctuated;
use std::collections::hash_map::{Entry, HashMap}; use std::path::{Path, PathBuf}; use std::{cmp, hash, mem, str};
/// The actual implementation for askama_derive::Template pub(crate) fn derive_template(input: TokenStream) -> TokenStream { let ast: syn::DeriveInput = syn::parse(input).unwrap(); match build_template(&ast) {
Ok(source) => source.parse().unwrap(),
Err(e) => e.into_compile_error(),
}
}
/// 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) -> Result<String, CompileError> { let template_args = TemplateArgs::new(ast)?; let config_toml = read_config_file(template_args.config_path.as_deref())?; let config = Config::new(&config_toml, template_args.whitespace.as_ref())?; let input = TemplateInput::new(ast, &config, template_args)?; let source: String = match input.source {
Source::Source(ref s) => s.clone(),
Source::Path(_) => get_template_source(&input.path)?,
};
impl TemplateArgs { fn new(ast: &'_ syn::DeriveInput) -> Result<Self, CompileError> { // Check that an attribute called `template()` exists once and that it is // the proper type (list). letmut template_args = None; for attr in &ast.attrs { if !attr.path().is_ident("template") { continue;
}
match attr.parse_args_with(Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated) {
Ok(args) if template_args.is_none() => template_args = Some(args),
Ok(_) => return Err("duplicated 'template' attribute".into()),
Err(e) => return Err(format!("unable to parse template arguments: {e}").into()),
};
}
let template_args =
template_args.ok_or_else(|| CompileError::from("no attribute 'template' found"))?;
letmut args = Self::default(); // Loop over the meta attributes and find everything that we // understand. Return a CompileError if something is not right. // `source` contains an enum that can represent `path` or `source`. for item in template_args { let pair = match item {
syn::Meta::NameValue(pair) => pair,
_ => { return Err(format!( "unsupported attribute argument {:?}",
item.to_token_stream()
)
.into())
}
};
let ident = match pair.path.get_ident() {
Some(ident) => ident,
None => unreachable!("not possible in syn::Meta::NameValue(…)"),
};
let value = match pair.value {
syn::Expr::Lit(lit) => lit,
syn::Expr::Group(group) => match *group.expr {
syn::Expr::Lit(lit) => lit,
_ => { return Err(format!("unsupported argument value type for {ident:?}").into())
}
},
_ => return Err(format!("unsupported argument value type for {ident:?}").into()),
};
if ident == "path" { iflet syn::Lit::Str(s) = value.lit { if args.source.is_some() { return Err("must specify 'source' or 'path', not both".into());
}
args.source = Some(Source::Path(s.value()));
} else { return Err("template path must be string literal".into());
}
} elseif ident == "source" { iflet syn::Lit::Str(s) = value.lit { if args.source.is_some() { return Err("must specify 'source' or 'path', not both".into());
}
args.source = Some(Source::Source(s.value()));
} else { return Err("template source must be string literal".into());
}
} elseif ident == "print" { iflet syn::Lit::Str(s) = value.lit {
args.print = s.value().parse()?;
} else { return Err("print value must be string literal".into());
}
} elseif ident == "escape" { iflet syn::Lit::Str(s) = value.lit {
args.escaping = Some(s.value());
} else { return Err("escape value must be string literal".into());
}
} elseif ident == "ext" { iflet syn::Lit::Str(s) = value.lit {
args.ext = Some(s.value());
} else { return Err("ext value must be string literal".into());
}
} elseif ident == "syntax" { iflet syn::Lit::Str(s) = value.lit {
args.syntax = Some(s.value())
} else { return Err("syntax value must be string literal".into());
}
} elseif ident == "config" { iflet syn::Lit::Str(s) = value.lit {
args.config_path = Some(s.value())
} else { return Err("config value must be string literal".into());
}
} elseif ident == "whitespace" { iflet syn::Lit::Str(s) = value.lit {
args.whitespace = Some(s.value())
} else { return Err("whitespace value must be string literal".into());
}
} else { return Err(format!("unsupported attribute key {ident:?} found").into());
}
}
Ok(args)
}
}
fn find_used_templates(
input: &TemplateInput<'_>,
map: &mut HashMap<PathBuf, String>,
source: String,
) -> Result<(), CompileError> { letmut dependency_graph = Vec::new(); letmut check = vec![(input.path.clone(), source)]; whilelet Some((path, source)) = check.pop() { for n in parse(&source, input.syntax)? { match n {
Node::Extends(extends) => { let extends = input.config.find_template(extends, Some(&path))?; let dependency_path = (path.clone(), extends.clone()); if dependency_graph.contains(&dependency_path) { return Err(format!( "cyclic dependency in graph {:#?}",
dependency_graph
.iter()
.map(|e| format!("{:#?} --> {:#?}", e.0, e.1))
.collect::<Vec<String>>()
)
.into());
}
dependency_graph.push(dependency_path); let source = get_template_source(&extends)?;
check.push((extends, source));
}
Node::Import(_, import, _) => { let import = input.config.find_template(import, Some(&path))?; let source = get_template_source(&import)?;
check.push((import, source));
}
_ => {}
}
}
map.insert(path, source);
}
Ok(())
}
struct Generator<'a> { // The template input state: original struct AST and attributes
input: &'a TemplateInput<'a>, // All contexts, keyed by the package-relative template path
contexts: &'a HashMap<&'a Path, Context<'a>>, // The heritage contains references to blocks and their ancestry
heritage: Option<&'a Heritage<'a>>, // Variables accessible directly from the current scope (not redirected to context)
locals: MapChain<'a, &'a str, LocalMeta>, // Suffix whitespace from the previous literal. Will be flushed to the // output buffer unless suppressed by whitespace suppression on the next // non-literal.
next_ws: Option<&'a str>, // Whitespace suppression from the previous non-literal. Will be used to // determine whether to flush prefix whitespace from the next literal.
skip_ws: WhitespaceHandling, // If currently in a block, this will contain the name of a potential parent block
super_block: Option<(&'a str, usize)>, // buffer for writable
buf_writable: Vec<Writable<'a>>, // Counter for write! hash named arguments
named: usize, // If set to `suppress`, the whitespace characters will be removed by default unless `+` is // used.
whitespace: WhitespaceHandling,
}
// Make sure the compiler understands that the generated code depends on the template files. for path inself.contexts.keys() { // Skip the fake path of templates defined in rust source. let path_is_valid = matchself.input.source {
Source::Path(_) => true,
Source::Source(_) => path != &self.input.path,
}; if path_is_valid { let path = path.to_str().unwrap();
buf.writeln(
"e! {
include_bytes!(#path);
}
.to_string(),
)?;
}
}
self.locals.push(); letmut arm_size = 0; iflet Some(CondTest { target, expr }) = cond { if i == 0 {
buf.write("if ");
} else {
buf.dedent()?;
buf.write("} else if ");
}
iflet Some(target) = target { letmut expr_buf = Buffer::new(0); self.visit_expr(&mut expr_buf, expr)?;
buf.write("let "); self.visit_target(buf, true, true, target);
buf.write(" = &(");
buf.write(&expr_buf.buf);
buf.write(")");
} else { // The following syntax `*(&(...) as &bool)` is used to // trigger Rust's automatic dereferencing, to coerce // e.g. `&&&&&bool` to `bool`. First `&(...) as &bool` // coerces e.g. `&&&bool` to `&bool`. Then `*(&bool)` // finally dereferences it to `bool`.
buf.write("*(&("); let expr_code = self.visit_expr_root(expr)?;
buf.write(&expr_code);
buf.write(") as &bool)");
}
} else {
buf.dedent()?;
buf.write("} else");
has_else = true;
}
let expr_code = self.visit_expr_root(&loop_block.iter)?;
let flushed = self.write_buf_writable(buf)?;
buf.writeln("{")?;
buf.writeln("let mut _did_loop = false;")?; match loop_block.iter {
Expr::Range(_, _, _) => buf.writeln(&format!("let _iter = {expr_code};")),
Expr::Array(..) => buf.writeln(&format!("let _iter = {expr_code}.iter();")), // If `iter` is a call then we assume it's something that returns // an iterator. If not then the user can explicitly add the needed // call without issues.
Expr::Call(..) | Expr::Index(..) => {
buf.writeln(&format!("let _iter = ({expr_code}).into_iter();"))
} // If accessing `self` then it most likely needs to be // borrowed, to prevent an attempt of moving.
_ if expr_code.starts_with("self.") => {
buf.writeln(&format!("let _iter = (&{expr_code}).into_iter();"))
} // If accessing a field then it most likely needs to be // borrowed, to prevent an attempt of moving.
Expr::Attr(..) => buf.writeln(&format!("let _iter = (&{expr_code}).into_iter();")), // Otherwise, we borrow `iter` assuming that it implements `IntoIterator`.
_ => buf.writeln(&format!("let _iter = ({expr_code}).into_iter();")),
}?; iflet Some(cond) = &loop_block.cond { self.locals.push();
buf.write("let _iter = _iter.filter(|"); self.visit_target(buf, true, true, &loop_block.var);
buf.write("| -> bool {"); self.visit_expr(buf, cond)?;
buf.writeln("});")?; self.locals.pop();
}
let (def, own_ctx) = match scope {
Some(s) => { let path = ctx.imports.get(s).ok_or_else(|| {
CompileError::from(format!("no import found for scope {s:?}"))
})?; let mctx = self
.contexts
.get(path.as_path())
.ok_or_else(|| CompileError::from(format!("context for {path:?} not found")))?; let def = mctx.macros.get(name).ok_or_else(|| {
CompileError::from(format!("macro {name:?} not found in scope {s:?}"))
})?;
(def, mctx)
}
None => { let def = ctx
.macros
.get(name)
.ok_or_else(|| CompileError::from(format!("macro {name:?} not found")))?;
(def, ctx)
}
};
self.flush_ws(ws); // Cannot handle_ws() here: whitespace from macro definition comes first self.locals.push(); self.write_buf_writable(buf)?;
buf.writeln("{")?; self.prepare_ws(def.ws1);
letmut names = Buffer::new(0); letmut values = Buffer::new(0); letmut is_first_variable = true; for (i, arg) in def.args.iter().enumerate() { let expr = args.get(i).ok_or_else(|| {
CompileError::from(format!("macro {name:?} takes more than {i} arguments"))
})?;
match expr { // If `expr` is already a form of variable then // don't reintroduce a new variable. This is // to avoid moving non-copyable values.
Expr::Var(name) => { let var = self.locals.resolve_or_self(name); self.locals.insert(arg, LocalMeta::with_ref(var));
}
Expr::Attr(obj, attr) => { letmut attr_buf = Buffer::new(0); self.visit_attr(&mut attr_buf, obj, attr)?;
let var = self.locals.resolve(&attr_buf.buf).unwrap_or(attr_buf.buf); self.locals.insert(arg, LocalMeta::with_ref(var));
} // Everything else still needs to become variables, // to avoid having the same logic be executed // multiple times, e.g. in the case of macro // parameters being used multiple times.
_ => { if is_first_variable {
is_first_variable = false
} else {
names.write(", ");
values.write(", ");
}
names.write(arg);
// Make sure the compiler understands that the generated code depends on the template file.
{ let path = path.to_str().unwrap();
buf.writeln(
"e! {
include_bytes!(#path);
}
.to_string(),
)?;
}
let size_hint = { // Since nodes must not outlive the Generator, we instantiate // a nested Generator here to handle the include's nodes. letmutgen = self.child(); letmut size_hint = gen.handle(ctx, &nodes, buf, AstLevel::Nested)?;
size_hint += gen.write_buf_writable(buf)?;
size_hint
}; self.prepare_ws(ws);
Ok(size_hint)
}
fn is_shadowing_variable(&self, var: &Target<'a>) -> Result<bool, CompileError> { match var {
Target::Name(name) => { let name = normalize_identifier(name); matchself.locals.get(&name) { // declares a new variable
None => Ok(false), // an initialized variable gets shadowed
Some(meta) if meta.initialized => Ok(true), // initializes a variable that was introduced in a LetDecl before
_ => Ok(false),
}
}
Target::Tuple(_, targets) => { for target in targets { matchself.is_shadowing_variable(target) {
Ok(false) => continue,
outcome => return outcome,
}
}
Ok(false)
}
Target::Struct(_, named_targets) => { for (_, target) in named_targets { matchself.is_shadowing_variable(target) {
Ok(false) => continue,
outcome => return outcome,
}
}
Ok(false)
}
_ => Err("literals are not allowed on the left-hand side of an assignment".into()),
}
}
let shadowed = self.is_shadowing_variable(var)?; if shadowed { // Need to flush the buffer if the variable is being shadowed, // to ensure the old variable is used. self.write_buf_writable(buf)?;
} if shadowed
|| !matches!(var, &Target::Name(_))
|| matches!(var, Target::Name(name) ifself.locals.get(name).is_none())
{
buf.write("let ");
}
// If `name` is `Some`, this is a call to a block definition, and we have to find // the first block for that name from the ancestry chain. If name is `None`, this // is from a `super()` call, and we can get the name from `self.super_block`. fn write_block(
&mutself,
buf: &mut Buffer,
name: Option<&'a str>,
outer: Ws,
) -> Result<usize, CompileError> { // Flush preceding whitespace according to the outer WS spec self.flush_ws(outer);
let prev_block = self.super_block; let cur = match (name, prev_block) { // The top-level context contains a block definition
(Some(cur_name), None) => (cur_name, 0), // A block definition contains a block definition of the same name
(Some(cur_name), Some((prev_name, _))) if cur_name == prev_name => { return Err(format!("cannot define recursive blocks ({cur_name})").into());
} // A block definition contains a definition of another block
(Some(cur_name), Some((_, _))) => (cur_name, 0), // `super()` was called inside a block
(None, Some((prev_name, gen))) => (prev_name, gen + 1), // `super()` is called from outside a block
(None, None) => return Err("cannot call 'super()' outside block".into()),
}; self.super_block = Some(cur);
// Get the block definition from the heritage chain let heritage = self
.heritage
.as_ref()
.ok_or_else(|| CompileError::from("no block ancestors available"))?; let (ctx, def) = heritage.blocks[cur.0].get(cur.1).ok_or_else(|| {
CompileError::from(match name {
None => format!("no super() block found for block '{}'", cur.0),
Some(name) => format!("no block found for name '{name}'"),
})
})?;
// Get the nodes and whitespace suppression data from the block definition let (ws1, nodes, ws2) = iflet Node::BlockDef(ws1, _, nodes, ws2) = def {
(ws1, nodes, ws2)
} else {
unreachable!()
};
// Handle inner whitespace suppression spec and process block nodes self.prepare_ws(*ws1); self.locals.push(); let size_hint = self.handle(ctx, nodes, buf, AstLevel::Block)?;
if !self.locals.is_current_empty() { // Need to flush the buffer before popping the variable stack self.write_buf_writable(buf)?;
}
self.locals.pop(); self.flush_ws(*ws2);
// Restore original block context and set whitespace suppression for // succeeding whitespace according to the outer WS spec self.super_block = prev_block; self.prepare_ws(outer);
Ok(size_hint)
}
#[cfg(not(feature = "serde-json"))] if name == "json" { return Err("the `json` filter requires the `serde-json` feature to be enabled".into());
} #[cfg(not(feature = "serde-yaml"))] if name == "yaml" { return Err("the `yaml` filter requires the `serde-yaml` feature to be enabled".into());
}
// Force type coercion on first argument to `join` filter (see #39). fn _visit_join_filter(
&mutself,
buf: &mut Buffer,
args: &[Expr<'_>],
) -> Result<(), CompileError> {
buf.write("::askama::filters::join((&"); for (i, arg) in args.iter().enumerate() { if i > 0 {
buf.write(", &");
} self.visit_expr(buf, arg)?; if i == 0 {
buf.write(").into_iter()");
}
}
buf.write(")?");
Ok(())
}
/* Helper methods for dealing with whitespace nodes */
// Combines `flush_ws()` and `prepare_ws()` to handle both trailing whitespace from the // preceding literal and leading whitespace from the succeeding literal. fn handle_ws(&mutself, ws: Ws) { self.flush_ws(ws); self.prepare_ws(ws);
}
// If the previous literal left some trailing whitespace in `next_ws` and the // prefix whitespace suppressor from the given argument, flush that whitespace. // In either case, `next_ws` is reset to `None` (no trailing whitespace). fn flush_ws(&mutself, ws: Ws) { ifself.next_ws.is_none() { return;
}
// If `whitespace` is set to `suppress`, we keep the whitespace characters only if there is // a `+` character. matchself.should_trim_ws(ws.0) {
WhitespaceHandling::Preserve => { let val = self.next_ws.unwrap(); if !val.is_empty() { self.buf_writable.push(Writable::Lit(val));
}
}
WhitespaceHandling::Minimize => { let val = self.next_ws.unwrap(); if !val.is_empty() { self.buf_writable
.push(Writable::Lit(match val.contains('\n') { true => "\n", false => " ",
}));
}
}
WhitespaceHandling::Suppress => {}
} self.next_ws = None;
}
// Sets `skip_ws` to match the suffix whitespace suppressor from the given // argument, to determine whether to suppress leading whitespace from the // next literal. fn prepare_ws(&mutself, ws: Ws) { self.skip_ws = self.should_trim_ws(ws.1);
}
}
struct Buffer { // The buffer to generate the code into
buf: String, // The current level of indentation (in spaces)
indent: u8, // Whether the output buffer is currently at the start of a line
start: bool,
}
/// Iterates the scopes in reverse and returns `Some(LocalMeta)` /// from the first scope where `key` exists. fn get(&self, key: &K) -> Option<&V> { letmut scopes = self.scopes.iter().rev();
scopes
.find_map(|set| set.get(key))
.or_else(|| self.parent.and_then(|set| set.get(key)))
}
// Note that if `insert` returns `Some` then it implies // an identifier is reused. For e.g. `{% macro f(a, a) %}` // and `{% let (a, a) = ... %}` then this results in a // generated template, which when compiled fails with the // compile error "identifier `a` used more than once".
}
// Identifiers to be replaced with raw identifiers, so as to avoid // collisions between template syntax and Rust's syntax. In particular // [Rust keywords](https://doc.rust-lang.org/reference/keywords.html) // should be replaced, since they're not reserved words in Askama // syntax but have a high probability of causing problems in the // generated code. // // This list excludes the Rust keywords *self*, *Self*, and *super* // because they are not allowed to be raw identifiers, and *loop* // because it's used something like a keyword in the template // language. static USE_RAW: [(&str, &str); 47] = [
("as", "r#as"),
("break", "r#break"),
("const", "r#const"),
("continue", "r#continue"),
("crate", "r#crate"),
("else", "r#else"),
("enum", "r#enum"),
("extern", "r#extern"),
("false", "r#false"),
("fn", "r#fn"),
("for", "r#for"),
("if", "r#if"),
("impl", "r#impl"),
("in", "r#in"),
("let", "r#let"),
("match", "r#match"),
("mod", "r#mod"),
("move", "r#move"),
("mut", "r#mut"),
("pub", "r#pub"),
("ref", "r#ref"),
("return", "r#return"),
("static", "r#static"),
("struct", "r#struct"),
("trait", "r#trait"),
("true", "r#true"),
("type", "r#type"),
("unsafe", "r#unsafe"),
("use", "r#use"),
("where", "r#where"),
("while", "r#while"),
("async", "r#async"),
("await", "r#await"),
("dyn", "r#dyn"),
("abstract", "r#abstract"),
("become", "r#become"),
("box", "r#box"),
("do", "r#do"),
("final", "r#final"),
("macro", "r#macro"),
("override", "r#override"),
("priv", "r#priv"),
("typeof", "r#typeof"),
("unsized", "r#unsized"),
("virtual", "r#virtual"),
("yield", "r#yield"),
("try", "r#try"),
];
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.