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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use std::str::Chars;


/// Traits with extension emthods to wrap strings in bounding characters
pub trait SimpleEnclose {
  
  /// Enclose in a start and an end character with an optional prefix
  /// before the main content but after the first character
  /// This is a common syntactical pattern in many markup and programming languages
   /// the closing character will be the same opening character
  /// The optional escape character is inserted before occurrences of the end character
  /// unless the preceding character is the escape character itself to avoid double escaping of pre-escaped strings
  fn enclose_in_chars(& self, start: char, end: char, prefix: Option<&str>, escape_char: Option<char>) -> String;

  /// Enclose in a start and an end character with an optional prefix after the first character
  fn enclose_escaped(& self, start: char, end: char, escape_char: Option<char>) -> String {
    self.enclose_in_chars(start, end, None, escape_char)
  }

  /// Enclose in a start and an end character with an optional prefix after the first character
  fn enclose(& self, start: char, end: char) -> String {
    self.enclose_in_chars(start, end, None, None)
  }

  /// Enclose in a start and an end character with an optional prefix after the first character
  /// escaped where necessary with a backslash \
  fn enclose_safe(& self, start: char, end: char) -> String {
    self.enclose_in_chars(start, end, None, Some('\\'))
  }

  /// Wrap a string in a pair of characters, with the closing character matching the first character
  /// if it a parenthesis (round bracket), angle bracket, (square)  bracket or curly brace. Otherwise
  /// the closing character will be the same opening character
  /// The optional escape character is inserted before occurrences of the end character
  /// unless the preceding character is the escape character itself to avoid double escaping of pre-escaped strings
  fn wrap_escaped(& self, opening: char, escape_char: Option<char>) -> String {
    let end = match opening {
      '(' => ')',
      '<' => '>',
      '{' => '}',
      '[' => ']',
      _ => opening
    };
    self.enclose_in_chars(opening, end, None, escape_char)
  }

  fn wrap(& self, opening: char) -> String {
    let end = match opening {
      '(' => ')',
      '<' => '>',
      '{' => '}',
      '[' => ']',
      _ => opening
    };
    self.enclose_in_chars(opening, end, None, None)
  }

  /// Wrap in matching characters escaped by a backslash \
  fn wrap_safe(& self, opening: char) -> String {
    let end = match opening {
      '(' => ')',
      '<' => '>',
      '{' => '}',
      '[' => ']',
      _ => opening
    };
    self.enclose_in_chars(opening, end, None, Some('\\'))
  }

  /// wrap in parentheses (sound brackets) with an optional prefix before the main content
  fn in_parentheses(& self, prefix: Option<&str>) -> String {
    self.enclose_in_chars('(', ')', prefix, None)
  }

  /// wrap in parentheses (sound brackets)
  fn parenthesize(& self) -> String {
    self.wrap('(')
  }

  /// wrap in parentheses (sound brackets)
  fn parenthesize_safe(& self) -> String {
    self.wrap_safe('(')
  }

  /// wrap in parentheses (sound brackets)
  fn double_quotes(& self) -> String {
    self.wrap('"')
  }

  /// Wrap in single quotes
  fn single_quotes(& self) -> String {
    self.wrap('\'')
  }

  /// Wrap in double quotes with escaped quotes in the content
  fn double_quotes_safe(& self) -> String {
    self.wrap_escaped('"', Some('\\'))
  }

  /// Wrap in single quotes with escaped quotes in the content
  fn single_quotes_safe(& self) -> String {
    self.wrap_escaped('\'', Some('\\'))
  }

}


/// Implement the base method for &str/String
impl SimpleEnclose for str {
  fn enclose_in_chars(&self, start: char, end: char, prefix: Option<&str>, escape_char: Option<char>) -> String {
    let mut out = match escape_char {
      Some(esc_char) => {
        if self.contains(end) {
          escape_in_str(self.chars(), end, esc_char)
        } else {
          self.to_owned()
        }
      },
      _ => self.to_owned()
    };
    out.insert(0, start);
    if let Some(pre) = prefix {
      out.insert_str(1, pre);
    }
    out.push(end);
    out
  }
}

/// Escape a string enclosed in (double) quotes.
/// The customary escape character is a backslash, but some systems require doubled quote characters
/// To avoid double-escaping, escape characters are not escaped. Strings are thus treated as literals
pub fn escape_in_str(chars: Chars, end: char, esc_char: char) -> String {
  let mut new_string = String::new();
  let mut prev_char = ' ';
  for ch in chars {
    if ch == end {
      // do not escape escaped characters
      if prev_char != esc_char && new_string.len() > 0 {
        new_string.push(esc_char);
      }
    }
    new_string.push(ch);
    prev_char = ch;
  }
  new_string
}