1 /++ 2 + Copyright: Copyright © 2018, Christian Köstlin 3 + License: MIT 4 + Authors: Christian Koestlin 5 +/ 6 module matcher; 7 8 import std.algorithm; 9 import std..string; 10 import std.range; 11 import std.exception; 12 13 class Matcher(T) 14 { 15 /++ 16 + Checks if s is an accepted values. 17 + Params: 18 + context = some context information for error messages 19 + v = what to check 20 + Throws: Exception if not accepted. 21 +/ 22 abstract void accept(string context, T v); 23 24 /++ 25 + Deliver a description of what is accepted. 26 +/ 27 override abstract string toString(); 28 } 29 30 class Just(T) : Matcher!T 31 { 32 T value; 33 this(T v) 34 { 35 this.value = v; 36 } 37 38 override void accept(string context, T v) 39 { 40 enforce(v == value, "%s is not allowed, just %s is allowed".format(v, value)); 41 } 42 43 override string toString() 44 { 45 return "just %s".format(value); 46 } 47 } 48 49 class Everything(T) : Matcher!T 50 { 51 override void accept(string context, T v) 52 { 53 } 54 55 override string toString() 56 { 57 return "everything"; 58 } 59 } 60 61 class Set(T) : Matcher!T 62 { 63 private T[] values; 64 this(T[] values) 65 { 66 this.values = values; 67 } 68 69 static auto of(V...)(V values) 70 { 71 return fromArray([values]); 72 } 73 74 static auto fromArray(T[] values) 75 { 76 return new Set(values); 77 } 78 79 static auto fromEnum(E)() 80 { 81 import std.traits; 82 import std.conv; 83 84 return fromArray([EnumMembers!E].map!(e => e.to!string).array); 85 } 86 87 override void accept(string context, T givenValues) 88 { 89 foreach (v; givenValues.split(",")) 90 { 91 enforce(values.canFind(v), 92 "%s is not in allowed values of '%s': %s".format(v, context, values)); 93 } 94 } 95 96 override string toString() 97 { 98 return "set from %s".format(values); 99 } 100 } 101 102 string trimPlusMinus(string s) 103 { 104 if (s.length == 0) 105 { 106 return s; 107 } 108 109 auto first = s[0]; 110 switch (first) 111 { 112 case '+': 113 case '-': 114 return s[1 .. $]; 115 default: 116 return s; 117 } 118 } 119 120 class PlusMinusSet(T) : Matcher!T 121 { 122 private string[] values; 123 this(string[] values) 124 { 125 this.values = values; 126 } 127 128 static auto fromArray(string[] values) 129 { 130 return new PlusMinuxSet(values); 131 } 132 133 override void accept(string context, T givenValues) 134 { 135 import std.exception; 136 137 foreach (v; givenValues.split(",").map!(a => a.trimPlusMinus)) 138 { 139 enforce(values.canFind(v), 140 "%s is not in allowed values of '%s': %s".format(v, context, values)); 141 } 142 } 143 144 override string toString() 145 { 146 return "+/- set from %s".format(values); 147 } 148 } 149 150 class One(T) : Matcher!T 151 { 152 private Set!T impl; 153 bool done = false; 154 this(T[] values) 155 { 156 impl = new Set!T(values); 157 } 158 159 static auto of(V...)(V values) 160 { 161 return fromArray([values]); 162 } 163 164 static auto fromArray(T[] values) 165 { 166 return new One(values); 167 } 168 169 static auto fromEnum(E)() 170 { 171 import std.traits; 172 import std.conv; 173 174 return fromArray([EnumMembers!E].map!(e => e.to!string).array); 175 } 176 177 override void accept(string context, T v) 178 { 179 if (done) 180 { 181 throw new Exception("Only one value allowed for '%s'".format(context)); 182 } 183 impl.accept(context, v); 184 done = true; 185 } 186 187 override string toString() 188 { 189 return "one from %s".format(impl.values); 190 } 191 }