use std::borrow::Cow; use std::collections::hash_map::{Entry, HashMap}; use std::fs::read_to_string; use std::iter::FusedIterator; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::{Arc, OnceLock};
use parser::node::Whitespace; use parser::{Node, Parsed}; use proc_macro2::Span; use rustc_hash::FxBuildHasher; use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::{Attribute, Expr, ExprLit, ExprPath, Ident, Lit, LitBool, LitStr, Meta, Token};
impl TemplateInput<'_> { /// Extract the template metadata from the `DeriveInput` structure. This /// mostly recovers the data for the `TemplateInput` fields from the /// `template()` attribute list fields. pub(crate) fn new<'n>(
ast: &'n syn::DeriveInput,
enum_ast: Option<&'n syn::DeriveInput>,
config: &'n Config,
args: &'n TemplateArgs,
) -> Result<TemplateInput<'n>, CompileError> { let TemplateArgs {
source: (source, source_span),
block, #[cfg(feature = "blocks")]
blocks,
print,
escaping,
ext,
ext_span,
syntax,
..
} = args;
// Validate the `source` and `ext` value together, since they are // related. In case `source` was used instead of `path`, the value // of `ext` is merged into a synthetic `path` value here. let path = match (&source, &ext) {
(Source::Path(path), _) => config.find_template(path, None, None)?,
(&Source::Source(_), Some(ext)) => {
PathBuf::from(format!("{}.{}", ast.ident, ext)).into()
}
(&Source::Source(_), None) => { return Err(CompileError::no_file_info( #[cfg(not(feature = "code-in-doc"))] "must include `ext` attribute when using `source` attribute", #[cfg(feature = "code-in-doc")] "must include `ext` attribute when using `source` or `in_doc` attribute",
None,
));
}
};
let escaping = escaping
.as_deref()
.or_else(|| path.extension().and_then(|s| s.to_str()))
.unwrap_or_default();
let escaper = config
.escapers
.iter()
.find_map(|(extensions, path)| {
extensions
.contains(&Cow::Borrowed(escaping))
.then_some(path.as_ref())
})
.ok_or_else(|| {
CompileError::no_file_info(
format_args!( "no escaper defined for extension '{escaping}'. You can define an escaper \ in the config file (named `askama.toml` by default). {}",
MsgValidEscapers(&config.escapers),
),
*ext_span,
)
})?;
let empty_punctuated = Punctuated::new(); let fields = match ast.data {
syn::Data::Struct(ref struct_) => { iflet syn::Fields::Named(ref fields) = &struct_.fields {
&fields.named
} else {
&empty_punctuated
}
}
syn::Data::Union(ref union_) => &union_.fields.named,
syn::Data::Enum(_) => &empty_punctuated,
}
.iter()
.map(|f| match &f.ident {
Some(ident) => ident.to_string(),
None => unreachable!("we checked that we are using a struct"),
})
.collect::<Vec<_>>();
let enum_args = PartialTemplateArgs::new(ast, &ast.attrs, false)?; let vars_args = enum_data
.variants
.iter()
.map(|variant| PartialTemplateArgs::new(ast, &variant.attrs, true))
.collect::<Result<Vec<_>, _>>()?; if vars_args.is_empty() { return Ok(Self::Struct(TemplateArgs::from_partial(ast, enum_args)?));
}
letmut needs_default_impl = vars_args.len(); let enum_source = enum_args.as_ref().and_then(|v| v.source.as_ref()); for (variant, var_args) in enum_data.variants.iter().zip(&vars_args) { if var_args
.as_ref()
.and_then(|v| v.source.as_ref())
.or(enum_source)
.is_none()
{ return Err(CompileError::new_with_span( #[cfg(not(feature = "code-in-doc"))] "either all `enum` variants need a `path` or `source` argument, \
or the `enum` itself needs a default implementation", #[cfg(feature = "code-in-doc")] "either all `enum` variants need a `path`, `source` or `in_doc` argument, \
or the `enum` itself needs a default implementation",
None,
Some(variant.ident.span()),
));
} elseif !var_args.is_none() {
needs_default_impl -= 1;
}
}
/// Try to find the source in the comment, in a `askama` code block. /// /// This is only done if no path or source was given in the `#[template]` attribute. #[cfg(feature = "code-in-doc")] fn source_from_docs(
span: Span,
docs: &[&Attribute],
ast: &syn::DeriveInput,
) -> Result<(Source, Option<Span>), CompileError> { let (source_span, source) = collect_comment_blocks(span, docs, ast)?; let source = strip_common_ws_prefix(source); let source = collect_askama_code_blocks(span, ast, source)?;
Ok((source, source_span))
}
if source_span.is_none() {
source_span = Some(kv.path.span());
}
};
letmut source = String::new(); for a in docs { // is a comment? let Meta::NameValue(kv) = &a.meta else { continue;
}; if !kv.path.is_ident("doc") { continue;
}
// is an understood comment, e.g. not `#[doc = inline_str(…)]` letmut value = &kv.value; let value = loop { match value {
Expr::Lit(lit) => break lit,
Expr::Group(group) => value = &group.expr,
_ => continue,
}
}; let Lit::Str(value) = &value.lit else { continue;
};
for attr in attrs { let Some(ident) = attr.path().get_ident() else { continue;
}; if ident == "template" {
this.template = ident.clone();
has_data = true;
} else { #[cfg(feature = "code-in-doc")] if ident == "doc" {
meta_docs.push(attr);
} continue;
}
let args = attr
.parse_args_with(<Punctuated<Meta, Token![,]>>::parse_terminated)
.map_err(|e| {
CompileError::no_file_info(
format_args!("unable to parse template arguments: {e}"),
Some(attr.path().span()),
)
})?; for arg in args { let pair = match arg {
Meta::NameValue(pair) => pair,
v => { return Err(CompileError::no_file_info( "unsupported attribute argument",
Some(v.span()),
));
}
}; let ident = match pair.path.get_ident() {
Some(ident) => ident,
None => unreachable!("not possible in syn::Meta::NameValue(…)"),
};
if ident == "askama" { if is_enum_variant { return Err(CompileError::no_file_info( "template attribute `askama` can only be used on the `enum`, \
not its variants",
Some(ident.span()),
));
}
ensure_only_once(ident, &mut this.crate_name)?;
this.crate_name = Some(get_exprpath(ident, pair.value)?); continue;
} elseif ident == "blocks" { if !cfg!(feature = "blocks") { return Err(CompileError::no_file_info( "enable feature `blocks` to use `blocks` argument",
Some(ident.span()),
));
} elseif is_enum_variant { return Err(CompileError::no_file_info( "template attribute `blocks` can only be used on the `enum`, \
not its variants",
Some(ident.span()),
));
} #[cfg(feature = "blocks")]
{
ensure_only_once(ident, &mut this.blocks)?;
this.blocks = Some(
get_exprarray(ident, pair.value)?
.elems
.into_iter()
.map(|value| get_strlit(ident, get_lit(ident, value)?))
.collect::<Result<_, _>>()?,
); continue;
}
}
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.