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 }