1use std::collections::{ HashMap, VecDeque };
2use std::fmt;
3use std::fs;
4use std::convert::TryFrom;
5
6#[cfg(feature = "hashmap_json")]
7use serde::Serialize;
8
9#[derive(Debug)]
10pub enum ErrorKind {
11 InvalidFileExtension,
12 IllegalVarName(String),
13 VarNotFound(String),
14 BadArgument(String),
15 MissingEndFor,
16 MissingEndIf,
17 RecursionTooDeep,
18}
19
20impl fmt::Display for ErrorKind {
21 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
22 match self {
23 ErrorKind::InvalidFileExtension => write!(f, "Invalid file extension (must start with '.')"),
24 ErrorKind::IllegalVarName(var_name) => write!(f, "Illegal variable name: '{}'", var_name),
25 ErrorKind::VarNotFound(var_name) => write!(f, "Variable '{}' not found", var_name),
26 ErrorKind::BadArgument(missing_message) => write!(f, "{}", missing_message),
27 ErrorKind::MissingEndFor => write!(f, "`for:` statement missing `[[ endfor ]]`"),
28 ErrorKind::MissingEndIf => write!(f, "`if:` statement missing `[[ endif ]]`"),
29 ErrorKind::RecursionTooDeep => write!(f, "`component:` statement recursion too deep (>5)"),
30 }
31 }
32}
33
34#[derive(Debug, PartialEq)]
35pub struct FileExtension {
36 file_extension: String,
37}
38
39impl FileExtension {
41 pub fn new(file_extension: String) -> Result<Self, ErrorKind> {
42 if file_extension.starts_with(".") {
43 Ok(FileExtension {
44 file_extension,
45 })
46 } else {
47 Err(ErrorKind::InvalidFileExtension)
48 }
49 }
50
51 pub fn get_string_ref(&self) -> &String {
52 &self.file_extension
53 }
54}
55
56impl TryFrom<String> for FileExtension {
57 type Error = ErrorKind;
58
59 fn try_from(a: String) -> Result<Self, ErrorKind> {
60 FileExtension::new(a)
61 }
62}
63
64impl From<FileExtension> for String {
65 fn from(file_extension: FileExtension) -> String {
66 file_extension.file_extension
67 }
68}
69
70
71#[derive(Clone, PartialEq)]
72#[cfg_attr(feature = "hashmap_json", derive(Debug))]
73pub enum VarValue {
74 Bool(bool),
75 String(String),
76 F64(f64),
77 U32(u32),
78 Vec(Vec<VarValue>),
79 HashMap(HashMap<String, VarValue>),
80}
81
82impl VarValue {
83 pub fn is_truthy(&self) -> bool {
84 match self {
85 Self::Bool(boolean) => *boolean,
86 Self::String(string) => string == "",
87 Self::F64(decimal) => *decimal != 0.0,
88 Self::U32(integer) => *integer != 0,
89 Self::Vec(vector) => vector.len() > 0,
90 Self::HashMap(hashmap) => hashmap.keys().len() > 0,
91 }
92 }
93}
94
95impl fmt::Display for VarValue {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 match self {
98 VarValue::Bool(boolean) => write!(f, "{}", boolean.to_string()),
99 VarValue::String(string) => write!(f, "{}", string),
100 VarValue::F64(decimal) => write!(f, "{}", decimal.to_string()),
101 VarValue::U32(integer) => write!(f, "{}", integer.to_string()),
102 VarValue::Vec(vector) => write!(f, "{:?}", vector.iter().map(|a| format!("{}", a)).collect::<Vec<String>>()),
103 VarValue::HashMap(_hashmap) => write!(f, "Enable the `hashmap_json` crate feature"),
104 }
105 }
106}
107
108#[cfg(feature = "hashmap_json")]
109impl fmt::Display for VarValue {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 match self {
112 VarValue::Bool(boolean) => write!(f, "{}", boolean.to_string()),
113 VarValue::String(string) => write!(f, "{}", string),
114 VarValue::F64(decimal) => write!(f, "{}", decimal.to_string()),
115 VarValue::U32(integer) => write!(f, "{}", integer.to_string()),
116 VarValue::Vec(vector) => write!(f, "{:?}", vector.iter().map(|a| format!("{}", a)).collect::<Vec<String>>()),
117 VarValue::HashMap(hashmap) => write!(f, "{}", serde_json::to_string(hashmap)),
118 }
119 }
120}
121
122#[derive(Clone, Debug, PartialEq)]
123pub struct SyntaxMatch {
124 pub content: String,
125 pub index: usize, }
127
128pub struct ForLoopInfo {
129 index: usize,
130 total: usize,
131 current: usize,
132 var_value: Vec<VarValue>, iter_var_name: Option<String>,
134 index_var_name: Option<String>,
135}
136
137pub type Vars = HashMap<String, VarValue>;
138
139pub struct Renderer {
140 pub templates_dir: String,
141 pub components_dir: String,
142 pub file_extension: FileExtension,
143}
144
145impl Renderer {
146 pub fn new(templates_dir: String, components_dir: String, file_extension: FileExtension) -> Self {
147 Self {
148 templates_dir,
149 components_dir,
150 file_extension,
151 }
152 }
153
154 pub fn concat_path(path1: &String, path2: &String) -> String {
155 if path1.ends_with("/") && path2.starts_with("/") {
156 let mut path1: String = path1.clone();
157 path1.truncate(path1.len()-1);
158 format!("{}{}", path1, path2)
159 } else if !path1.ends_with("/") && !path2.starts_with("/") {
160 format!("{}/{}", path1, path2)
161 } else {
162 format!("{}{}", path1, path2)
163 }
164 }
165
166 pub fn sanitize(text: &String) -> String {
167 text.replace("<", "<").replace(">", ">")
168 }
169
170 pub fn check_var_name_legality(var_name: &String, dot_allowed: bool) -> Result<(), ErrorKind> {
171 let mut legal_chars: Vec<char> = vec!['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_', '/', '.'];
172 if !dot_allowed {
173 legal_chars.pop();
174 }
175 let fail: bool = var_name.chars().any(|c| !legal_chars.contains(&c.to_ascii_lowercase()));
177 if fail {
178 Err(ErrorKind::IllegalVarName(var_name.clone()))
179 } else {
180 Ok(())
181 }
182 }
183
184 pub fn find_syntax_matches(template_content: &String) -> Vec<SyntaxMatch> {
185 let legal_chars: Vec<char> = vec!['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_', '.', ':', '-', '!'];
186 let mut matches: Vec<SyntaxMatch> = Vec::new();
187 let chars: Vec<char> = template_content.chars().collect();
189 let mut in_match: bool = false;
190 let mut match_index: usize = 0; for index in 0..chars.len() {
192 let current_char: char = chars[index];
193 if index > 1 && index < chars.len()-2 {
194 if current_char == ' ' && chars[index-1] == '[' && chars[index-2] == '[' {
195 in_match = true;
196 match_index = index-2;
197 } else if in_match && chars[index] == ' ' && chars[index+1] == ']' && chars[index+2] == ']' {
198 in_match = false;
199 matches.push(SyntaxMatch {
200 index: match_index,
201 content: chars[match_index..index+3].iter().collect(),
202 });
203 } else if in_match && !legal_chars.contains(¤t_char.to_ascii_lowercase()) {
204 in_match = false;
205 }
206 }
207 }
208 matches
209 }
210
211 pub fn get_var(var_name: String, vars: &Vars) -> Result<&VarValue, ErrorKind> {
212 Self::check_var_name_legality(&var_name, true)?;
213 let mut parts: VecDeque<&str> = var_name.split(".").into_iter().collect();
214 let part_uno: &str = parts.pop_front().unwrap();
215 let var_value_unwrapped: &Option<&VarValue> = &vars.get(part_uno);
216 if var_value_unwrapped.is_none() {
217 return Err(ErrorKind::VarNotFound(var_name));
219 }
220 let mut var_value = var_value_unwrapped.unwrap();
221 for part in parts {
222 if let VarValue::HashMap(var_value_hashmap) = &var_value {
223 let var_value_hashmap_unwrapped: Option<&VarValue> = var_value_hashmap.get(part);
224 if var_value_hashmap_unwrapped.is_none() {
225 return Err(ErrorKind::VarNotFound(var_name));
227 }
228 var_value = var_value_hashmap_unwrapped.unwrap();
229 } else {
230 return Err(ErrorKind::VarNotFound(var_name));
232 }
233 }
234 Ok(var_value)
235 }
236
237 pub fn render(&self, template_contents: String, vars: &mut Vars, recursion_layer: Option<usize>) -> Result<String, ErrorKind> {
238 let recursion_layer: usize = recursion_layer.unwrap_or(0);
239 let syntax_matches: Vec<SyntaxMatch> = Self::find_syntax_matches(&template_contents);
240 if syntax_matches.len() == 0 {
241 return Ok(template_contents);
242 }
243 let mut rendered: String = template_contents[0..syntax_matches[0].index].to_string();
244 let mut for_loops: Vec<ForLoopInfo> = vec![];
245 let mut index: usize = 0;
246 let mut iterations_: usize = 0;
247 loop {
248 if index == syntax_matches.len() {
249 break;
250 }
251 if iterations_ > 75000 {
252 println!("Passed 75000 iterations while rendering, infinite loop?");
253 }
254 let syntax_match: &SyntaxMatch = &syntax_matches[index];
255 let exp_parts: Vec<&str> = syntax_match.content[3..syntax_match.content.len()-3].split(":").collect();
256 if exp_parts.len() < 1 {
257 return Err(ErrorKind::BadArgument("An empty '[[ ]]' is not valid".to_string()));
258 }
259 if exp_parts[0] == "component" {
260 if recursion_layer > 5 {
262 return Err(ErrorKind::RecursionTooDeep);
263 }
264 if exp_parts.len() != 2 {
265 return Err(ErrorKind::BadArgument("`component:` statement missing component name (second arg), or more than two args".to_string()));
266 }
267 let mut file_name: String = exp_parts[1].to_string();
268 if !file_name.contains(".") {
269 file_name += self.file_extension.get_string_ref();
270 }
271 rendered += &self.render_template(Self::concat_path(&self.components_dir, &file_name), vars, Some(recursion_layer+1))?;
272 } else if exp_parts[0] == "for" {
273 let mut already_exists: bool = false;
274 let most_recent: Option<&ForLoopInfo> = for_loops.last();
275 if most_recent.is_some() {
276 if most_recent.unwrap().index == index {
277 already_exists = true;
279 }
280 }
281 if !already_exists {
282 if exp_parts.len() < 2 {
284 return Err(ErrorKind::BadArgument("`for:` statement missing variable name to loop over (second arg)".to_string()));
285 }
286 let var_name: String = exp_parts[1].to_string();
287 let var_value: &VarValue = Self::get_var(var_name, &vars)?;
288 if let VarValue::Vec(vec_value) = var_value {
289 let vec_value_cloned = vec_value.clone();
290 let vec_length: usize = vec_value.len().clone();
291 let iter_var_name: Option<String>;
292 if exp_parts.len() >= 3 {
293 let iter_var_name_: String = exp_parts[2].to_string();
296 Self::check_var_name_legality(&iter_var_name_, false)?;
297 iter_var_name = Some(iter_var_name_.clone());
298 if vec_value.len() > 0 {
300 vars.insert(iter_var_name_, vec_value[0].clone());
301 }
302 } else {
303 iter_var_name = None;
304 }
305 let index_var_name: Option<String>;
306 if exp_parts.len() >= 4 {
307 let index_var_name_: String = exp_parts[3].to_string();
309 Self::check_var_name_legality(&index_var_name_, false)?;
310 index_var_name = Some(index_var_name_.clone());
311 vars.insert(index_var_name_, VarValue::U32(0));
312 } else {
313 index_var_name = None;
314 }
315 if exp_parts.len() >= 5 {
316 let max_var_name: String = exp_parts[4].to_string();
318 Self::check_var_name_legality(&max_var_name, false)?;
319 vars.insert(max_var_name, VarValue::U32(vec_length as u32-1));
320 }
321 for_loops.push(ForLoopInfo {
322 index,
323 total: vec_length,
324 current: 0,
325 var_value: vec_value_cloned,
326 iter_var_name,
327 index_var_name,
328 });
329 if vec_length == 0 {
331 let sliced: Vec<SyntaxMatch> = syntax_matches[index+1..syntax_matches.len()].to_vec();
333 let mut new_index: Option<usize> = None;
334 let mut extra_fors: usize = 0;
335 for i in 0..sliced.len() {
336 let match_content: &String = &sliced[i].content;
337 if match_content.starts_with("[[ for:") {
338 extra_fors += 1;
339 } else if match_content == "[[ endfor ]]" {
340 if extra_fors == 0 {
341 new_index = Some(i);
342 break;
343 }
344 extra_fors -= 1;
345 }
346 }
347 if new_index.is_none() {
348 return Err(ErrorKind::MissingEndFor);
350 }
351 index += new_index.unwrap()+1;
352 continue;
353 }
354 } else {
355 return Err(ErrorKind::BadArgument("variable being looped over in `for:` statement is not a vector".to_string()));
356 }
357 }
358 } else if exp_parts[0] == "endfor" {
359 let for_loops_len: usize = for_loops.len();
361 let current_loop: &mut ForLoopInfo = &mut for_loops[for_loops_len-1];
362 current_loop.current += 1;
363 if current_loop.current >= current_loop.total {
364 for_loops.pop();
366 } else {
367 if current_loop.iter_var_name.is_some() {
369 vars.insert(current_loop.iter_var_name.clone().unwrap(), current_loop.var_value[current_loop.current].clone());
370 }
371 if current_loop.index_var_name.is_some() {
372 vars.insert(current_loop.index_var_name.clone().unwrap(), VarValue::U32(current_loop.current.clone() as u32));
373 }
374 index = current_loop.index;
376 continue;
377 }
378 } else if exp_parts[0] == "if" {
379 if exp_parts.len() < 2 {
380 return Err(ErrorKind::BadArgument("`if:` statement missing variable name (second arg)".to_string()));
381 }
382 let var_name: String = exp_parts[1].to_string();
383 let var_value: &VarValue = Self::get_var(var_name, &vars)?;
384 let condition_pass: bool;
385 if exp_parts.len() == 2 {
386 if var_value.is_truthy() {
388 condition_pass = true;
389 } else {
390 condition_pass = false;
391 }
392 } else if exp_parts.len() == 3 {
393 let mut var_name2: String = exp_parts[2].to_string();
395 let mut if_not: bool = false;
396 if var_name2.starts_with("!") {
397 var_name2 = var_name2[1..var_name2.len()].to_string();
398 if_not = true;
399 }
400 let var_value2: &VarValue = Self::get_var(var_name2, &vars)?;
401 if if_not {
402 if var_value != var_value2 {
404 condition_pass = true;
405 } else {
406 condition_pass = false;
407 }
408 } else {
409 if var_value == var_value2 {
411 condition_pass = true;
412 } else {
413 condition_pass = false;
414 }
415 }
416 } else {
417 return Err(ErrorKind::BadArgument("`if:` statement cannot have more than 3 args".to_string()));
418 }
419 if !condition_pass { let sliced: Vec<SyntaxMatch> = syntax_matches[index+1..syntax_matches.len()].to_vec();
422 let mut new_index: Option<usize> = None;
423 let mut extra_ifs: usize = 0;
424 for i in 0..sliced.len() {
425 let match_content: &String = &sliced[i].content;
426 if match_content.starts_with("[[ if:") {
427 extra_ifs += 1;
428 } else if match_content == "[[ endif ]]" {
429 if extra_ifs == 0 {
430 new_index = Some(i);
431 break;
432 }
433 extra_ifs -= 1;
434 }
435 }
436 if new_index.is_none() {
437 return Err(ErrorKind::MissingEndIf);
439 }
440 index += new_index.unwrap()+1;
441 continue;
442 }
443 } else if exp_parts[0] == "endif" {
444 } else { let var_name: String;
448 if exp_parts[0] == "html" {
449 if exp_parts.len() != 2 {
450 return Err(ErrorKind::BadArgument("`html:` statement missing variable name, the second arg, or has more than two args".to_string()));
451 }
452 var_name = exp_parts[1].to_string();
453 } else {
454 var_name = exp_parts[0].to_string();
455 }
456 let var_value_string: String = (Self::get_var(var_name, &vars)?).to_string();
458 let current_last = rendered.split("\n").last().unwrap();
460 let mut indentation: usize = 0;
461 for i in 0..current_last.len() {
462 if current_last.chars().nth(i).unwrap() != ' ' {
463 break;
464 }
465 indentation += 1;
466 }
467 let mut var_lines: VecDeque<&str> = var_value_string.split("\n").into_iter().collect();
468 let var_first: &str = var_lines.pop_front().unwrap();
469 let var_value: String;
471 if var_lines.len() == 0 {
472 var_value = var_first.to_string();
473 } else {
474 var_value = format!("{}\n{}", var_first, var_lines.into_iter().map(
475 |var_line| {
476 " ".repeat(indentation)+var_line
477 }
478 ).collect::<Vec<String>>().join("\n"));
479 }
480 if exp_parts[0] == "html" {
481 rendered += &var_value;
483 } else {
484 rendered += &Self::sanitize(&var_value);
485 }
486 }
487 if index != syntax_matches.len()-1 {
488 rendered += &template_contents[syntax_match.index+syntax_match.content.len()..syntax_matches[index+1].index];
490 } else {
491 rendered += &template_contents[syntax_match.index+syntax_match.content.len()..template_contents.len()];
493 }
494 index += 1;
495 iterations_ += 1;
496 }
497 Ok(rendered)
498 }
499
500 pub fn render_template(&self, template_name: String, vars: &mut Vars, recursion_layer: Option<usize>) -> Result<String, ErrorKind> {
501 let mut template_file_name = template_name;
502 if !template_file_name.contains(".") {
503 template_file_name += self.file_extension.get_string_ref();
504 }
505 let content: String = fs::read_to_string(Self::concat_path(&self.templates_dir, &template_file_name)).unwrap();
506 self.render(content, vars, recursion_layer)
507 }
508}