minijinja/
debug.rs

1use std::collections::BTreeMap;
2use std::fmt;
3
4use crate::compiler::tokens::Span;
5use crate::error::ErrorKind;
6use crate::value::Value;
7
8/// This is a snapshot of the debug information.
9#[cfg_attr(docsrs, doc(cfg(feature = "debug")))]
10#[derive(Default)]
11pub(crate) struct DebugInfo {
12    pub(crate) template_source: Option<String>,
13    pub(crate) referenced_locals: BTreeMap<String, Value>,
14}
15
16struct VarPrinter<'x>(&'x BTreeMap<String, Value>);
17
18impl fmt::Debug for VarPrinter<'_> {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        if self.0.is_empty() {
21            return f.write_str("No referenced variables");
22        }
23        let mut m = f.debug_struct("Referenced variables:");
24        let mut vars = self.0.iter().collect::<Vec<_>>();
25        vars.sort_by_key(|x| x.0);
26        for (key, value) in vars {
27            m.field(key, value);
28        }
29        m.finish()
30    }
31}
32
33impl DebugInfo {
34    pub fn source(&self) -> Option<&str> {
35        self.template_source.as_deref()
36    }
37}
38
39pub(super) fn render_debug_info(
40    f: &mut fmt::Formatter,
41    name: Option<&str>,
42    kind: ErrorKind,
43    line: Option<usize>,
44    span: Option<Span>,
45    info: &DebugInfo,
46) -> fmt::Result {
47    if let Some(source) = info.source() {
48        let title = format!(
49            " {} ",
50            name.unwrap_or_default()
51                .rsplit(&['/', '\\'])
52                .next()
53                .unwrap_or("Template Source")
54        );
55        ok!(writeln!(f));
56        writeln!(f, "{:-^1$}", title, 79).unwrap();
57        let lines: Vec<_> = source.lines().enumerate().collect();
58        let idx = line.unwrap_or(1).saturating_sub(1);
59        let skip = idx.saturating_sub(3);
60        let pre = lines.iter().skip(skip).take(3.min(idx)).collect::<Vec<_>>();
61        let post = lines.iter().skip(idx + 1).take(3).collect::<Vec<_>>();
62        for (idx, line) in pre {
63            writeln!(f, "{:>4} | {}", idx + 1, line).unwrap();
64        }
65
66        if let Some(line) = lines.get(idx) {
67            writeln!(f, "{:>4} > {}", idx + 1, line.1).unwrap();
68        }
69        if let Some(span) = span {
70            if span.start_line == span.end_line {
71                ok!(writeln!(
72                    f,
73                    "     i {}{} {}",
74                    " ".repeat(span.start_col as usize),
75                    "^".repeat(span.end_col as usize - span.start_col as usize),
76                    kind,
77                ));
78            }
79        }
80
81        for (idx, line) in post {
82            writeln!(f, "{:>4} | {}", idx + 1, line).unwrap();
83        }
84        write!(f, "{:~^1$}", "", 79).unwrap();
85    }
86    ok!(writeln!(f));
87    ok!(writeln!(f, "{:#?}", VarPrinter(&info.referenced_locals)));
88    write!(f, "{:-^1$}", "", 79).unwrap();
89    Ok(())
90}