simpletemplate/lib.rs
1//! Render html templates.
2//!
3//! # Examples
4//!
5//! ```
6//! // variables
7//! let data = json!({ "foo": "bar" });
8//! let template = r"{{ foo }}";
9//! let expected = "bar";
10//! assert_eq!(render(template, data), expected);
11//! ```
12//!
13//! ```
14//! // logic statements
15//! let data = json!({ "foo": "bar" });
16//! let template = r"{{ if foo }}{{ foo }}{{ else }}foo is not set{{ endif }}";
17//! let expected = "bar";
18//! assert_eq!(render(template, data), expected);
19//! ```
20//!
21//! ```
22//! let data = json!({ "foo": null });
23//! let template = r"{{ if foo }}{{ foo }}{{ else }}foo is not set{{ endif }}";
24//! let expected = "foo is not set";
25//! assert_eq!(render(template, data), expected);
26//! ```
27//!
28//! ```
29//! // for loop
30//! let data = json!({ "items": ["a", "b", "c"] });
31//! let template = r"{{ for item in items }}{{ item }}{{ endfor }}";
32//! let expected = "abc";
33//! assert_eq!(render(template, data), expected);
34//! ```
35//!
36//! ```
37//! let data = json!({ "items": [] });
38//! let template = r"{{ for item in items }}{{ item }}{{ endfor }}";
39//! let expected = "";
40//! assert_eq!(render(template, data), expected);
41//! ```
42//!
43//! ```
44//! // accessing by index
45//! let data = json!({ "items": ["a", "b", "c"] });
46//! let template = r"{{ items[0] }}";
47//! let expected = "a";
48//! assert_eq!(render(template, data), expected);
49//! ```
50//!
51//! ```
52//! let data = json!({ "items": ["a", "b", "c"] });
53//! let template = r"{{ items[1] }}";
54//! let expected = "b";
55//! assert_eq!(render(template, data), expected);
56//! ```
57//!
58//! ```
59//! let data = json!({ "items": ["a", "b", "c"] });
60//! let template = r"{{ items[2] }}";
61//! let expected = "c";
62//! assert_eq!(render(template, data), expected);
63//! ```
64//!
65//! ```
66//! // combining logic
67//! let data = json!({
68//! "foo": "bar",
69//! "items": ["a", "b", "c"],
70//! "show_items": true,
71//! "show_foo": false,
72//! });
73//! let template = r"{{ if show_items }}{{ for item in items }}{{ item }}{{ endfor }}{{ endif }}{{ if show_foo }}{{ foo }}{{ endif }}";
74//! let expected = "abc";
75//! assert_eq!(render(template, data), expected);
76//! ```
77//!
78//! ```
79//! let data = json!({
80//! "foo": "bar",
81//! "items": ["a", "b", "c"],
82//! "show_items": true,
83//! "show_foo": false,
84//! });
85//! let template = r"{{ if show_items }}{{ for item in items }}{{ item }}{{ endfor }}{{ endif }}{{ if show_foo }}{{ foo }}{{ else }}foo is not set{{ endif }}";
86//! let expected = "abcfoo is not set";
87//! assert_eq!(render(template, data), expected);
88//! ```
89//!
90//! ```
91//! // blank template
92//! let data = json!({});
93//! let template = "";
94//! let expected = "";
95//! assert_eq!(render(template, data), expected);
96//! ```
97//!
98//! ```
99//! // invalid variable
100//! let data = json!({ "foo": "bar" });
101//! let template = "{{ baz }}";
102//! let expected = "null";
103//! assert_eq!(render(template, data), expected);
104//! ```
105//!
106//! ```
107//! // invalid index
108//! let data = json!({ "items": ["a", "b", "c"] });
109//! let template = "{{ items[5] }}";
110//! let expected = "null";
111//! assert_eq!(render(template, data), expected);
112//! ```
113//!
114//! ```
115//! // invalid statement
116//! let data = json!({});
117//! let template = "{{ if foo }}{{ endif }}";
118//! let expected = "";
119//! assert_eq!(render(template, data), expected);
120//! ```
121
122pub use serde_json::{Value};
123pub use regex::Regex;
124pub use string_join::display::Join;
125
126pub fn render(content: &str, data: Value) -> String {
127 // handle for loop
128 let re = Regex::new(r"\{\{ for (\w+) in (\w+) \}\}([\s\S]*?)\{\{ endfor \}\}").unwrap();
129 let replaced = re.replace_all(content, |caps: ®ex::Captures| {
130 let loop_variable = &caps[1];
131 let loop_variable_formatted = "".join(["{{ ", loop_variable, " }}"]);
132 let loop_variable = loop_variable_formatted.as_str();
133
134 let loop_iterable = &caps[2];
135 let loop_body = &caps[3];
136
137 let iterable = &data[loop_iterable];
138 if !iterable.is_array() {
139 return "".to_string()
140 }
141
142 let mut result = "".to_string();
143 for (index, value) in iterable.as_array().unwrap().iter().enumerate() {
144 let mut loop_body_replaced = loop_body.trim_start_matches('\n').to_string();
145 let val = get_value_string(value);
146 loop_body_replaced = loop_body_replaced.replace(loop_variable, &val);
147 loop_body_replaced = loop_body_replaced.replace("{{ index }}", &index.to_string());
148 result.push_str(&loop_body_replaced);
149 }
150 result
151 });
152
153 // handle if statements
154 let re = Regex::new(r"\{\{ if (\w+) \}\}\s*([\s\S]*?)\s*(?:\{\{ else \}\}\s*([\s\S]*?)\s*)?\{\{ endif \}\}").unwrap();
155 let replaced = re.replace_all(&replaced, |caps: ®ex::Captures| {
156 let matched_group_count = (1..caps.len()).filter(|i| caps.get(*i).is_some()).count();
157 let condition = &caps[1];
158 let if_body = &caps[2];
159 let else_body = if matched_group_count > 2 {
160 &caps[3]
161 } else {
162 ""
163 };
164
165 let value = &data[condition];
166 if value.is_null() || value == "false" || value == false {
167 else_body.to_string()
168 } else {
169 if_body.to_string()
170 }
171 });
172
173
174 // handle variables
175 let re = Regex::new(r"\{\{ (\w+) \}\}").unwrap();
176 let replaced = re.replace_all(&replaced, |caps: ®ex::Captures| {
177 let key = &caps[1];
178 let value = &data[key];
179 if value.is_array() {
180 // Join the array values into a single string, separated by commas
181 value.as_array()
182 .unwrap()
183 .iter()
184 .map(|v| get_value_string(v))
185 .collect::<Vec<_>>()
186 .join(", ")
187 } else {
188 get_value_string(value)
189 }
190 });
191
192 // handle indexing
193 let re = Regex::new(r"\{\{ (\w+)\[(\d+)\] \}\}").unwrap();
194 let replaced = re.replace_all(&replaced, |caps: ®ex::Captures| {
195 let array_name = &caps[1];
196 let index = caps[2].parse::<usize>().unwrap();
197 let value = &data[array_name][index];
198 get_value_string(value)
199 });
200
201 replaced.to_string()
202}
203
204fn get_value_string(value: &Value) -> String {
205 if value.is_string() {
206 value.as_str().unwrap().to_string()
207 } else {
208 value.to_string()
209 }
210}