struct Generator<'a, 'h> { /// 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 Arc<Path>, Context<'a>, FxBuildHasher>, /// The heritage contains references to blocks and their ancestry
heritage: Option<&'h Heritage<'a, 'h>>, /// Variables accessible directly from the current scope (not redirected to context)
locals: MapChain<'a>, /// 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: Whitespace, /// 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: WritableBuffer<'a>, /// Used in blocks to check if we are inside a filter block.
is_in_filter_block: usize, /// Set of called macros we are currently in. Used to prevent (indirect) recursions.
seen_macros: Vec<(&'a Macro<'a>, Option<FileInfo<'a>>)>,
}
let doc = format!( "A sub-template that renders only the block `{}` of [`{ident}`].",
block.name
); let method_name = format!("as_{}", block.name); let trait_name = format!("__Askama__{ident}__as__{}", block.name); let wrapper_name = format!("__Askama__{ident}__as__{}__Wrapper", block.name); let self_lt_name = format!("'__Askama__{ident}__as__{}__self", block.name);
let method_id = Ident::new(&method_name, span); let trait_id = Ident::new(&trait_name, span); let wrapper_id = Ident::new(&wrapper_name, span); let self_lt = Lifetime::new(&self_lt_name, span);
// generics of the input with an additional lifetime to capture `self` letmut wrapper_generics = self.input.ast.generics.clone(); if wrapper_generics.lt_token.is_none() {
wrapper_generics.lt_token = Some(Token);
wrapper_generics.gt_token = Some(Token);
}
wrapper_generics.params.insert( 0,
GenericParam::Lifetime(LifetimeParam::new(self_lt.clone())),
);
let (impl_generics, ty_generics, where_clause) = self.input.ast.generics.split_for_impl(); let (wrapper_impl_generics, wrapper_ty_generics, wrapper_where_clause) =
wrapper_generics.split_for_impl();
/// In here, we inspect in the expression if it is a literal, and if it is, whether it /// can be escaped at compile time. fn compile_time_escape<'a>(expr: &Expr<'a>, escaper: &str) -> Option<Writable<'a>> { // we only optimize for known escapers enum OutputKind {
Html,
Text,
}
// we only optimize for known escapers let output = match escaper.strip_prefix("askama::filters::")? { "Html" => OutputKind::Html, "Text" => OutputKind::Text,
_ => return None,
};
// for now, we only escape strings, chars, numbers, and bools at compile time let value = match *expr {
Expr::StrLit(StrLit {
prefix: None,
content,
}) => { if content.find('\\').is_none() { // if the literal does not contain any backslashes, then it does not need unescaping
Cow::Borrowed(content)
} else { // the input could be string escaped if it contains any backslashes let input = format!(r#""{content}""#); let input = input.parse().ok()?; let input = syn::parse2::<syn::LitStr>(input).ok()?;
Cow::Owned(input.value())
}
}
Expr::CharLit(CharLit {
prefix: None,
content,
}) => { if content.find('\\').is_none() { // if the literal does not contain any backslashes, then it does not need unescaping
Cow::Borrowed(content)
} else { // the input could be string escaped if it contains any backslashes let input = format!(r#"'{content}'"#); let input = input.parse().ok()?; let input = syn::parse2::<syn::LitChar>(input).ok()?;
Cow::Owned(input.value().to_string())
}
}
Expr::NumLit(_, value) => { enum NumKind {
Int(Option<IntKind>),
Float(Option<FloatKind>),
}
let (orig_value, kind) = match value {
Num::Int(value, kind) => (value, NumKind::Int(kind)),
Num::Float(value, kind) => (value, NumKind::Float(kind)),
}; let value = match orig_value.chars().any(|c| c == '_') { true => Cow::Owned(orig_value.chars().filter(|&c| c != '_').collect()), false => Cow::Borrowed(orig_value),
};
/// Iterates the scopes in reverse and returns `Some(LocalMeta)` /// from the first scope where `key` exists. fn get<'b>(&'b self, key: &str) -> Option<&'b LocalMeta> { self.scopes.iter().rev().find_map(|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".
}
/// Returns `true` if enough assumptions can be made, /// to determine that `self` is copyable. fn is_copyable(expr: &Expr<'_>) -> bool {
is_copyable_within_op(expr, false)
}
fn is_copyable_within_op(expr: &Expr<'_>, within_op: bool) -> bool { match expr {
Expr::BoolLit(_)
| Expr::NumLit(_, _)
| Expr::StrLit(_)
| Expr::CharLit(_)
| Expr::BinOp(_, _, _) => true,
Expr::Unary(.., expr) => is_copyable_within_op(expr, true),
Expr::Range(..) => true, // The result of a call likely doesn't need to be borrowed, // as in that case the call is more likely to return a // reference in the first place then.
Expr::Call { .. } | Expr::Path(..) | Expr::Filter(..) | Expr::RustMacro(..) => true, // If the `expr` is within a `Unary` or `BinOp` then // an assumption can be made that the operand is copy. // If not, then the value is moved and adding `.clone()` // will solve that issue. However, if the operand is // implicitly borrowed, then it's likely not even possible // to get the template to compile.
_ => within_op && is_attr_self(expr),
}
}
/// Returns `true` if this is an `Attr` where the `obj` is `"self"`. fn is_attr_self(mut expr: &Expr<'_>) -> bool { loop { match expr {
Expr::Attr(obj, _) if matches!(***obj, Expr::Var("self")) => returntrue,
Expr::Attr(obj, _) if matches!(***obj, Expr::Attr(..)) => expr = obj,
_ => returnfalse,
}
}
}
/// 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. fn normalize_identifier(ident: &str) -> &str { // This table works for as long as the replacement string is the original string // prepended with "r#". The strings get right-padded to the same length with b'_'. // While the code does not need it, please keep the list sorted when adding new // keywords.
if ident.len() > MAX_RUST_KEYWORD_LEN { return ident;
} let kws = RUST_KEYWORDS[ident.len()];
// Since the individual buckets are quite short, a linear search is faster than a binary search. for probe in kws { if padded_ident == *AsciiChar::slice_as_bytes(probe[2..].try_into().unwrap()) { return AsciiStr::from_slice(&probe[..ident.len() + 2]);
}
}
ident
}
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.