Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  input.rs

  Sprache: Rust
 

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};

use crate::config::{Config, SyntaxAndCache};
use crate::{CompileError, FileInfo, MsgValidEscapers, OnceMap};

#[derive(Clone)]
pub(cratestruct TemplateInput<'a> {
    pub(crate) ast: &'a syn::DeriveInput,
    pub(crate) enum_ast: Option<&'a syn::DeriveInput>,
    pub(crate) config: &'a Config,
    pub(crate) syntax: &'a SyntaxAndCache<'a>,
    pub(crate) source: &'a Source,
    pub(crate) source_span: Option<Span>,
    pub(crate) block: Option<(&'a str, Span)>,
    #[cfg(feature = "blocks")]
    pub(crate) blocks: &'a [Block],
    pub(crate) print: Print,
    pub(crate) escaper: &'a str,
    pub(crate) path: Arc<Path>,
    pub(crate) fields: Arc<[String]>,
}

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(cratefn 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,
                ));
            }
        };

        // Validate syntax
        let syntax = syntax.as_deref().map_or_else(
            || Ok(config.syntaxes.get(config.default_syntax).unwrap()),
            |s| {
                config.syntaxes.get(s).ok_or_else(|| {
                    CompileError::no_file_info(format_args!("syntax `{s}` is undefined"), None)
                })
            },
        )?;

        // Match extension against defined output formats

        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_) => {
                if let 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<_>>();

        Ok(TemplateInput {
            ast,
            enum_ast,
            config,
            syntax,
            source,
            source_span: *source_span,
            block: block.as_ref().map(|(block, span)| (block.as_str(), *span)),
            #[cfg(feature = "blocks")]
            blocks: blocks.as_slice(),
            print: *print,
            escaper,
            path,
            fields: fields.into(),
        })
    }

    pub(cratefn find_used_templates(
        &self,
        map: &mut HashMap<Arc<Path>, Arc<Parsed>, FxBuildHasher>,
    ) -> Result<(), CompileError> {
        let (source, source_path) = match &self.source {
            Source::Source(s) => (s.clone(), None),
            Source::Path(_) => (
                get_template_source(&self.path, None)?,
                Some(Arc::clone(&self.path)),
            ),
        };

        let mut dependency_graph = Vec::new();
        let mut check = vec![(Arc::clone(&self.path), source, source_path)];
        while let Some((path, source, source_path)) = check.pop() {
            let parsed = match self.syntax.parse(Arc::clone(&source), source_path) {
                Ok(parsed) => parsed,
                Err(err) => {
                    let msg = err
                        .message
                        .unwrap_or_else(|| "failed to parse template source".into());
                    let file_path = err
                        .file_path
                        .as_deref()
                        .unwrap_or(Path::new("<source attribute>"));
                    let file_info =
                        FileInfo::new(file_path, Some(&source), Some(&source[err.offset..]));
                    return Err(CompileError::new(msg, Some(file_info)));
                }
            };

            let mut top = true;
            let mut nested = vec![parsed.nodes()];
            while let Some(nodes) = nested.pop() {
                for n in nodes {
                    let mut add_to_check = |new_path: Arc<Path>| -> Result<(), CompileError> {
                        if let Entry::Vacant(e) = map.entry(new_path) {
                            // Add a dummy entry to `map` in order to prevent adding `path`
                            // multiple times to `check`.
                            let new_path = e.key();
                            let source = parsed.source();
                            let source = get_template_source(
                                new_path,
                                Some((
                                    &path,
                                    source,
                                    n.span().as_suffix_of(source).unwrap_or_default(),
                                )),
                            )?;
                            check.push((new_path.clone(), source, Some(new_path.clone())));
                            e.insert(Arc::default());
                        }
                        Ok(())
                    };

                    match n {
                        Node::Extends(extends) if top => {
                            let extends = self.config.find_template(
                                extends.path,
                                Some(&path),
                                Some(FileInfo::of(extends.span(), &path, &parsed)),
                            )?;
                            let dependency_path = (path.clone(), extends.clone());
                            if path == extends {
                                // We add the path into the graph to have a better looking error.
                                dependency_graph.push(dependency_path);
                                return cyclic_graph_error(&dependency_graph);
                            } else if dependency_graph.contains(&dependency_path) {
                                return cyclic_graph_error(&dependency_graph);
                            }
                            dependency_graph.push(dependency_path);
                            add_to_check(extends)?;
                        }
                        Node::Macro(m) if top => {
                            nested.push(&m.nodes);
                        }
                        Node::Import(import) if top => {
                            let import = self.config.find_template(
                                import.path,
                                Some(&path),
                                Some(FileInfo::of(import.span(), &path, &parsed)),
                            )?;
                            add_to_check(import)?;
                        }
                        Node::FilterBlock(f) => {
                            nested.push(&f.nodes);
                        }
                        Node::Include(include) => {
                            let include = self.config.find_template(
                                include.path,
                                Some(&path),
                                Some(FileInfo::of(include.span(), &path, &parsed)),
                            )?;
                            add_to_check(include)?;
                        }
                        Node::BlockDef(b) => {
                            nested.push(&b.nodes);
                        }
                        Node::If(i) => {
                            for cond in &i.branches {
                                nested.push(&cond.nodes);
                            }
                        }
                        Node::Loop(l) => {
                            nested.push(&l.body);
                            nested.push(&l.else_nodes);
                        }
                        Node::Match(m) => {
                            for arm in &m.arms {
                                nested.push(&arm.nodes);
                            }
                        }
                        Node::Lit(_)
                        | Node::Comment(_)
                        | Node::Expr(_, _)
                        | Node::Call(_)
                        | Node::Extends(_)
                        | Node::Let(_)
                        | Node::Import(_)
                        | Node::Macro(_)
                        | Node::Raw(_)
                        | Node::Continue(_)
                        | Node::Break(_) => {}
                    }
                }
                top = false;
            }
            map.insert(path, parsed);
        }
        Ok(())
    }
}

pub(crateenum AnyTemplateArgs {
    Struct(TemplateArgs),
    Enum {
        enum_args: Option<PartialTemplateArgs>,
        vars_args: Vec<Option<PartialTemplateArgs>>,
        has_default_impl: bool,
    },
}

impl AnyTemplateArgs {
    pub(cratefn new(ast: &syn::DeriveInput) -> Result<Self, CompileError> {
        let syn::Data::Enum(enum_data) = &ast.data else {
            return Ok(Self::Struct(TemplateArgs::new(ast)?));
        };

        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)?));
        }

        let mut 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()),
                ));
            } else if !var_args.is_none() {
                needs_default_impl -= 1;
            }
        }

        Ok(Self::Enum {
            enum_args,
            vars_args,
            has_default_impl: needs_default_impl > 0,
        })
    }

    pub(cratefn take_crate_name(&mut self) -> Option<ExprPath> {
        match self {
            AnyTemplateArgs::Struct(template_args) => template_args.crate_name.take(),
            AnyTemplateArgs::Enum { enum_args, .. } => {
                if let Some(PartialTemplateArgs { crate_name, .. }) = enum_args {
                    crate_name.take()
                } else {
                    None
                }
            }
        }
    }
}

#[cfg(feature = "blocks")]
pub(cratestruct Block {
    pub(crate) name: String,
    pub(crate) span: Span,
}

pub(cratestruct TemplateArgs {
    pub(crate) source: (Source, Option<Span>),
    block: Option<(String, Span)>,
    #[cfg(feature = "blocks")]
    blocks: Vec<Block>,
    print: Print,
    escaping: Option<String>,
    ext: Option<String>,
    ext_span: Option<Span>,
    syntax: Option<String>,
    config: Option<String>,
    crate_name: Option<ExprPath>,
    pub(crate) whitespace: Option<Whitespace>,
    pub(crate) template_span: Option<Span>,
    pub(crate) config_span: Option<Span>,
}

impl TemplateArgs {
    pub(cratefn new(ast: &syn::DeriveInput) -> Result<Self, CompileError> {
        Self::from_partial(ast, PartialTemplateArgs::new(ast, &ast.attrs, false)?)
    }

    pub(cratefn from_partial(
        ast: &syn::DeriveInput,
        args: Option<PartialTemplateArgs>,
    ) -> Result<Self, CompileError> {
        let Some(args) = args else {
            return Err(CompileError::new_with_span(
                "no attribute `template` found",
                None,
                Some(ast.ident.span()),
            ));
        };
        Ok(Self {
            source: match args.source {
                Some(PartialTemplateArgsSource::Path(s)) => {
                    (Source::Path(s.value().into()), Some(s.span()))
                }
                Some(PartialTemplateArgsSource::Source(s)) => {
                    (Source::Source(s.value().into()), Some(s.span()))
                }
                #[cfg(feature = "code-in-doc")]
                Some(PartialTemplateArgsSource::InDoc(span, source)) => (source, Some(span)),
                None => {
                    return Err(CompileError::no_file_info(
                        #[cfg(not(feature = "code-in-doc"))]
                        "specify one template argument `path` or `source`",
                        #[cfg(feature = "code-in-doc")]
                        "specify one template argument `path`, `source` or `in_doc`",
                        Some(args.template.span()),
                    ));
                }
            },
            block: args.block.map(|value| (value.value(), value.span())),
            #[cfg(feature = "blocks")]
            blocks: args
                .blocks
                .unwrap_or_default()
                .into_iter()
                .map(|value| Block {
                    name: value.value(),
                    span: value.span(),
                })
                .collect(),
            print: args.print.unwrap_or_default(),
            escaping: args.escape.map(|value| value.value()),
            ext: args.ext.as_ref().map(|value| value.value()),
            ext_span: args.ext.as_ref().map(|value| value.span()),
            syntax: args.syntax.map(|value| value.value()),
            config: args.config.as_ref().map(|value| value.value()),
            crate_name: args.crate_name,
            whitespace: args.whitespace,
            template_span: Some(args.template.span()),
            config_span: args.config.as_ref().map(|value| value.span()),
        })
    }

    pub(cratefn fallback() -> Self {
        Self {
            source: (Source::Source("".into()), None),
            block: None,
            #[cfg(feature = "blocks")]
            blocks: vec![],
            print: Print::default(),
            escaping: None,
            ext: Some("txt".to_string()),
            ext_span: None,
            syntax: None,
            config: None,
            crate_name: None,
            whitespace: None,
            template_span: None,
            config_span: None,
        }
    }

    pub(cratefn config_path(&self) -> Option<&str> {
        self.config.as_deref()
    }
}

/// 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))
}

#[cfg(feature = "code-in-doc")]
fn collect_comment_blocks(
    span: Span,
    docs: &[&Attribute],
    ast: &syn::DeriveInput,
) -> Result<(Option<Span>, String), CompileError> {
    let mut source_span: Option<Span> = None;
    let mut assign_span = |kv: &syn::MetaNameValue| {
        // FIXME: uncomment once <https://github.com/rust-lang/rust/issues/54725> is stable
        // let new_span = kv.path.span();
        // source_span = Some(match source_span {
        //     Some(cur_span) => cur_span.join(new_span).unwrap_or(cur_span),
        //     None => new_span,
        // });

        if source_span.is_none() {
            source_span = Some(kv.path.span());
        }
    };

    let mut 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(…)]`
        let mut 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;
        };

        assign_span(kv);
        source.push_str(value.value().as_str());
        source.push('\n');
    }
    if source.is_empty() {
        return Err(no_askama_code_block(span, ast));
    }

    Ok((source_span, source))
}

#[cfg(feature = "code-in-doc")]
fn no_askama_code_block(span: Span, ast: &syn::DeriveInput) -> CompileError {
    let kind = match &ast.data {
        syn::Data::Struct(_) => "struct",
        syn::Data::Enum(_) => "enum",
        // actually unreachable: `union`s are rejected by `TemplateArgs::new()`
        syn::Data::Union(_) => "union",
    };
    CompileError::no_file_info(
        format_args!(
            "when using `in_doc` with the value `true`, the {kind}'s documentation needs a \
             `askama` code block"
        ),
        Some(span),
    )
}

#[cfg(feature = "code-in-doc")]
fn strip_common_ws_prefix(source: String) -> String {
    let mut common_prefix_iter = source
        .lines()
        .filter_map(|s| Some(&s[..s.find(|c: char| !c.is_ascii_whitespace())?]));
    let mut common_prefix = common_prefix_iter.next().unwrap_or_default();
    for p in common_prefix_iter {
        if common_prefix.is_empty() {
            break;
        }
        let ((pos, _), _) = common_prefix
            .char_indices()
            .zip(p.char_indices())
            .take_while(|(l, r)| l == r)
            .last()
            .unwrap_or_default();
        common_prefix = &common_prefix[..pos];
    }
    if common_prefix.is_empty() {
        return source;
    }

    source
        .lines()
        .flat_map(|s| [s.get(common_prefix.len()..).unwrap_or_default(), "\n"])
        .collect()
}

#[cfg(feature = "code-in-doc")]
fn collect_askama_code_blocks(
    span: Span,
    ast: &syn::DeriveInput,
    source: String,
) -> Result<Source, CompileError> {
    use pulldown_cmark::{CodeBlockKind, Event, Parser, Tag, TagEnd};

    let mut tmpl_source = String::new();
    let mut in_askama_code = false;
    let mut had_askama_code = false;
    for e in Parser::new(&source) {
        match (in_askama_code, e) {
            (false, Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(s)))) => {
                if s.split(",")
                    .any(|s| JINJA_EXTENSIONS.contains(&s.trim_ascii()))
                {
                    in_askama_code = true;
                    had_askama_code = true;
                }
            }
            (true, Event::End(TagEnd::CodeBlock)) => in_askama_code = false,
            (true, Event::Text(text)) => tmpl_source.push_str(&text),
            _ => {}
        }
    }
    if !had_askama_code {
        return Err(no_askama_code_block(span, ast));
    }

    if tmpl_source.ends_with('\n') {
        tmpl_source.pop();
    }
    Ok(Source::Source(tmpl_source.into()))
}

struct ResultIter<I, E>(Result<I, Option<E>>);

impl<I: IntoIterator, E> From<Result<I, E>> for ResultIter<I::IntoIter, E> {
    fn from(value: Result<I, E>) -> Self {
        Self(match value {
            Ok(i) => Ok(i.into_iter()),
            Err(e) => Err(Some(e)),
        })
    }
}

impl<I: Iterator, E> Iterator for ResultIter<I, E> {
    type Item = Result<I::Item, E>;

    fn next(&mut self) -> Option<Self::Item> {
        match &mut self.0 {
            Ok(iter) => Some(Ok(iter.next()?)),
            Err(err) => Some(Err(err.take()?)),
        }
    }
}

impl<I: FusedIterator, E> FusedIterator for ResultIter<I, E> {}

#[derive(Debug, Clone, Hash, PartialEq)]
pub(crateenum Source {
    Path(Arc<str>),
    Source(Arc<str>),
}

#[derive(Clone, Copy, Debug, PartialEq, Hash)]
pub(crateenum Print {
    All,
    Ast,
    Code,
    None,
}

impl Default for Print {
    fn default() -> Self {
        Self::None
    }
}

impl FromStr for Print {
    type Err = String;

    fn from_str(s: &str) -> Result<SelfSelf::Err> {
        match s {
            "all" => Ok(Self::All),
            "ast" => Ok(Self::Ast),
            "code" => Ok(Self::Code),
            "none" => Ok(Self::None),
            _ => Err(format!("invalid value for `print` option: {s}")),
        }
    }
}

fn cyclic_graph_error(dependency_graph: &[(Arc<Path>, Arc<Path>)]) -> Result<(), CompileError> {
    Err(CompileError::no_file_info(
        format_args!(
            "cyclic dependency in graph {:#?}",
            dependency_graph
                .iter()
                .map(|e| format!("{:#?} --> {:#?}", e.0, e.1))
                .collect::<Vec<String>>()
        ),
        None,
    ))
}

pub(cratefn get_template_source(
    tpl_path: &Arc<Path>,
    import_from: Option<(&Arc<Path>, &str, &str)>,
) -> Result<Arc<str>, CompileError> {
    static CACHE: OnceLock<OnceMap<Arc<Path>, Arc<str>>> = OnceLock::new();

    CACHE.get_or_init(OnceMap::default).get_or_try_insert(
        tpl_path,
        |tpl_path| match read_to_string(tpl_path) {
            Ok(mut source) => {
                if source.ends_with('\n') {
                    let _ = source.pop();
                }
                Ok((Arc::clone(tpl_path), Arc::from(source)))
            }
            Err(err) => Err(CompileError::new(
                format_args!(
                    "unable to open template file '{}': {err}",
                    tpl_path.to_str().unwrap(),
                ),
                import_from.map(|(node_file, file_source, node_source)| {
                    FileInfo::new(node_file, Some(file_source), Some(node_source))
                }),
            )),
        },
        Arc::clone,
    )
}

pub(cratestruct PartialTemplateArgs {
    pub(crate) template: Ident,
    pub(crate) source: Option<PartialTemplateArgsSource>,
    pub(crate) block: Option<LitStr>,
    pub(crate) print: Option<Print>,
    pub(crate) escape: Option<LitStr>,
    pub(crate) ext: Option<LitStr>,
    pub(crate) syntax: Option<LitStr>,
    pub(crate) config: Option<LitStr>,
    pub(crate) whitespace: Option<Whitespace>,
    pub(crate) crate_name: Option<ExprPath>,
    #[cfg(feature = "blocks")]
    pub(crate) blocks: Option<Vec<LitStr>>,
}

#[derive(Clone)]
pub(crateenum PartialTemplateArgsSource {
    Path(LitStr),
    Source(LitStr),
    #[cfg(feature = "code-in-doc")]
    InDoc(Span, Source),
}

impl PartialTemplateArgsSource {
    pub(cratefn span(&self) -> Span {
        match self {
            Self::Path(s) => s.span(),
            Self::Source(s) => s.span(),
            #[cfg(feature = "code-in-doc")]
            Self::InDoc(s, _) => s.span(),
        }
    }
}

// implement PartialTemplateArgs::new()
const _: () = {
    impl PartialTemplateArgs {
        pub(cratefn new(
            ast: &syn::DeriveInput,
            attrs: &[Attribute],
            is_enum_variant: bool,
        ) -> Result<Option<Self>, CompileError> {
            new(ast, attrs, is_enum_variant)
        }
    }

    #[inline]
    fn new(
        ast: &syn::DeriveInput,
        attrs: &[Attribute],
        is_enum_variant: bool,
    ) -> Result<Option<PartialTemplateArgs>, CompileError> {
        // FIXME: implement once <https://github.com/rust-lang/rfcs/pull/3715> is stable
        if let syn::Data::Union(data) = &ast.data {
            return Err(CompileError::new_with_span(
                "askama templates are not supported for `union` types, only `struct` and `enum`",
                None,
                Some(data.union_token.span),
            ));
        }

        #[cfg(feature = "code-in-doc")]
        let mut meta_docs = vec![];

        let mut this = PartialTemplateArgs {
            template: Ident::new("template", Span::call_site()),
            source: None,
            block: None,
            print: None,
            escape: None,
            ext: None,
            syntax: None,
            config: None,
            whitespace: None,
            crate_name: None,
            #[cfg(feature = "blocks")]
            blocks: None,
        };
        let mut has_data = false;

        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;
                } else if ident == "blocks" {
                    if !cfg!(feature = "blocks") {
                        return Err(CompileError::no_file_info(
                            "enable feature `blocks` to use `blocks` argument",
                            Some(ident.span()),
                        ));
                    } else if 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;
                    }
                }

                let value = get_lit(ident, pair.value)?;

                if ident == "path" {
                    ensure_source_only_once(ident, &this.source)?;
                    this.source = Some(PartialTemplateArgsSource::Path(get_strlit(ident, value)?));
                } else if ident == "source" {
                    ensure_source_only_once(ident, &this.source)?;
                    this.source =
                        Some(PartialTemplateArgsSource::Source(get_strlit(ident, value)?));
                } else if ident == "in_doc" {
                    let value = get_boollit(ident, value)?;
                    if !value.value() {
                        continue;
                    }
                    ensure_source_only_once(ident, &this.source)?;

                    #[cfg(not(feature = "code-in-doc"))]
                    {
                        return Err(CompileError::no_file_info(
                            "enable feature `code-in-doc` to use `in_doc` argument",
                            Some(ident.span()),
                        ));
                    }
                    #[cfg(feature = "code-in-doc")]
                    {
                        this.source = Some(PartialTemplateArgsSource::InDoc(
                            value.span(),
                            Source::Path("".into()),
                        ));
                    }
                } else if ident == "block" {
                    set_strlit_pair(ident, value, &mut this.block)?;
                } else if ident == "print" {
                    set_parseable_string(ident, value, &mut this.print)?;
                } else if ident == "escape" {
                    set_strlit_pair(ident, value, &mut this.escape)?;
                } else if ident == "ext" {
                    set_strlit_pair(ident, value, &mut this.ext)?;
                } else if ident == "syntax" {
                    set_strlit_pair(ident, value, &mut this.syntax)?;
                } else if ident == "config" {
                    set_strlit_pair(ident, value, &mut this.config)?;
                } else if ident == "whitespace" {
                    set_parseable_string(ident, value, &mut this.whitespace)?;
                } else {
                    return Err(CompileError::no_file_info(
                        format_args!("unsupported template attribute `{ident}` found"),
                        Some(ident.span()),
                    ));
                }
            }
        }
        if !has_data {
            return Ok(None);
        }

        #[cfg(feature = "code-in-doc")]
        if let Some(PartialTemplateArgsSource::InDoc(lit_span, _)) = this.source {
            let (source, doc_span) = source_from_docs(lit_span, &meta_docs, ast)?;
            this.source = Some(PartialTemplateArgsSource::InDoc(
                doc_span.unwrap_or(lit_span),
                source,
            ));
        }

        Ok(Some(this))
    }

    fn set_strlit_pair(
        name: &Ident,
        value: ExprLit,
        dest: &mut Option<LitStr>,
    ) -> Result<(), CompileError> {
        ensure_only_once(name, dest)?;
        *dest = Some(get_strlit(name, value)?);
        Ok(())
    }

    fn set_parseable_string<T: FromStr<Err: ToString>>(
        name: &Ident,
        value: ExprLit,
        dest: &mut Option<T>,
    ) -> Result<(), CompileError> {
        ensure_only_once(name, dest)?;
        let str_value = get_strlit(name, value)?;
        *dest = Some(
            str_value
                .value()
                .parse()
                .map_err(|msg| CompileError::no_file_info(msg, Some(str_value.span())))?,
        );
        Ok(())
    }

    fn ensure_only_once<T>(name: &Ident, dest: &mut Option<T>) -> Result<(), CompileError> {
        if dest.is_none() {
            Ok(())
        } else {
            Err(CompileError::no_file_info(
                format_args!("template attribute `{name}` already set"),
                Some(name.span()),
            ))
        }
    }

    fn get_lit(name: &Ident, mut expr: Expr) -> Result<ExprLit, CompileError> {
        loop {
            match expr {
                Expr::Lit(lit) => return Ok(lit),
                Expr::Group(group) => expr = *group.expr,
                v => {
                    return Err(CompileError::no_file_info(
                        format_args!("template attribute `{name}` expects a literal"),
                        Some(v.span()),
                    ));
                }
            }
        }
    }

    fn get_strlit(name: &Ident, value: ExprLit) -> Result<LitStr, CompileError> {
        if let Lit::Str(s) = value.lit {
            Ok(s)
        } else {
            Err(CompileError::no_file_info(
                format_args!("template attribute `{name}` expects a string literal"),
                Some(value.lit.span()),
            ))
        }
    }

    fn get_boollit(name: &Ident, value: ExprLit) -> Result<LitBool, CompileError> {
        if let Lit::Bool(s) = value.lit {
            Ok(s)
        } else {
            Err(CompileError::no_file_info(
                format_args!("template attribute `{name}` expects a boolean value"),
                Some(value.lit.span()),
            ))
        }
    }

    fn get_exprpath(name: &Ident, mut expr: Expr) -> Result<ExprPath, CompileError> {
        loop {
            match expr {
                Expr::Path(path) => return Ok(path),
                Expr::Group(group) => expr = *group.expr,
                v => {
                    return Err(CompileError::no_file_info(
                        format_args!("template attribute `{name}` expects a path or identifier"),
                        Some(v.span()),
                    ));
                }
            }
        }
    }

    #[cfg(feature = "blocks")]
    fn get_exprarray(name: &Ident, mut expr: Expr) -> Result<syn::ExprArray, CompileError> {
        loop {
            match expr {
                Expr::Array(array) => return Ok(array),
                Expr::Group(group) => expr = *group.expr,
                v => {
                    return Err(CompileError::no_file_info(
                        format_args!("template attribute `{name}` expects an array"),
                        Some(v.span()),
                    ));
                }
            }
        }
    }

    fn ensure_source_only_once(
        name: &Ident,
        source: &Option<PartialTemplateArgsSource>,
    ) -> Result<(), CompileError> {
        if source.is_some() {
            return Err(CompileError::no_file_info(
                #[cfg(feature = "code-in-doc")]
                "must specify `source`, `path` or `is_doc` exactly once",
                #[cfg(not(feature = "code-in-doc"))]
                "must specify `source` or `path` exactly once",
                Some(name.span()),
            ));
        }
        Ok(())
    }
};

#[cfg(feature = "code-in-doc")]
const JINJA_EXTENSIONS: &[&str] = &["askama""j2""jinja""jinja2""rinja"];

#[test]
fn get_source() {
    let path = Config::new("", None, None, None, None)
        .and_then(|config| config.find_template("b.html", None, None))
        .unwrap();
    assert_eq!(get_template_source(&path, None).unwrap(), "bar".into());
}

Messung V0.5 in Prozent
C=86 H=93 G=89

¤ Dauer der Verarbeitung: 0.23 Sekunden  (vorverarbeitet am  2026-08-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

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.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Statistik
#Sources=434850
#Domains=661743