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
23pub 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
50pub 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}