Skip to main content

rumdl_lib/utils/
jinja_utils.rs

1use regex::Regex;
2use std::sync::LazyLock;
3
4// Jinja2 template delimiters
5static JINJA_EXPRESSION_REGEX: LazyLock<Regex> =
6    LazyLock::new(|| Regex::new(r"\{\{.*?\}\}").expect("Failed to compile Jinja expression regex"));
7
8static JINJA_STATEMENT_REGEX: LazyLock<Regex> =
9    LazyLock::new(|| Regex::new(r"\{%.*?%\}").expect("Failed to compile Jinja statement regex"));
10
11/// Pre-compute all Jinja template ranges in the content
12pub fn find_jinja_ranges(content: &str) -> Vec<(usize, usize)> {
13    let mut ranges = Vec::new();
14
15    // Collect Jinja expressions {{ ... }}
16    for mat in JINJA_EXPRESSION_REGEX.find_iter(content) {
17        ranges.push((mat.start(), mat.end()));
18    }
19
20    // Collect Jinja statements {% ... %}
21    for mat in JINJA_STATEMENT_REGEX.find_iter(content) {
22        ranges.push((mat.start(), mat.end()));
23    }
24
25    // Sort by start position for efficient binary search later
26    ranges.sort_by_key(|r| r.0);
27    ranges
28}
29
30/// Check if a position is within a Jinja2 template expression or statement
31pub fn is_in_jinja_template(content: &str, pos: usize) -> bool {
32    // Check Jinja expressions {{ ... }}
33    for mat in JINJA_EXPRESSION_REGEX.find_iter(content) {
34        if pos >= mat.start() && pos < mat.end() {
35            return true;
36        }
37    }
38
39    // Check Jinja statements {% ... %}
40    for mat in JINJA_STATEMENT_REGEX.find_iter(content) {
41        if pos >= mat.start() && pos < mat.end() {
42            return true;
43        }
44    }
45
46    false
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn test_jinja_expression_detection() {
55        let content = "Some text {{ variable }} more text";
56
57        // Position before Jinja
58        assert!(!is_in_jinja_template(content, 5));
59
60        // Position inside Jinja expression
61        assert!(is_in_jinja_template(content, 15));
62
63        // Position after Jinja
64        assert!(!is_in_jinja_template(content, 30));
65    }
66
67    #[test]
68    fn test_jinja_statement_detection() {
69        let content = "{% if condition %} text {% endif %}";
70
71        // Inside first statement
72        assert!(is_in_jinja_template(content, 5));
73
74        // Between statements
75        assert!(!is_in_jinja_template(content, 20));
76
77        // Inside second statement
78        assert!(is_in_jinja_template(content, 28));
79    }
80
81    #[test]
82    fn test_complex_jinja_expression() {
83        let content = "{{ pd_read_csv()[index] | filter }}";
84
85        // The entire expression should be detected
86        assert!(is_in_jinja_template(content, 10));
87        assert!(is_in_jinja_template(content, 20));
88    }
89}