1 module tagion.utils.Escaper;
2 
3 import std.algorithm.iteration : joiner, map;
4 import std.array : join;
5 import std.format;
6 import std.range;
7 import std.range.primitives : isInputRange;
8 import std.string : indexOf;
9 import std.traits : ForeachType;
10 
11 enum special_chars = "ntr'\"\\"; /// List of special chars which will be escapes
12 
13 enum code_esc_special_chars =
14     format(q{enum escaped_special_chars="%s";},
15             zip('\\'.repeat(special_chars.length),
16             special_chars.map!(c => cast(char) c))
17             .map!(m => only(m[0], m[1])).array.join);
18 mixin(code_esc_special_chars);
19 
20 /** 
21     Range which takes a range of chars and converts to a range with added esc '\\' 
22 infront of a list fo special chars.
23 */
24 @safe
25 struct Escaper(S) if (isInputRange!S && is(ForeachType!S : const(char))) {
26     protected {
27         char escape_char;
28         S range;
29         ESCMode mode;
30     }
31     enum ESCMode {
32         none, /// Normal  char
33         esc, /// Esc char '\'
34         symbol /// Escaped symbol
35     }
36 
37     @disable this();
38     this(S range) pure {
39         this.range = range;
40     }
41 
42     pure {
43         bool empty() const {
44             return range.empty;
45         }
46 
47         char front() {
48             prepareEscape;
49             with (ESCMode) final switch (mode) {
50             case none:
51                 return cast(char) range.front;
52             case esc:
53                 return '\\';
54             case symbol:
55                 return escape_char;
56             }
57             assert(0);
58         }
59 
60         void popFront() {
61             with (ESCMode) final switch (mode) {
62             case none:
63                 prepareEscape;
64                 range.popFront;
65                 break;
66             case esc:
67                 mode = symbol;
68                 break;
69             case symbol:
70                 mode = none;
71                 range.popFront;
72             }
73         }
74 
75         void prepareEscape() {
76             if (mode is ESCMode.none) {
77                 const index = escaped_special_chars.indexOf(range.front);
78                 if (index >= 0) {
79                     mode = ESCMode.esc;
80                     escape_char = special_chars[index];
81                 }
82             }
83         }
84     }
85 }
86 
87 @safe
88 Escaper!S escaper(S)(S range) {
89     return Escaper!S(range);
90 }
91 
92 ///Examples: Escaping a text range
93 @safe
94 unittest {
95     //    import std.stdio;
96     import std.algorithm.comparison : equal;
97 
98     { /// Simple string unchanged
99         auto test = escaper("text");
100         assert(equal(test, "text"));
101     }
102     { /// Unsert esc in front of control chars
103         auto test = escaper("t\n #name \r");
104         assert(equal(test, r"t\n #name \r"));
105     }
106 
107     { /// Inserts esc in front of  "
108         auto test = escaper("t \"#name\" ");
109         assert(equal(test, `t \"#name\" `));
110     }
111 }