Repository: djc/askama
Pull request #879 (2023-10-06 10:06:51)

Refactor the top-level askama_derive structure


Patch:
diff --git a/askama_derive/src/config.rs b/askama_derive/src/config.rs
index b2a13aa..47801cd 100644
--- a/askama_derive/src/config.rs
+++ b/askama_derive/src/config.rs
@@ -6,7 +6,6 @@ use std::{env, fs};
 use serde::Deserialize;
 
 use crate::CompileError;
-
 use parser::node::Whitespace;
 use parser::Syntax;
 
diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs
index 76b775b..9031e8b 100644
--- a/askama_derive/src/generator.rs
+++ b/askama_derive/src/generator.rs
@@ -1,250 +1,19 @@
-use crate::config::{get_template_source, read_config_file, Config, WhitespaceHandling};
+use std::collections::hash_map::{Entry, HashMap};
+use std::path::{Path, PathBuf};
+use std::{cmp, hash, mem, str};
+
+use crate::config::{get_template_source, WhitespaceHandling};
 use crate::heritage::{Context, Heritage};
-use crate::input::{Print, Source, TemplateInput};
+use crate::input::{Source, TemplateInput};
 use crate::CompileError;
 
 use parser::node::{
     Call, Comment, CondTest, If, Include, Let, Lit, Loop, Match, Target, Whitespace, Ws,
 };
 use parser::{Expr, Node, Parsed};
-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)?,
-    };
-
-    let mut templates = HashMap::new();
-    find_used_templates(&input, &mut templates, source)?;
-
-    let mut contexts = HashMap::new();
-    for (path, parsed) in &templates {
-        contexts.insert(
-            path.as_path(),
-            Context::new(input.config, path, parsed.nodes())?,
-        );
-    }
-
-    let ctx = &contexts[input.path.as_path()];
-    let heritage = if !ctx.blocks.is_empty() || ctx.extends.is_some() {
-        Some(Heritage::new(ctx, &contexts))
-    } else {
-        None
-    };
-
-    if input.print == Print::Ast || input.print == Print::All {
-        eprintln!("{:?}", templates[input.path.as_path()].nodes());
-    }
-
-    let code = Generator::new(
-        &input,
-        &contexts,
-        heritage.as_ref(),
-        MapChain::new(),
-        config.whitespace,
-    )
-    .build(&contexts[input.path.as_path()])?;
-    if input.print == Print::Code || input.print == Print::All {
-        eprintln!("{code}");
-    }
-    Ok(code)
-}
-
-#[derive(Default)]
-pub(crate) struct TemplateArgs {
-    pub(crate) source: Option<Source>,
-    pub(crate) print: Print,
-    pub(crate) escaping: Option<String>,
-    pub(crate) ext: Option<String>,
-    pub(crate) syntax: Option<String>,
-    pub(crate) config_path: Option<String>,
-    pub(crate) whitespace: Option<String>,
-}
-
-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).
-        let mut 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"))?;
-
-        let mut 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(…)"),
-            };
+use quote::quote;
 
-            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" {
-                if let 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());
-                }
-            } else if ident == "source" {
-                if let 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());
-                }
-            } else if ident == "print" {
-                if let syn::Lit::Str(s) = value.lit {
-                    args.print = s.value().parse()?;
-                } else {
-                    return Err("print value must be string literal".into());
-                }
-            } else if ident == "escape" {
-                if let syn::Lit::Str(s) = value.lit {
-                    args.escaping = Some(s.value());
-                } else {
-                    return Err("escape value must be string literal".into());
-                }
-            } else if ident == "ext" {
-                if let syn::Lit::Str(s) = value.lit {
-                    args.ext = Some(s.value());
-                } else {
-                    return Err("ext value must be string literal".into());
-                }
-            } else if ident == "syntax" {
-                if let syn::Lit::Str(s) = value.lit {
-                    args.syntax = Some(s.value())
-                } else {
-                    return Err("syntax value must be string literal".into());
-                }
-            } else if ident == "config" {
-                if let syn::Lit::Str(s) = value.lit {
-                    args.config_path = Some(s.value())
-                } else {
-                    return Err("config value must be string literal".into());
-                }
-            } else if ident == "whitespace" {
-                if let 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, Parsed>,
-    source: String,
-) -> Result<(), CompileError> {
-    let mut dependency_graph = Vec::new();
-    let mut check = vec![(input.path.clone(), source)];
-    while let Some((path, source)) = check.pop() {
-        let parsed = Parsed::new(source, input.syntax)?;
-        for n in parsed.nodes() {
-            match n {
-                Node::Extends(extends) => {
-                    let extends = input.config.find_template(extends.path, 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.path, Some(&path))?;
-                    let source = get_template_source(&import)?;
-                    check.push((import, source));
-                }
-                _ => {}
-            }
-        }
-        map.insert(path, parsed);
-    }
-    Ok(())
-}
-
-struct Generator<'a> {
+pub(crate) 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
@@ -268,18 +37,14 @@ struct Generator<'a> {
     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,
 }
 
 impl<'a> Generator<'a> {
-    fn new<'n>(
+    pub(crate) fn new<'n>(
         input: &'n TemplateInput<'_>,
         contexts: &'n HashMap<&'n Path, Context<'n>>,
         heritage: Option<&'n Heritage<'_>>,
         locals: MapChain<'n, &'n str, LocalMeta>,
-        whitespace: WhitespaceHandling,
     ) -> Generator<'n> {
         Generator {
             input,
@@ -292,12 +57,11 @@ impl<'a> Generator<'a> {
             super_block: None,
             buf_writable: vec![],
             named: 0,
-            whitespace,
         }
     }
 
     // Takes a Context and generates the relevant implementations.
-    fn build(mut self, ctx: &'a Context<'_>) -> Result<String, CompileError> {
+    pub(crate) fn build(mut self, ctx: &'a Context<'_>) -> Result<String, CompileError> {
         let mut buf = Buffer::new(0);
 
         self.impl_template(ctx, &mut buf)?;
@@ -952,7 +716,7 @@ impl<'a> Generator<'a> {
                 // 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) => {
+                &Expr::Var(name) if name != "self" => {
                     let var = self.locals.resolve_or_self(name);
                     self.locals.insert(arg, LocalMeta::with_ref(var));
                 }
@@ -1027,13 +791,7 @@ impl<'a> Generator<'a> {
         // handle the include's nodes. Unfortunately we can't easily share the `includes` cache.
 
         let locals = MapChain::with_parent(&self.locals);
-        let mut child = Self::new(
-            self.input,
-            self.contexts,
-            self.heritage,
-            locals,
-            self.whitespace,
-        );
+        let mut child = Self::new(self.input, self.contexts, self.heritage, locals);
 
         let nodes = match self.contexts.get(path.as_path()) {
             Some(ctx) => ctx.nodes,
@@ -1869,7 +1627,7 @@ impl<'a> Generator<'a> {
             Some(Whitespace::Suppress) => WhitespaceHandling::Suppress,
             Some(Whitespace::Preserve) => WhitespaceHandling::Preserve,
             Some(Whitespace::Minimize) => WhitespaceHandling::Minimize,
-            None => self.whitespace,
+            None => self.input.config.whitespace,
         }
     }
 
@@ -1970,7 +1728,7 @@ impl Buffer {
 }
 
 #[derive(Clone, Default)]
-struct LocalMeta {
+pub(crate) struct LocalMeta {
     refs: Option<String>,
     initialized: bool,
 }
@@ -1994,7 +1752,7 @@ impl LocalMeta {
 // type SetChain<'a, T> = MapChain<'a, T, ()>;
 
 #[derive(Debug)]
-struct MapChain<'a, K, V>
+pub(crate) struct MapChain<'a, K, V>
 where
     K: cmp::Eq + hash::Hash,
 {
@@ -2006,13 +1764,6 @@ impl<'a, K: 'a, V: 'a> MapChain<'a, K, V>
 where
     K: cmp::Eq + hash::Hash,
 {
-    fn new() -> MapChain<'a, K, V> {
-        MapChain {
-            parent: None,
-            scopes: vec![HashMap::new()],
-        }
-    }
-
     fn with_parent<'p>(parent: &'p MapChain<'_, K, V>) -> MapChain<'p, K, V> {
         MapChain {
             parent: Some(parent),
@@ -2075,6 +1826,15 @@ impl MapChain<'_, &str, LocalMeta> {
     }
 }
 
+impl<'a, K: Eq + hash::Hash, V> Default for MapChain<'a, K, V> {
+    fn default() -> Self {
+        Self {
+            parent: None,
+            scopes: vec![HashMap::new()],
+        }
+    }
+}
+
 /// Returns `true` if enough assumptions can be made,
 /// to determine that `self` is copyable.
 fn is_copyable(expr: &Expr<'_>) -> bool {
diff --git a/askama_derive/src/heritage.rs b/askama_derive/src/heritage.rs
index ddf9b99..d75d0a5 100644
--- a/askama_derive/src/heritage.rs
+++ b/askama_derive/src/heritage.rs
@@ -3,7 +3,6 @@ use std::path::{Path, PathBuf};
 
 use crate::config::Config;
 use crate::CompileError;
-
 use parser::node::{BlockDef, Macro, Match};
 use parser::Node;
 
diff --git a/askama_derive/src/input.rs b/askama_derive/src/input.rs
index d4e8ad9..ee9b942 100644
--- a/askama_derive/src/input.rs
+++ b/askama_derive/src/input.rs
@@ -1,21 +1,23 @@
-use crate::config::Config;
-use crate::generator::TemplateArgs;
-use crate::CompileError;
-use parser::Syntax;
-
+use std::collections::hash_map::HashMap;
 use std::path::{Path, PathBuf};
 use std::str::FromStr;
 
 use mime::Mime;
+use quote::ToTokens;
+use syn::punctuated::Punctuated;
+
+use crate::config::{get_template_source, read_config_file, Config};
+use crate::CompileError;
+use parser::{Node, Parsed, Syntax};
 
 pub(crate) struct TemplateInput<'a> {
     pub(crate) ast: &'a syn::DeriveInput,
     pub(crate) config: &'a Config<'a>,
     pub(crate) syntax: &'a Syntax<'a>,
-    pub(crate) source: Source,
+    pub(crate) source: &'a Source,
     pub(crate) print: Print,
     pub(crate) escaper: &'a str,
-    pub(crate) ext: Option<String>,
+    pub(crate) ext: Option<&'a str>,
     pub(crate) mime_type: String,
     pub(crate) path: PathBuf,
 }
@@ -27,7 +29,7 @@ impl TemplateInput<'_> {
     pub(crate) fn new<'n>(
         ast: &'n syn::DeriveInput,
         config: &'n Config<'_>,
-        args: TemplateArgs,
+        args: &'n TemplateArgs,
     ) -> Result<TemplateInput<'n>, CompileError> {
         let TemplateArgs {
             source,
@@ -41,7 +43,9 @@ impl TemplateInput<'_> {
         // 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 source = source.expect("template path or source not found in attributes");
+        let source = source
+            .as_ref()
+            .expect("template path or source not found in attributes");
         let path = match (&source, &ext) {
             (Source::Path(path), _) => config.find_template(path, None)?,
             (&Source::Source(_), Some(ext)) => PathBuf::from(format!("{}.{}", ast.ident, ext)),
@@ -51,28 +55,25 @@ impl TemplateInput<'_> {
         };
 
         // Validate syntax
-        let syntax = syntax.map_or_else(
+        let syntax = syntax.as_deref().map_or_else(
             || Ok(config.syntaxes.get(config.default_syntax).unwrap()),
             |s| {
                 config
                     .syntaxes
-                    .get(&s)
+                    .get(s)
                     .ok_or_else(|| CompileError::from(format!("attribute syntax {s} not exist")))
             },
         )?;
 
         // Match extension against defined output formats
 
-        let escaping = escaping.unwrap_or_else(|| {
-            path.extension()
-                .map(|s| s.to_str().unwrap())
-                .unwrap_or("")
-                .to_string()
-        });
+        let escaping = escaping
+            .as_deref()
+            .unwrap_or_else(|| path.extension().map(|s| s.to_str().unwrap()).unwrap_or(""));
 
         let mut escaper = None;
         for (extensions, path) in &config.escapers {
-            if extensions.contains(&escaping) {
+            if extensions.contains(escaping) {
                 escaper = Some(path);
                 break;
             }
@@ -91,17 +92,192 @@ impl TemplateInput<'_> {
             config,
             syntax,
             source,
-            print,
+            print: *print,
             escaper,
-            ext,
+            ext: ext.as_deref(),
             mime_type,
             path,
         })
     }
 
+    pub(crate) fn find_used_templates(
+        &self,
+        map: &mut HashMap<PathBuf, Parsed>,
+    ) -> Result<(), CompileError> {
+        let source = match &self.source {
+            Source::Source(s) => s.clone(),
+            Source::Path(_) => get_template_source(&self.path)?,
+        };
+
+        let mut dependency_graph = Vec::new();
+        let mut check = vec![(self.path.clone(), source)];
+        while let Some((path, source)) = check.pop() {
+            let parsed = Parsed::new(source, self.syntax)?;
+            for n in parsed.nodes() {
+                match n {
+                    Node::Extends(extends) => {
+                        let extends = self.config.find_template(extends.path, 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 = self.config.find_template(import.path, Some(&path))?;
+                        let source = get_template_source(&import)?;
+                        check.push((import, source));
+                    }
+                    _ => {}
+                }
+            }
+            map.insert(path, parsed);
+        }
+        Ok(())
+    }
+
     #[inline]
     pub(crate) fn extension(&self) -> Option<&str> {
-        ext_default_to_path(self.ext.as_deref(), &self.path)
+        ext_default_to_path(self.ext, &self.path)
+    }
+}
+
+#[derive(Debug, Default)]
+pub(crate) struct TemplateArgs {
+    source: Option<Source>,
+    print: Print,
+    escaping: Option<String>,
+    ext: Option<String>,
+    syntax: Option<String>,
+    config: String,
+    whitespace: Option<String>,
+}
+
+impl TemplateArgs {
+    pub(crate) fn new(ast: &'_ syn::DeriveInput) -> Result<Self, CompileError> {
+        // Check that an attribute called `template()` exists once and that it is
+        // the proper type (list).
+        let mut 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"))?;
+
+        let mut 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" {
+                if let 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());
+                }
+            } else if ident == "source" {
+                if let 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());
+                }
+            } else if ident == "print" {
+                if let syn::Lit::Str(s) = value.lit {
+                    args.print = s.value().parse()?;
+                } else {
+                    return Err("print value must be string literal".into());
+                }
+            } else if ident == "escape" {
+                if let syn::Lit::Str(s) = value.lit {
+                    args.escaping = Some(s.value());
+                } else {
+                    return Err("escape value must be string literal".into());
+                }
+            } else if ident == "ext" {
+                if let syn::Lit::Str(s) = value.lit {
+                    args.ext = Some(s.value());
+                } else {
+                    return Err("ext value must be string literal".into());
+                }
+            } else if ident == "syntax" {
+                if let syn::Lit::Str(s) = value.lit {
+                    args.syntax = Some(s.value())
+                } else {
+                    return Err("syntax value must be string literal".into());
+                }
+            } else if ident == "config" {
+                if let syn::Lit::Str(s) = value.lit {
+                    args.config = read_config_file(Some(&s.value()))?;
+                } else {
+                    return Err("config value must be string literal".into());
+                }
+            } else if ident == "whitespace" {
+                if let 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)
+    }
+
+    pub(crate) fn config(&self) -> Result<Config<'_>, CompileError> {
+        Config::new(&self.config, self.whitespace.as_ref())
     }
 }
 
@@ -124,12 +300,13 @@ fn extension(path: &Path) -> Option<&str> {
     }
 }
 
+#[derive(Debug)]
 pub(crate) enum Source {
     Path(String),
     Source(String),
 }
 
-#[derive(PartialEq)]
+#[derive(Clone, Copy, Debug, PartialEq)]
 pub(crate) enum Print {
     All,
     Ast,
diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs
index 8a737aa..547a449 100644
--- a/askama_derive/src/lib.rs
+++ b/askama_derive/src/lib.rs
@@ -1,8 +1,8 @@
 #![deny(elided_lifetimes_in_paths)]
 #![deny(unreachable_pub)]
 
-use std::borrow::Cow;
 use std::fmt;
+use std::{borrow::Cow, collections::HashMap};
 
 use proc_macro::TokenStream;
 use proc_macro2::Span;
@@ -11,12 +11,61 @@ use parser::ParseError;
 
 mod config;
 mod generator;
+use generator::{Generator, MapChain};
 mod heritage;
+use heritage::{Context, Heritage};
 mod input;
+use input::{Print, TemplateArgs, TemplateInput};
 
 #[proc_macro_derive(Template, attributes(template))]
 pub fn derive_template(input: TokenStream) -> TokenStream {
-    generator::derive_template(input)
+    let ast = syn::parse::<syn::DeriveInput>(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.
+pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result<String, CompileError> {
+    let template_args = TemplateArgs::new(ast)?;
+    let config = template_args.config()?;
+    let input = TemplateInput::new(ast, &config, &template_args)?;
+
+    let mut templates = HashMap::new();
+    input.find_used_templates(&mut templates)?;
+
+    let mut contexts = HashMap::new();
+    for (path, parsed) in &templates {
+        contexts.insert(
+            path.as_path(),
+            Context::new(input.config, path, parsed.nodes())?,
+        );
+    }
+
+    let ctx = &contexts[input.path.as_path()];
+    let heritage = if !ctx.blocks.is_empty() || ctx.extends.is_some() {
+        Some(Heritage::new(ctx, &contexts))
+    } else {
+        None
+    };
+
+    if input.print == Print::Ast || input.print == Print::All {
+        eprintln!("{:?}", templates[input.path.as_path()].nodes());
+    }
+
+    let code = Generator::new(&input, &contexts, heritage.as_ref(), MapChain::default())
+        .build(&contexts[input.path.as_path()])?;
+    if input.print == Print::Code || input.print == Print::All {
+        eprintln!("{code}");
+    }
+    Ok(code)
 }
 
 #[derive(Debug, Clone)]
diff --git a/askama_parser/src/expr.rs b/askama_parser/src/expr.rs
index 3b6bced..7760a98 100644
--- a/askama_parser/src/expr.rs
+++ b/askama_parser/src/expr.rs
@@ -5,18 +5,19 @@ use nom::bytes::complete::{tag, take_till};
 use nom::character::complete::char;
 use nom::combinator::{cut, map, not, opt, peek, recognize};
 use nom::error::ErrorKind;
+use nom::error_position;
 use nom::multi::{fold_many0, many0, separated_list0};
 use nom::sequence::{pair, preceded, terminated, tuple};
-use nom::{error_position, IResult};
 
 use super::{
     char_lit, identifier, not_ws, num_lit, path_or_identifier, str_lit, ws, Level, PathOrIdentifier,
 };
+use crate::ParseResult;
 
 macro_rules! expr_prec_layer {
     ( $name:ident, $inner:ident, $op:expr ) => {
-        fn $name(i: &'a str, level: Level) -> IResult<&'a str, Self> {
-            let level = level.nest(i)?;
+        fn $name(i: &'a str, level: Level) -> ParseResult<'a, Self> {
+            let (_, level) = level.nest(i)?;
             let (i, left) = Self::$inner(i, level)?;
             let (i, right) = many0(pair(
                 ws(tag($op)),
@@ -31,8 +32,8 @@ macro_rules! expr_prec_layer {
         }
     };
     ( $name:ident, $inner:ident, $( $op:expr ),+ ) => {
-        fn $name(i: &'a str, level: Level) -> IResult<&'a str, Self> {
-            let level = level.nest(i)?;
+        fn $name(i: &'a str, level: Level) -> ParseResult<'a, Self> {
+            let (_, level) = level.nest(i)?;
             let (i, left) = Self::$inner(i, level)?;
             let (i, right) = many0(pair(
                 ws(alt(($( tag($op) ),+,))),
@@ -71,8 +72,8 @@ pub enum Expr<'a> {
 }
 
 impl<'a> Expr<'a> {
-    pub(super) fn arguments(i: &'a str, level: Level) -> IResult<&'a str, Vec<Self>> {
-        let level = level.nest(i)?;
+    pub(super) fn arguments(i: &'a str, level: Level) -> ParseResult<'a, Vec<Self>> {
+        let (_, level) = level.nest(i)?;
         preceded(
             ws(char('(')),
             cut(terminated(
@@ -82,8 +83,8 @@ impl<'a> Expr<'a> {
         )(i)
     }
 
-    pub(super) fn parse(i: &'a str, level: Level) -> IResult<&'a str, Self> {
-        let level = level.nest(i)?;
+    pub(super) fn parse(i: &'a str, level: Level) -> ParseResult<'a, Self> {
+        let (_, level) = level.nest(i)?;
         let range_right = move |i| {
             pair(
                 ws(alt((tag("..="), tag("..")))),
@@ -114,9 +115,10 @@ impl<'a> Expr<'a> {
     expr_prec_layer!(addsub, muldivmod, "+", "-");
     expr_prec_layer!(muldivmod, filtered, "*", "/", "%");
 
-    fn filtered(i: &'a str, level: Level) -> IResult<&'a str, Self> {
-        let level = level.nest(i)?;
-        fn filter(i: &str, level: Level) -> IResult<&str, (&str, Option<Vec<Expr<'_>>>)> {
+    fn filtered(i: &'a str, level: Level) -> ParseResult<'a, Self> {
+        let (_, level) = level.nest(i)?;
+        #[allow(clippy::type_complexity)]
+        fn filter(i: &str, level: Level) -> ParseResult<'_, (&str, Option<Vec<Expr<'_>>>)> {
             let (i, (_, fname, args)) = tuple((
                 char('|'),
                 ws(identifier),
@@ -143,8 +145,8 @@ impl<'a> Expr<'a> {
         Ok((i, res))
     }
 
-    fn prefix(i: &'a str, level: Level) -> IResult<&'a str, Self> {
-        let level = level.nest(i)?;
+    fn prefix(i: &'a str, level: Level) -> ParseResult<'a, Self> {
+        let (_, level) = level.nest(i)?;
         let (i, (ops, mut expr)) = pair(many0(ws(alt((tag("!"), tag("-"))))), |i| {
             Suffix::parse(i, level)
         })(i)?;
@@ -154,8 +156,8 @@ impl<'a> Expr<'a> {
         Ok((i, expr))
     }
 
-    fn single(i: &'a str, level: Level) -> IResult<&'a str, Self> {
-        let level = level.nest(i)?;
+    fn single(i: &'a str, level: Level) -> ParseResult<'a, Self> {
+        let (_, level) = level.nest(i)?;
         alt((
             Self::num,
             Self::str,
@@ -166,8 +168,8 @@ impl<'a> Expr<'a> {
         ))(i)
     }
 
-    fn group(i: &'a str, level: Level) -> IResult<&'a str, Self> {
-        let level = level.nest(i)?;
+    fn group(i: &'a str, level: Level) -> ParseResult<'a, Self> {
+        let (_, level) = level.nest(i)?;
         let (i, expr) = preceded(ws(char('(')), opt(|i| Self::parse(i, level)))(i)?;
         let expr = match expr {
             Some(expr) => expr,
@@ -195,8 +197,8 @@ impl<'a> Expr<'a> {
         Ok((i, Self::Tuple(exprs)))
     }
 
-    fn array(i: &'a str, level: Level) -> IResult<&'a str, Self> {
-        let level = level.nest(i)?;
+    fn array(i: &'a str, level: Level) -> ParseResult<'a, Self> {
+        let (_, level) = level.nest(i)?;
         preceded(
             ws(char('[')),
             cut(terminated(
@@ -209,7 +211,7 @@ impl<'a> Expr<'a> {
         )(i)
     }
 
-    fn path_var_bool(i: &'a str) -> IResult<&'a str, Self> {
+    fn path_var_bool(i: &'a str) -> ParseResult<'a, Self> {
         map(path_or_identifier, |v| match v {
             PathOrIdentifier::Path(v) => Self::Path(v),
             PathOrIdentifier::Identifier(v @ "true") => Self::BoolLit(v),
@@ -218,15 +220,15 @@ impl<'a> Expr<'a> {
         })(i)
     }
 
-    fn str(i: &'a str) -> IResult<&'a str, Self> {
+    fn str(i: &'a str) -> ParseResult<'a, Self> {
         map(str_lit, Self::StrLit)(i)
     }
 
-    fn num(i: &'a str) -> IResult<&'a str, Self> {
+    fn num(i: &'a str) -> ParseResult<'a, Self> {
         map(num_lit, Self::NumLit)(i)
     }
 
-    fn char(i: &'a str) -> IResult<&'a str, Self> {
+    fn char(i: &'a str) -> ParseResult<'a, Self> {
         map(char_lit, Self::CharLit)(i)
     }
 }
@@ -241,8 +243,8 @@ enum Suffix<'a> {
 }
 
 impl<'a> Suffix<'a> {
-    fn parse(i: &'a str, level: Level) -> IResult<&'a str, Expr<'a>> {
-        let level = level.nest(i)?;
+    fn parse(i: &'a str, level: Level) -> ParseResult<'a, Expr<'a>> {
+        let (_, level) = level.nest(i)?;
         let (mut i, mut expr) = Expr::single(i, level)?;
         loop {
             let (j, suffix) = opt(alt((
@@ -271,8 +273,8 @@ impl<'a> Suffix<'a> {
         Ok((i, expr))
     }
 
-    fn r#macro(i: &'a str) -> IResult<&'a str, Self> {
-        fn nested_parenthesis(input: &str) -> IResult<&str, ()> {
+    fn r#macro(i: &'a str) -> ParseResult<'a, Self> {
+        fn nested_parenthesis(input: &str) -> ParseResult<'_, ()> {
             let mut nested = 0;
             let mut last = 0;
             let mut in_str = false;
@@ -329,7 +331,7 @@ impl<'a> Suffix<'a> {
         )(i)
     }
 
-    fn attr(i: &'a str) -> IResult<&'a str, Self> {
+    fn attr(i: &'a str) -> ParseResult<'a, Self> {
         map(
             preceded(
                 ws(pair(char('.'), not(char('.')))),
@@ -339,8 +341,8 @@ impl<'a> Suffix<'a> {
         )(i)
     }
 
-    fn index(i: &'a str, level: Level) -> IResult<&'a str, Self> {
-        let level = level.nest(i)?;
+    fn index(i: &'a str, level: Level) -> ParseResult<'a, Self> {
+        let (_, level) = level.nest(i)?;
         map(
             preceded(
                 ws(char('[')),
@@ -350,12 +352,12 @@ impl<'a> Suffix<'a> {
         )(i)
     }
 
-    fn call(i: &'a str, level: Level) -> IResult<&'a str, Self> {
-        let level = level.nest(i)?;
+    fn call(i: &'a str, level: Level) -> ParseResult<'a, Self> {
+        let (_, level) = level.nest(i)?;
         map(move |i| Expr::arguments(i, level), Self::Call)(i)
     }
 
-    fn r#try(i: &'a str) -> IResult<&'a str, Self> {
+    fn r#try(i: &'a str) -> ParseResult<'a, Self> {
         map(preceded(take_till(not_ws), char('?')), |_| Self::Try)(i)
     }
 }
diff --git a/askama_parser/src/lib.rs b/askama_parser/src/lib.rs
index 78b0807..ff26483 100644
--- a/askama_parser/src/lib.rs
+++ b/askama_parser/src/lib.rs
@@ -1,6 +1,7 @@
 #![deny(unreachable_pub)]
 #![deny(elided_lifetimes_in_paths)]
 
+use std::borrow::Cow;
 use std::cell::Cell;
 use std::{fmt, str};
 
@@ -9,10 +10,10 @@ use nom::bytes::complete::{escaped, is_not, tag, take_till};
 use nom::character::complete::char;
 use nom::character::complete::{anychar, digit1};
 use nom::combinator::{cut, eof, map, opt, recognize};
-use nom::error::ErrorKind;
+use nom::error::{Error, ErrorKind, FromExternalError};
 use nom::multi::many1;
 use nom::sequence::{delimited, pair, preceded, terminated, tuple};
-use nom::{error_position, AsChar, IResult, InputTakeAtPosition};
+use nom::{error_position, AsChar, InputTakeAtPosition};
 
 pub mod expr;
 pub use expr::Expr;
@@ -76,14 +77,16 @@ pub struct Ast<'a> {
 impl<'a> Ast<'a> {
     pub fn from_str(src: &'a str, syntax: &Syntax<'_>) -> Result<Self, ParseError> {
         let parse = |i: &'a str| Node::many(i, &State::new(syntax));
-        let err = match terminated(parse, cut(eof))(src) {
+        let (input, message) = match terminated(parse, cut(eof))(src) {
             Ok(("", nodes)) => return Ok(Self { nodes }),
             Ok(_) => unreachable!("eof() is not eof?"),
-            Err(nom::Err::Error(err)) | Err(nom::Err::Failure(err)) => err,
+            Err(
+                nom::Err::Error(ErrorContext { input, message, .. })
+                | nom::Err::Failure(ErrorContext { input, message, .. }),
+            ) => (input, message),
             Err(nom::Err::Incomplete(_)) => return Err(ParseError("parsing incomplete".into())),
         };
 
-        let nom::error::Error { input, .. } = err;
         let offset = src.len() - input.len();
         let (source_before, source_after) = src.split_at(offset);
 
@@ -96,7 +99,12 @@ impl<'a> Ast<'a> {
         let column = last_line.chars().count();
 
         let msg = format!(
-            "problems parsing template source at row {}, column {} near:\n{}",
+            "{}problems parsing template source at row {}, column {} near:\n{}",
+            if let Some(message) = message {
+                format!("{message}\n")
+            } else {
+                String::new()
+            },
             row + 1,
             column,
             source_after,
@@ -121,6 +129,57 @@ impl fmt::Display for ParseError {
     }
 }
 
+pub(crate) type ParseResult<'a, T = &'a str> = Result<(&'a str, T), nom::Err<ErrorContext<'a>>>;
+
+/// This type is used to handle `nom` errors and in particular to add custom error messages.
+/// It used to generate `ParserError`.
+///
+/// It cannot be used to replace `ParseError` because it expects a generic, which would make
+/// `askama`'s users experience less good (since this generic is only needed for `nom`).
+#[derive(Debug)]
+pub(crate) struct ErrorContext<'a> {
+    pub(crate) input: &'a str,
+    pub(crate) message: Option<Cow<'static, str>>,
+}
+
+impl<'a> nom::error::ParseError<&'a str> for ErrorContext<'a> {
+    fn from_error_kind(input: &'a str, _code: ErrorKind) -> Self {
+        Self {
+            input,
+            message: None,
+        }
+    }
+
+    fn append(_: &'a str, _: ErrorKind, other: Self) -> Self {
+        other
+    }
+}
+
+impl<'a, E: std::fmt::Display> FromExternalError<&'a str, E> for ErrorContext<'a> {
+    fn from_external_error(input: &'a str, _kind: ErrorKind, e: E) -> Self {
+        Self {
+            input,
+            message: Some(Cow::Owned(e.to_string())),
+        }
+    }
+}
+
+impl<'a> ErrorContext<'a> {
+    pub(crate) fn from_err(error: nom::Err<Error<&'a str>>) -> nom::Err<Self> {
+        match error {
+            nom::Err::Incomplete(i) => nom::Err::Incomplete(i),
+            nom::Err::Failure(Error { input, .. }) => nom::Err::Failure(Self {
+                input,
+                message: None,
+            }),
+            nom::Err::Error(Error { input, .. }) => nom::Err::Error(Self {
+                input,
+                message: None,
+            }),
+        }
+    }
+}
+
 fn is_ws(c: char) -> bool {
     matches!(c, ' ' | '\t' | '\r' | '\n')
 }
@@ -130,16 +189,16 @@ fn not_ws(c: char) -> bool {
 }
 
 fn ws<'a, O>(
-    inner: impl FnMut(&'a str) -> IResult<&'a str, O>,
-) -> impl FnMut(&'a str) -> IResult<&'a str, O> {
+    inner: impl FnMut(&'a str) -> ParseResult<'a, O>,
+) -> impl FnMut(&'a str) -> ParseResult<'a, O> {
     delimited(take_till(not_ws), inner, take_till(not_ws))
 }
 
 /// Skips input until `end` was found, but does not consume it.
 /// Returns tuple that would be returned when parsing `end`.
 fn skip_till<'a, O>(
-    end: impl FnMut(&'a str) -> IResult<&'a str, O>,
-) -> impl FnMut(&'a str) -> IResult<&'a str, (&'a str, O)> {
+    end: impl FnMut(&'a str) -> ParseResult<'a, O>,
+) -> impl FnMut(&'a str) -> ParseResult<'a, (&'a str, O)> {
     enum Next<O> {
         IsEnd(O),
         NotEnd(char),
@@ -157,8 +216,8 @@ fn skip_till<'a, O>(
     }
 }
 
-fn keyword<'a>(k: &'a str) -> impl FnMut(&'a str) -> IResult<&'a str, &'a str> {
-    move |i: &'a str| -> IResult<&'a str, &'a str> {
+fn keyword<'a>(k: &'a str) -> impl FnMut(&'a str) -> ParseResult<'_> {
+    move |i: &'a str| -> ParseResult<'a> {
         let (j, v) = identifier(i)?;
         if k == v {
             Ok((j, v))
@@ -168,15 +227,15 @@ fn keyword<'a>(k: &'a str) -> impl FnMut(&'a str) -> IResult<&'a str, &'a str> {
     }
 }
 
-fn identifier(input: &str) -> IResult<&str, &str> {
-    fn start(s: &str) -> IResult<&str, &str> {
+fn identifier(input: &str) -> ParseResult<'_> {
+    fn start(s: &str) -> ParseResult<'_> {
         s.split_at_position1_complete(
             |c| !(c.is_alpha() || c == '_' || c >= '\u{0080}'),
             nom::error::ErrorKind::Alpha,
         )
     }
 
-    fn tail(s: &str) -> IResult<&str, &str> {
+    fn tail(s: &str) -> ParseResult<'_> {
         s.split_at_position1_complete(
             |c| !(c.is_alphanum() || c == '_' || c >= '\u{0080}'),
             nom::error::ErrorKind::Alpha,
@@ -186,11 +245,11 @@ fn identifier(input: &str) -> IResult<&str, &str> {
     recognize(pair(start, opt(tail)))(input)
 }
 
-fn bool_lit(i: &str) -> IResult<&str, &str> {
+fn bool_lit(i: &str) -> ParseResult<'_> {
     alt((keyword("false"), keyword("true")))(i)
 }
 
-fn num_lit(i: &str) -> IResult<&str, &str> {
+fn num_lit(i: &str) -> ParseResult<'_> {
     recognize(tuple((
         opt(char('-')),
         digit1,
@@ -198,7 +257,7 @@ fn num_lit(i: &str) -> IResult<&str, &str> {
     )))(i)
 }
 
-fn str_lit(i: &str) -> IResult<&str, &str> {
+fn str_lit(i: &str) -> ParseResult<'_> {
     let (i, s) = delimited(
         char('"'),
         opt(escaped(is_not("\\\""), '\\', anychar)),
@@ -207,7 +266,7 @@ fn str_lit(i: &str) -> IResult<&str, &str> {
     Ok((i, s.unwrap_or_default()))
 }
 
-fn char_lit(i: &str) -> IResult<&str, &str> {
+fn char_lit(i: &str) -> ParseResult<'_> {
     let (i, s) = delimited(
         char('\''),
         opt(escaped(is_not("\\\'"), '\\', anychar)),
@@ -221,7 +280,7 @@ enum PathOrIdentifier<'a> {
     Identifier(&'a str),
 }
 
-fn path_or_identifier(i: &str) -> IResult<&str, PathOrIdentifier<'_>> {
+fn path_or_identifier(i: &str) -> ParseResult<'_, PathOrIdentifier<'_>> {
     let root = ws(opt(tag("::")));
     let tail = opt(many1(preceded(ws(tag("::")), identifier)));
 
@@ -267,36 +326,37 @@ impl<'a> State<'a> {
         }
     }
 
-    fn nest<'b>(&self, i: &'b str) -> Result<(), nom::Err<nom::error::Error<&'b str>>> {
-        self.level.set(self.level.get().nest(i)?);
-        Ok(())
+    fn nest<'b>(&self, i: &'b str) -> ParseResult<'b, ()> {
+        let (_, level) = self.level.get().nest(i)?;
+        self.level.set(level);
+        Ok((i, ()))
     }
 
     fn leave(&self) {
         self.level.set(self.level.get().leave());
     }
 
-    fn tag_block_start<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str> {
+    fn tag_block_start<'i>(&self, i: &'i str) -> ParseResult<'i> {
         tag(self.syntax.block_start)(i)
     }
 
-    fn tag_block_end<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str> {
+    fn tag_block_end<'i>(&self, i: &'i str) -> ParseResult<'i> {
         tag(self.syntax.block_end)(i)
     }
 
-    fn tag_comment_start<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str> {
+    fn tag_comment_start<'i>(&self, i: &'i str) -> ParseResult<'i> {
         tag(self.syntax.comment_start)(i)
     }
 
-    fn tag_comment_end<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str> {
+    fn tag_comment_end<'i>(&self, i: &'i str) -> ParseResult<'i> {
         tag(self.syntax.comment_end)(i)
     }
 
-    fn tag_expr_start<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str> {
+    fn tag_expr_start<'i>(&self, i: &'i str) -> ParseResult<'i> {
         tag(self.syntax.expr_start)(i)
     }
 
-    fn tag_expr_end<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str> {
+    fn tag_expr_end<'i>(&self, i: &'i str) -> ParseResult<'i> {
         tag(self.syntax.expr_end)(i)
     }
 
@@ -340,12 +400,15 @@ impl Default for Syntax<'static> {
 pub(crate) struct Level(u8);
 
 impl Level {
-    fn nest(self, i: &str) -> Result<Level, nom::Err<nom::error::Error<&str>>> {
+    fn nest(self, i: &str) -> ParseResult<'_, Level> {
         if self.0 >= Self::MAX_DEPTH {
-            return Err(nom::Err::Failure(error_position!(i, ErrorKind::TooLarge)));
+            return Err(ErrorContext::from_err(nom::Err::Failure(error_position!(
+                i,
+                ErrorKind::TooLarge
+            ))));
         }
 
-        Ok(Level(self.0 + 1))
+        Ok((i, Level(self.0 + 1)))
     }
 
     fn leave(&self) -> Self {
diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs
index ba4d09e..69703e6 100644
--- a/askama_parser/src/node.rs
+++ b/askama_parser/src/node.rs
@@ -1,3 +1,4 @@
+use std::borrow::Cow;
 use std::str;
 
 use nom::branch::alt;
@@ -7,9 +8,11 @@ use nom::combinator::{
     complete, consumed, cut, eof, map, map_res, not, opt, peek, recognize, value,
 };
 use nom::error::{Error, ErrorKind};
+use nom::error_position;
 use nom::multi::{fold_many0, many0, many1, separated_list0, separated_list1};
 use nom::sequence::{delimited, pair, preceded, terminated, tuple};
-use nom::{error_position, IResult};
+
+use crate::{ErrorContext, ParseResult};
 
 use super::{
     bool_lit, char_lit, identifier, is_ws, keyword, num_lit, path_or_identifier, skip_till,
@@ -37,7 +40,7 @@ pub enum Node<'a> {
 }
 
 impl<'a> Node<'a> {
-    pub(super) fn many(i: &'a str, s: &State<'_>) -> IResult<&'a str, Vec<Self>> {
+    pub(super) fn many(i: &'a str, s: &State<'_>) -> ParseResult<'a, Vec<Self>> {
         complete(many0(alt((
             map(|i| Lit::parse(i, s), Self::Lit),
             map(|i| Comment::parse(i, s), Self::Comment),
@@ -46,7 +49,7 @@ impl<'a> Node<'a> {
         ))))(i)
     }
 
-    fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
+    fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
         let mut p = delimited(
             |i| s.tag_block_start(i),
             alt((
@@ -74,7 +77,7 @@ impl<'a> Node<'a> {
         result
     }
 
-    fn r#break(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
+    fn r#break(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
         let mut p = tuple((
             opt(Whitespace::parse),
             ws(keyword("break")),
@@ -87,7 +90,7 @@ impl<'a> Node<'a> {
         Ok((j, Self::Break(Ws(pws, nws))))
     }
 
-    fn r#continue(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
+    fn r#continue(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
         let mut p = tuple((
             opt(Whitespace::parse),
             ws(keyword("continue")),
@@ -100,7 +103,7 @@ impl<'a> Node<'a> {
         Ok((j, Self::Continue(Ws(pws, nws))))
     }
 
-    fn expr(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
+    fn expr(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
         let mut p = tuple((
             |i| s.tag_expr_start(i),
             cut(tuple((
@@ -128,7 +131,7 @@ pub enum Target<'a> {
 }
 
 impl<'a> Target<'a> {
-    pub(super) fn parse(i: &'a str) -> IResult<&'a str, Self> {
+    pub(super) fn parse(i: &'a str) -> ParseResult<'a, Self> {
         let mut opt_opening_paren = map(opt(ws(char('('))), |o| o.is_some());
         let mut opt_closing_paren = map(opt(ws(char(')'))), |o| o.is_some());
         let mut opt_opening_brace = map(opt(ws(char('{'))), |o| o.is_some());
@@ -211,7 +214,7 @@ impl<'a> Target<'a> {
         map(identifier, Self::Name)(i)
     }
 
-    fn lit(i: &'a str) -> IResult<&'a str, Self> {
+    fn lit(i: &'a str) -> ParseResult<'a, Self> {
         alt((
             map(str_lit, Self::StrLit),
             map(char_lit, Self::CharLit),
@@ -220,7 +223,7 @@ impl<'a> Target<'a> {
         ))(i)
     }
 
-    fn named(i: &'a str) -> IResult<&str, (&str, Self)> {
+    fn named(i: &'a str) -> ParseResult<'a, (&str, Self)> {
         let (i, (src, target)) = pair(identifier, opt(preceded(ws(char(':')), Self::parse)))(i)?;
         Ok((i, (src, target.unwrap_or(Self::Name(src)))))
     }
@@ -234,7 +237,7 @@ pub struct When<'a> {
 }
 
 impl<'a> When<'a> {
-    fn r#match(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
+    fn r#match(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
         let mut p = tuple((
             |i| s.tag_block_start(i),
             opt(Whitespace::parse),
@@ -257,7 +260,7 @@ impl<'a> When<'a> {
     }
 
     #[allow(clippy::self_named_constructors)]
-    fn when(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
+    fn when(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
         let mut p = tuple((
             |i| s.tag_block_start(i),
             opt(Whitespace::parse),
@@ -289,11 +292,19 @@ pub struct Cond<'a> {
 }
 
 impl<'a> Cond<'a> {
-    fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
+    fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
         let mut p = tuple((
             |i| s.tag_block_start(i),
             opt(Whitespace::parse),
-            ws(keyword("else")),
+            ws(alt((keyword("else"), |i| {
+                let _ = keyword("elif")(i)?;
+                Err(nom::Err::Failure(ErrorContext {
+                    input: i,
+                    message: Some(Cow::Borrowed(
+                        "unknown `elif` keyword; did you mean `else if`?",
+                    )),
+                }))
+            }))),
             cut(tuple((
                 opt(|i| CondTest::parse(i, s)),
                 opt(Whitespace::parse),
@@ -320,7 +331,7 @@ pub struct CondTest<'a> {
 }
 
 impl<'a> CondTest<'a> {
-    fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
+    fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
         let mut p = preceded(
             ws(keyword("if")),
             cut(tuple((
@@ -345,7 +356,7 @@ pub enum Whitespace {
 }
 
 impl Whitespace {
-    fn parse(i: &str) -> IResult<&str, Self> {
+    fn parse(i: &str) -> ParseResult<'_, Self> {
         alt((
             value(Self::Preserve, char('+')),
             value(Self::Suppress, char('-')),
@@ -367,8 +378,8 @@ pub struct Loop<'a> {
 }
 
 impl<'a> Loop<'a> {
-    fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
-        fn content<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Vec<Node<'a>>> {
+    fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
+        fn content<'a>(i: &'a str, s: &State<'_>) -> ParseResult<'a, Vec<Node<'a>>> {
             s.enter_loop();
             let result = Node::many(i, s);
             s.leave_loop();
@@ -448,8 +459,8 @@ pub struct Macro<'a> {
 }
 
 impl<'a> Macro<'a> {
-    fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
-        fn parameters(i: &str) -> IResult<&str, Vec<&str>> {
+    fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
+        fn parameters(i: &str) -> ParseResult<'_, Vec<&str>> {
             delimited(
                 ws(char('(')),
                 separated_list0(char(','), ws(identifier)),
@@ -475,14 +486,23 @@ impl<'a> Macro<'a> {
                 |i| s.tag_block_start(i),
                 opt(Whitespace::parse),
                 ws(keyword("endmacro")),
-                cut(preceded(ws(opt(keyword(name))), opt(Whitespace::parse))),
+                cut(preceded(
+                    opt(|before| {
+                        let (after, end_name) = ws(identifier)(before)?;
+                        check_end_name(before, after, name, end_name, "macro")
+                    }),
+                    opt(Whitespace::parse),
+                )),
             ))),
         )));
         let (i, (contents, (_, pws2, _, nws2))) = end(i)?;
 
         if name == "super" {
             // TODO: yield a a better error message here
-            return Err(nom::Err::Failure(Error::new(i, ErrorKind::Fail)));
+            return Err(ErrorContext::from_err(nom::Err::Failure(Error::new(
+                i,
+                ErrorKind::Fail,
+            ))));
         }
 
         Ok((
@@ -506,7 +526,7 @@ pub struct Import<'a> {
 }
 
 impl<'a> Import<'a> {
-    fn parse(i: &'a str) -> IResult<&'a str, Self> {
+    fn parse(i: &'a str) -> ParseResult<'a, Self> {
         let mut p = tuple((
             opt(Whitespace::parse),
             ws(keyword("import")),
@@ -537,7 +557,7 @@ pub struct Call<'a> {
 }
 
 impl<'a> Call<'a> {
-    fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
+    fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
         let mut p = tuple((
             opt(Whitespace::parse),
             ws(keyword("call")),
@@ -572,7 +592,7 @@ pub struct Match<'a> {
 }
 
 impl<'a> Match<'a> {
-    fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
+    fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
         let mut p = tuple((
             opt(Whitespace::parse),
             ws(keyword("match")),
@@ -623,7 +643,7 @@ pub struct BlockDef<'a> {
 }
 
 impl<'a> BlockDef<'a> {
-    fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
+    fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
         let mut start = tuple((
             opt(Whitespace::parse),
             ws(keyword("block")),
@@ -639,7 +659,13 @@ impl<'a> BlockDef<'a> {
                 |i| s.tag_block_start(i),
                 opt(Whitespace::parse),
                 ws(keyword("endblock")),
-                cut(tuple((opt(ws(keyword(name))), opt(Whitespace::parse)))),
+                cut(tuple((
+                    opt(|before| {
+                        let (after, end_name) = ws(identifier)(before)?;
+                        check_end_name(before, after, name, end_name, "block")
+                    }),
+                    opt(Whitespace::parse),
+                ))),
             ))),
         )));
         let (i, (nodes, (_, pws2, _, (_, nws2)))) = end(i)?;
@@ -656,6 +682,27 @@ impl<'a> BlockDef<'a> {
     }
 }
 
+fn check_end_name<'a>(
+    before: &'a str,
+    after: &'a str,
+    name: &'a str,
+    end_name: &'a str,
+    kind: &str,
+) -> ParseResult<'a> {
+    if name == end_name {
+        return Ok((after, end_name));
+    }
+    let message = if name.is_empty() && !end_name.is_empty() {
+        format!("unexpected name `{end_name}` in `end{kind}` tag for unnamed `{kind}`")
+    } else {
+        format!("expected name `{name}` in `end{kind}` tag, found `{end_name}`")
+    };
+    Err(nom::Err::Failure(ErrorContext {
+        input: before,
+        message: Some(Cow::Owned(message)),
+    }))
+}
+
 #[derive(Debug, PartialEq)]
 pub struct Lit<'a> {
     pub lws: &'a str,
@@ -664,7 +711,7 @@ pub struct Lit<'a> {
 }
 
 impl<'a> Lit<'a> {
-    fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
+    fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
         let p_start = alt((
             tag(s.syntax.block_start),
             tag(s.syntax.comment_start),
@@ -704,7 +751,7 @@ pub struct Raw<'a> {
 }
 
 impl<'a> Raw<'a> {
-    fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
+    fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
         let endraw = tuple((
             |i| s.tag_block_start(i),
             opt(Whitespace::parse),
@@ -739,7 +786,7 @@ pub struct Let<'a> {
 }
 
 impl<'a> Let<'a> {
-    fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
+    fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
         let mut p = tuple((
             opt(Whitespace::parse),
             ws(alt((keyword("let"), keyword("set")))),
@@ -772,7 +819,7 @@ pub struct If<'a> {
 }
 
 impl<'a> If<'a> {
-    fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
+    fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
         let mut p = tuple((
             opt(Whitespace::parse),
             |i| CondTest::parse(i, s),
@@ -817,7 +864,7 @@ pub struct Include<'a> {
 }
 
 impl<'a> Include<'a> {
-    fn parse(i: &'a str) -> IResult<&'a str, Self> {
+    fn parse(i: &'a str) -> ParseResult<'a, Self> {
         let mut p = tuple((
             opt(Whitespace::parse),
             ws(keyword("include")),
@@ -840,7 +887,7 @@ pub struct Extends<'a> {
 }
 
 impl<'a> Extends<'a> {
-    fn parse(i: &'a str) -> IResult<&'a str, Self> {
+    fn parse(i: &'a str) -> ParseResult<'a, Self> {
         let (i, path) = preceded(ws(keyword("extends")), cut(ws(str_lit)))(i)?;
         Ok((i, Self { path }))
     }
@@ -853,12 +900,12 @@ pub struct Comment<'a> {
 }
 
 impl<'a> Comment<'a> {
-    fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
-        fn body<'a>(mut i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> {
+    fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
+        fn body<'a>(mut i: &'a str, s: &State<'_>) -> ParseResult<'a> {
             let mut level = 0;
             loop {
                 let (end, tail) = take_until(s.syntax.comment_end)(i)?;
-                match take_until::<_, _, Error<_>>(s.syntax.comment_start)(i) {
+                match take_until::<_, _, ErrorContext<'_>>(s.syntax.comment_start)(i) {
                     Ok((start, _)) if start.as_ptr() < end.as_ptr() => {
                         level += 1;
                         i = &start[2..];
diff --git a/askama_rocket/Cargo.toml b/askama_rocket/Cargo.toml
index 0496a34..9495d1b 100644
--- a/askama_rocket/Cargo.toml
+++ b/askama_rocket/Cargo.toml
@@ -18,7 +18,7 @@ askama = { version = "0.12", path = "../askama", default-features = false, featu
 rocket = { version = "0.5.0-rc.3", default-features = false }
 
 [dev-dependencies]
-futures-lite = "1.12.0"
+futures-lite = "2.0.0"
 
 [features]
 default = ["askama/default"]
diff --git a/book/src/template_syntax.md b/book/src/template_syntax.md
index 4483562..9716369 100644
--- a/book/src/template_syntax.md
+++ b/book/src/template_syntax.md
@@ -176,6 +176,13 @@ inheritance. Blocks can only be specified at the top level of a template
 or inside other blocks, not inside `if`/`else` branches or in `for`-loop
 bodies.
 
+It is also possible to use the name of the `block` in `endblock` (both in
+declaration and use):
+
+```html
+{% block content %}<p>Placeholder content</p>{% endblock content %}
+```
+
 ### Child template
 
 Here's an example child template:
@@ -437,9 +444,9 @@ struct Item<'a> {
 
 ## Macros
 
-You can define macros within your template by using `{% macro name(args) %}`, ending with `{% endmacro %}`
+You can define macros within your template by using `{% macro name(args) %}`, ending with `{% endmacro %}`.
 
-You can then call it later with `{% call name(args) %}`
+You can then call it with `{% call name(args) %}`:
 
 ```
 {% macro heading(arg) %}
@@ -451,10 +458,16 @@ You can then call it later with `{% call name(args) %}`
 {% call heading(s) %}
 ```
 
-You can place templates in a separate file and use it in your templates by using `{% import %}`
+You can place macros in a separate file and use them in your templates by using `{% import %}`:
 
 ```
 {%- import "macro.html" as scope -%}
 
 {% call scope::heading(s) %}
 ```
+
+You can optionally specify the name of the macro in `endmacro`:
+
+```html
+{% macro heading(arg) %}<p>{{arg}}</p>{% endmacro heading %}
+```


Test patch:
diff --git a/testing/templates/macro-self-arg.html b/testing/templates/macro-self-arg.html
new file mode 100644
index 0000000..c7c6d5b
--- /dev/null
+++ b/testing/templates/macro-self-arg.html
@@ -0,0 +1,5 @@
+{%- macro my_s(slf) -%}
+    {{ slf.s }}
+{%- endmacro -%}
+
+{%- call my_s(self) -%}
diff --git a/testing/templates/named-end.html b/testing/templates/named-end.html
new file mode 100644
index 0000000..35eb0bb
--- /dev/null
+++ b/testing/templates/named-end.html
@@ -0,0 +1,9 @@
+{% extends "base.html" %}
+{# Testing named "endmacro" #}
+{% macro foo(b) -%}
+    {% if b %}t{% else %}f{% endif -%}
+{% endmacro foo -%}
+{# Testing named endblock declaration #}
+{% block what %}{% endblock what %}
+{# Testing named endblock call #}
+{% block foo %}tadam{% endblock foo %}
diff --git a/testing/tests/inheritance.rs b/testing/tests/inheritance.rs
index 5d8aefb..906be04 100644
--- a/testing/tests/inheritance.rs
+++ b/testing/tests/inheritance.rs
@@ -340,3 +340,15 @@ fn test_let_block() {
     let t = LetChild {};
     assert_eq!(t.render().unwrap(), "1");
 }
+
+#[derive(Template)]
+#[template(path = "named-end.html")]
+struct NamedBlocks<'a> {
+    title: &'a str,
+}
+
+#[test]
+fn test_named_end() {
+    let n = NamedBlocks { title: "title" };
+    assert_eq!(n.render().unwrap(), "title\n\ntadam\nCopyright 2017");
+}
diff --git a/testing/tests/macro.rs b/testing/tests/macro.rs
index 7e910a9..faada37 100644
--- a/testing/tests/macro.rs
+++ b/testing/tests/macro.rs
@@ -83,3 +83,15 @@ fn str_cmp() {
     let t = StrCmpTemplate;
     assert_eq!(t.render().unwrap(), "AfooBotherCneitherD");
 }
+
+#[derive(Template)]
+#[template(path = "macro-self-arg.html")]
+struct MacroSelfArgTemplate<'a> {
+    s: &'a str,
+}
+
+#[test]
+fn test_macro_self_arg() {
+    let t = MacroSelfArgTemplate { s: "foo" };
+    assert_eq!(t.render().unwrap(), "foo");
+}
diff --git a/testing/tests/ui/elif.rs b/testing/tests/ui/elif.rs
new file mode 100644
index 0000000..650f292
--- /dev/null
+++ b/testing/tests/ui/elif.rs
@@ -0,0 +1,8 @@
+use askama::Template;
+
+#[derive(Template)]
+#[template(source = "{% if true %}{% elif false %}{% endif %}", ext = "html")]
+struct UnknownElif;
+
+fn main() {
+}
diff --git a/testing/tests/ui/elif.stderr b/testing/tests/ui/elif.stderr
new file mode 100644
index 0000000..0d5de47
--- /dev/null
+++ b/testing/tests/ui/elif.stderr
@@ -0,0 +1,9 @@
+error: unknown `elif` keyword; did you mean `else if`?
+       problems parsing template source at row 1, column 16 near:
+       "elif false %}{% endif %}"
+ --> tests/ui/elif.rs:3:10
+  |
+3 | #[derive(Template)]
+  |          ^^^^^^^^
+  |
+  = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/testing/tests/ui/name_mismatch_endblock.rs b/testing/tests/ui/name_mismatch_endblock.rs
new file mode 100644
index 0000000..9fbfbe4
--- /dev/null
+++ b/testing/tests/ui/name_mismatch_endblock.rs
@@ -0,0 +1,8 @@
+use askama::Template;
+
+#[derive(Template)]
+#[template(source = "{% block foo %}{% endblock not_foo %}", ext = "html")]
+struct NameMismatchEndBlock;
+
+fn main() {
+}
diff --git a/testing/tests/ui/name_mismatch_endblock.stderr b/testing/tests/ui/name_mismatch_endblock.stderr
new file mode 100644
index 0000000..958c413
--- /dev/null
+++ b/testing/tests/ui/name_mismatch_endblock.stderr
@@ -0,0 +1,9 @@
+error: expected name `foo` in `endblock` tag, found `not_foo`
+       problems parsing template source at row 1, column 27 near:
+       "not_foo %}"
+ --> tests/ui/name_mismatch_endblock.rs:3:10
+  |
+3 | #[derive(Template)]
+  |          ^^^^^^^^
+  |
+  = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/testing/tests/ui/name_mismatch_endmacro.rs b/testing/tests/ui/name_mismatch_endmacro.rs
new file mode 100644
index 0000000..10ea31f
--- /dev/null
+++ b/testing/tests/ui/name_mismatch_endmacro.rs
@@ -0,0 +1,8 @@
+use askama::Template;
+
+#[derive(Template)]
+#[template(source = "{% macro foo(arg) %} {{arg}} {% endmacro not_foo %}", ext = "html")]
+struct NameMismatchEndMacro;
+
+fn main() {
+}
diff --git a/testing/tests/ui/name_mismatch_endmacro.stderr b/testing/tests/ui/name_mismatch_endmacro.stderr
new file mode 100644
index 0000000..74101a4
--- /dev/null
+++ b/testing/tests/ui/name_mismatch_endmacro.stderr
@@ -0,0 +1,9 @@
+error: expected name `foo` in `endmacro` tag, found `not_foo`
+       problems parsing template source at row 1, column 41 near:
+       "not_foo %}"
+ --> tests/ui/name_mismatch_endmacro.rs:3:10
+  |
+3 | #[derive(Template)]
+  |          ^^^^^^^^
+  |
+  = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)