kataru/
vars.rs

1use crate::{structs::Bookmark, Value};
2use regex::{Captures, Regex};
3use std::borrow::Cow;
4
5static VARS_RE_STR: &str = r"\$((?:[A-Za-z]+:)?(?:\w+\.)?\w+)";
6static BRACKET_EXPR_STR: &str = r"(:?(\{\{)|(\}\})|(\{[^\{\}]+\}))";
7
8lazy_static! {
9    static ref VARS_RE_STRING: String = format!(r"{}\b", VARS_RE_STR);
10    static ref SINGLE_VAR_RE_STRING: String = format!(r"^{}$", VARS_RE_STR);
11    pub static ref VARS_RE: Regex = Regex::new(&VARS_RE_STRING).unwrap();
12    pub static ref SINGLE_VAR_RE: Regex = Regex::new(&SINGLE_VAR_RE_STRING).unwrap();
13    pub static ref BRACKET_VARS_RE: Regex = Regex::new(BRACKET_EXPR_STR).unwrap();
14}
15
16fn truncate_ends(s: &str) -> &str {
17    let mut chars = s.chars();
18    chars.next();
19    chars.next_back();
20    chars.as_str()
21}
22
23/// This is a line with var=${var} and var2=${var2}
24pub fn replace_vars(text: &str, bookmark: &Bookmark) -> String {
25    let vars_replaced = BRACKET_VARS_RE.replace_all(text, |cap: &Captures| {
26        let expr = &cap[1];
27        if expr == "{{" {
28            return Cow::from("{");
29        }
30        if expr == "}}" {
31            return Cow::from("}");
32        }
33        match Value::from_expr(truncate_ends(expr), bookmark) {
34            Ok(value) => Cow::from(value.to_string()),
35            Err(_) => Cow::from(expr.to_string().to_string()),
36        }
37    });
38
39    VARS_RE
40        .replace_all(&vars_replaced, |cap: &Captures| {
41            let var = &cap[1];
42            match bookmark.value(var) {
43                Ok(value) => Cow::from(value.to_string()),
44                Err(_) => Cow::from(format!("${}", var).to_string()),
45            }
46        })
47        .to_string()
48}
49
50/// Returns Some(&str) when a variable was successfully extracted.
51/// Otherwise returns None.
52pub fn extract_var(text: &str) -> Option<&str> {
53    if SINGLE_VAR_RE.is_match(text) {
54        Some(&text[1..])
55    } else {
56        None
57    }
58}
59
60#[inline]
61pub fn contains_var(text: &str) -> bool {
62    VARS_RE.is_match(text)
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use crate::Value;
69
70    #[test]
71    fn test_str_replace() {
72        let mut bookmark = Bookmark::new(hashmap! {
73            "test".to_string() => hashmap! {
74                "var1".to_string() => Value::Number(1.0)
75            },
76            "global".to_string() => hashmap! {
77                "var2".to_string() => Value::String("a".to_string()),
78                "char.var1".to_string() => Value::String("b".to_string())
79            }
80        });
81        bookmark.set_namespace("test".to_string());
82
83        assert_eq!(
84            replace_vars(
85                "var1 = {$var1}, var2 = {$global:var2}, char.var1 = $char.var1. Tickets cost $10.",
86                &bookmark
87            ),
88            "var1 = 1, var2 = a, char.var1 = b. Tickets cost $10."
89        );
90
91        assert_eq!(
92            replace_vars("var1 + 1 = {$var1 + 1}.", &bookmark),
93            "var1 + 1 = 2."
94        );
95    }
96
97    #[test]
98    fn test_invalid_vars() {
99        let bookmark = Bookmark::default();
100        assert_eq!(
101            replace_vars("var1 = {$varx}.", &bookmark),
102            "var1 = {$varx}."
103        );
104        assert_eq!(
105            replace_vars("This string has {{curly braces}}", &bookmark),
106            "This string has {curly braces}"
107        )
108    }
109}