askama_derive/
heritage.rs

1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3
4use crate::config::Config;
5use crate::CompileError;
6use parser::node::{BlockDef, Macro, Match};
7use parser::Node;
8
9pub(crate) struct Heritage<'a> {
10    pub(crate) root: &'a Context<'a>,
11    pub(crate) blocks: BlockAncestry<'a>,
12}
13
14impl Heritage<'_> {
15    pub(crate) fn new<'n>(
16        mut ctx: &'n Context<'n>,
17        contexts: &'n HashMap<&'n Path, Context<'n>>,
18    ) -> Heritage<'n> {
19        let mut blocks: BlockAncestry<'n> = ctx
20            .blocks
21            .iter()
22            .map(|(name, def)| (*name, vec![(ctx, *def)]))
23            .collect();
24
25        while let Some(ref path) = ctx.extends {
26            ctx = &contexts[path.as_path()];
27            for (name, def) in &ctx.blocks {
28                blocks.entry(name).or_default().push((ctx, def));
29            }
30        }
31
32        Heritage { root: ctx, blocks }
33    }
34}
35
36type BlockAncestry<'a> = HashMap<&'a str, Vec<(&'a Context<'a>, &'a BlockDef<'a>)>>;
37
38#[derive(Clone)]
39pub(crate) struct Context<'a> {
40    pub(crate) nodes: &'a [Node<'a>],
41    pub(crate) extends: Option<PathBuf>,
42    pub(crate) blocks: HashMap<&'a str, &'a BlockDef<'a>>,
43    pub(crate) macros: HashMap<&'a str, &'a Macro<'a>>,
44    pub(crate) imports: HashMap<&'a str, PathBuf>,
45}
46
47impl Context<'_> {
48    pub(crate) fn new<'n>(
49        config: &Config<'_>,
50        path: &Path,
51        nodes: &'n [Node<'n>],
52    ) -> Result<Context<'n>, CompileError> {
53        let mut extends = None;
54        let mut blocks = HashMap::new();
55        let mut macros = HashMap::new();
56        let mut imports = HashMap::new();
57        let mut nested = vec![nodes];
58        let mut top = true;
59
60        while let Some(nodes) = nested.pop() {
61            for n in nodes {
62                match n {
63                    Node::Extends(e) if top => match extends {
64                        Some(_) => return Err("multiple extend blocks found".into()),
65                        None => {
66                            extends = Some(config.find_template(e.path, Some(path))?);
67                        }
68                    },
69                    Node::Macro(m) if top => {
70                        macros.insert(m.name, m);
71                    }
72                    Node::Import(import) if top => {
73                        let path = config.find_template(import.path, Some(path))?;
74                        imports.insert(import.scope, path);
75                    }
76                    Node::Extends(_) | Node::Macro(_) | Node::Import(_) if !top => {
77                        return Err(
78                            "extends, macro or import blocks not allowed below top level".into(),
79                        );
80                    }
81                    Node::BlockDef(b) => {
82                        blocks.insert(b.name, b);
83                        nested.push(&b.nodes);
84                    }
85                    Node::If(i) => {
86                        for cond in &i.branches {
87                            nested.push(&cond.nodes);
88                        }
89                    }
90                    Node::Loop(l) => {
91                        nested.push(&l.body);
92                        nested.push(&l.else_nodes);
93                    }
94                    Node::Match(Match { arms, .. }) => {
95                        for arm in arms {
96                            nested.push(&arm.nodes);
97                        }
98                    }
99                    _ => {}
100                }
101            }
102            top = false;
103        }
104
105        Ok(Context {
106            nodes,
107            extends,
108            blocks,
109            macros,
110            imports,
111        })
112    }
113}