tempera/
templating.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
//! Module that handles template tagged strings.

use crate::custom::resolve_styles;
use crate::styling::style_to_ansi;
use lazy_static::lazy_static;
use regex::{Captures, Regex};

lazy_static! {
  static ref PARSER: Regex =
    Regex::new(r"(?i)(?:\{(?P<styleSingle>[^\{\}]+)\})|(?:\{\{(?P<styleDouble>[^\{\}]+)\}\})").unwrap();
  static ref SPLITTER: Regex = Regex::new(r"[\|\s]+").unwrap();
}

static RESET: &'static str = "\u{1b}[0m";

fn add_styles(replacement: &mut String, styles: &mut Vec<String>, id: &str) -> bool {
  let open = style_to_ansi(id);

  if open.0.is_empty() {
    styles.push("ignore".to_string());
    return false;
  }

  styles.push(id.to_string());
  replacement.push_str(open.0.as_str());

  true
}

fn remove_styles(replacement: &mut String, styles: &mut Vec<Vec<String>>) {
  // Pop a the style from the list of the applied ones
  let to_remove = styles.pop();

  if to_remove.is_none() {
    return;
  }

  // Unset the style by applying the closing ANSI codes
  for id in to_remove.unwrap() {
    replacement.push_str(style_to_ansi(&id).1.as_str());
  }

  // If the applied styles stack is still non-empty, it means we have to restore the previous style
  if !styles.is_empty() {
    for id in styles.last().unwrap() {
      replacement.push_str(style_to_ansi(&id).0.as_str());
    }
  }
}

/// Apply colors to a template tagged string.
pub fn colorize_template(content: &str) -> String {
  let mut applied: Vec<Vec<String>> = vec![];
  let mut replaced = false;

  // For each tag in the string
  let mut modified = PARSER.replace_all(content, |captures: &Captures| {
    // Get the styles
    let ids = captures.name("styleSingle").or(captures.name("styleDouble"));

    if ids.is_none() {
      return String::from(captures.get(0).unwrap().as_str());
    }

    let mut replacement = String::from("");
    let mut current: Vec<String> = vec![];

    // Split ids
    for raw_id in resolve_styles(&SPLITTER.split(ids.unwrap().as_str()).collect::<Vec<&str>>()).iter() {
      let id = raw_id.trim();

      match id {
        "-" => {
          // Close all styles applied so far
          if !applied.is_empty() {
            if applied.last().unwrap().get(0).unwrap() == "ignore" {
              applied.pop();
            } else {
              remove_styles(&mut replacement, &mut applied);
            }
          }

          // Do not process anything else in this style
          break;
        }
        "reset" => {
          // Reset all styles, it means drop all the styles applied so far so do not unset anything
          applied.clear();

          // Do not process anything else in this style
          break;
        }
        _ => {
          // Adding a new style
          if add_styles(&mut replacement, &mut current, id) {
            replaced = true
          }
        }
      }
    }

    // If we added any style in this tag, add to the applied styles stack (in reverse order in order to guarantee proper closing)
    if !current.is_empty() {
      current.reverse();
      applied.push(current);
    }

    return replacement;
  });

  if replaced {
    modified += RESET;
  }

  modified.to_string()
}

/// Removes all templates tags from the string.
pub fn clean_template(content: &str) -> String {
  PARSER.replace_all(content, "").to_string()
}