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 }