1 module selectors.where;
2 
3 import std.algorithm;
4 import std.array;
5 import std.traits;
6 
7 version(unittest) {
8   import fluent.asserts;
9 }
10 
11 ///
12 struct WhereAnyProxy(T: U[], U) {
13   private {
14     U[] list;
15   }
16 
17   alias M = U.M;
18   alias RootType = U.RootType;
19 
20   this(T list) {
21     this.list = list;
22   }
23 
24   ///
25   auto dispatch(string name, P...)() {
26     mixin(`alias R = ReturnType!(U.` ~ name ~ `);`);
27 
28     R[] result;
29 
30     foreach(localWhere; list) {
31       mixin(`result ~= localWhere.` ~ name ~ `();`);
32     }
33 
34     return whereAnyProxy(result);
35   }
36 
37   static if(isAggregateType!M) {
38     static foreach(member; __traits(allMembers, M)) {
39       static if(!std.traits.isArray!(typeof(__traits(getMember, M, member))) || isSomeString!(typeof(__traits(getMember, M, member))) ) {
40         mixin(`
41           auto ` ~ member ~ `(P...)() {
42             return dispatch!("` ~ member ~ `", P)();
43           }
44         `);
45       }
46     }
47   }
48 
49 
50   /// Does nothing but improve code readability
51   auto and() {
52 
53     foreach(localWhere; list) {
54       localWhere.and;
55     }
56 
57     return this;
58   }
59 
60   /// Negates the next filter
61   auto not() {
62 
63     foreach(localWhere; list) {
64       localWhere.not;
65     }
66 
67     return this;
68   }
69 
70   /// Check equality
71   auto equal(M value)() {
72 
73     foreach(index, localWhere; list) {
74       list[index].equal!value;
75     }
76 
77     return this;
78   }
79 
80   /// Returns all items that match at least one value
81   auto isAnyOf(T...)() {
82     foreach(localWhere; list) {
83       localWhere.isAnyOf!T;
84     }
85 
86     return this;
87   }
88 
89   /// Check if the filtered list has at least one value
90   bool exists() {
91     return !list.filter!(a => a.exists).empty;
92   }
93 
94   /// Iterate over the filtered items
95   int opApply(scope int delegate(RootType) dg) {
96     int result = 0;
97 
98     foreach(item; list) {
99       if(item.exists) {
100         result = dg(item.rootItem);
101       }
102 
103       if (result)
104         break;
105     }
106     return result;
107   }
108 
109   /// Iterate over the filtered items
110   int opApply(scope int delegate(size_t index, RootType) dg) {
111     int result = 0;
112 
113     foreach(index, item; list) {
114       if(item.exists) {
115         result = dg(index, item.rootItem);
116       }
117 
118       if (result)
119         break;
120     }
121     return result;
122   }
123 }
124 
125 ///
126 auto whereAnyProxy(T)(T list) {
127   return WhereAnyProxy!(T)(list);
128 }
129 
130 ///
131 struct WhereAny(T : U[], string path, U) {
132   private {
133     U[] list;
134     bool negation;
135   }
136 
137   static if(path == "") {
138     alias M = U;
139   } else {
140     mixin(`alias M = typeof(U` ~ path ~ `);`);
141   }
142 
143   auto dispatch(string name)() {
144     mixin(`
145     return whereAnyProxy(list.map!(item => where(item, item.` ~ name ~ `)).array);
146     `);
147   }
148 
149   static if(isAggregateType!M) {
150     static foreach(member; __traits(allMembers, M)) {
151       static if(std.traits.isArray!(typeof(__traits(getMember, M, member))) && !isSomeString!(typeof(__traits(getMember, M, member))) ) {
152         mixin(`
153           auto ` ~ member ~ `() {
154             return dispatch!"` ~ member ~ `";
155           }
156         `);
157       }
158     }
159   }
160 }
161 
162 ///
163 struct Where(T : U[], string path, RootType_, U) {
164   private {
165     U[] list;
166     bool negation;
167   }
168 
169   alias RootType = RootType_;
170 
171   static if(path == "") {
172     alias M = U;
173   } else {
174     mixin(`alias M = typeof(U` ~ path ~ `);`);
175   }
176 
177   ///
178   this(U[] list) {
179     this.list = list;
180   }
181 
182   static if(!is(RootType == void)) {
183     private RootType _root;
184 
185     this(U[] list, RootType root) {
186       this.list = list;
187       this._root = root;
188     }
189 
190     ///
191     RootType rootItem() {
192       return _root;
193     }
194   }
195 
196   /// Filter by a member name
197   private auto dispatch(string member)() {
198     static if (path == "") {
199       enum newPath = "." ~ member;
200     } else {
201       enum newPath = path ~ "." ~ member;
202     }
203 
204     static assert(__traits(hasMember, M, member), "The `" ~ M.stringof ~ "` type has no `" ~ member ~ "` property.");
205 
206     static if(is(RootType == void)) {
207       return Where!(T, newPath, RootType)(list);
208     } else {
209       return Where!(T, newPath, RootType)(list, rootItem);
210     }
211   }
212 
213   static if(isAggregateType!M) {
214     static foreach(member; __traits(allMembers, M)) {
215       static if(!std.traits.isArray!(typeof(__traits(getMember, M, member))) || isSomeString!(typeof(__traits(getMember, M, member))) ) {
216         mixin(`
217           auto ` ~ member ~ `() {
218             return dispatch!"` ~ member ~ `";
219           }
220         `);
221       }
222     }
223   }
224 
225   /// Query array properties
226   auto any() {
227     return WhereAny!(T, path)(list);
228   }
229 
230   /// Does nothing but improve code readability
231   auto and() {
232     return this;
233   }
234 
235   /// Negates the next filter
236   auto not() {
237     negation = !negation;
238     return this;
239   }
240 
241   /// Check equality
242   auto equal(M value)() {
243     bool aff(const U item) pure {
244       mixin(`return item` ~ path ~ ` == value;`);
245     }
246 
247     bool neg(const U item) pure {
248       mixin(`return item` ~ path ~ ` != value;`);
249     }
250 
251     if(negation) {
252       list = list.filter!neg.array;
253     } else {
254       list = list.filter!aff.array;
255     }
256 
257     return this;
258   }
259 
260   /// Returns all items that match at least one value
261   auto isAnyOf(T...)() {
262     string[] names;
263     static foreach(t; T) {
264       names ~= t;
265     }
266 
267     bool aff(const U item) pure {
268       mixin(`return names.canFind(item` ~ path ~ `);`);
269     }
270 
271     bool neg(const U item) pure {
272       mixin(`return !names.canFind(item` ~ path ~ `);`);
273     }
274 
275     if(negation) {
276       list = list.filter!neg.array;
277     } else {
278       list = list.filter!aff.array;
279     }
280 
281     return this;
282   }
283 
284   /// Check if the filtered list has at least one value
285   bool exists() {
286     return list.length > 0;
287   }
288 
289   /// Iterate over the filtered items
290   int opApply(scope int delegate(ref U) dg) {
291     int result = 0;
292 
293     foreach(item; list) {
294       result = dg(item);
295 
296       if (result)
297         break;
298     }
299     return result;
300   }
301 
302   /// Iterate over the filtered items
303   int opApply(scope int delegate(size_t index, ref U) dg) {
304     int result = 0;
305 
306     foreach(index, item; list) {
307       result = dg(index, item);
308 
309       if (result)
310         break;
311     }
312     return result;
313   }
314 }
315 
316 /// Filter callables by attribute name
317 unittest {
318   import introspection.callable;
319   import introspection.attribute;
320 
321   @("test")
322   void test() { }
323 
324   enum item = describeCallable!test;
325   enum items = [ item ];
326 
327   ///auto hasAttribute = items.where.any.attributes.name.equal!`"test"`.exists;
328 
329   //hasAttribute.should.equal(true);
330   items.where.any.attributes.name.equal!"other".exists.should.equal(false);
331 }
332 
333 version(unittest) { struct TestStructure { } }
334 
335 /// Filter callables by type name
336 unittest {
337   import introspection.aggregate;
338 
339   enum item = describeAggregate!TestStructure;
340   enum items = [ item ];
341 
342   items.where.type.name.equal!"TestStructure".and.exists.should.equal(true);
343   items.where.type.fullyQualifiedName.equal!"selectors.where.TestStructure".and.exists.should.equal(true);
344   items.where.type.fullyQualifiedName.equal!"selectors.where.OtherStructure".and.exists.should.equal(false);
345 
346   items.where.type.name.isAnyOf!"TestStructure".and.exists.should.equal(true);
347   items.where.type.fullyQualifiedName.isAnyOf!"selectors.where.TestStructure".and.exists.should.equal(true);
348   items.where.type.fullyQualifiedName.isAnyOf!"selectors.where.OtherStructure".and.exists.should.equal(false);
349 
350   items.where.type.name.not.equal!"TestStructure".and.exists.should.equal(false);
351   items.where.type.fullyQualifiedName.not.equal!"selectors.where.TestStructure".and.exists.should.equal(false);
352   items.where.type.fullyQualifiedName.not.equal!"selectors.where.OtherStructure".and.exists.should.equal(true);
353 
354   items.where.type.name.not.isAnyOf!"TestStructure".and.exists.should.equal(false);
355   items.where.type.fullyQualifiedName.not.isAnyOf!"selectors.where.TestStructure".and.exists.should.equal(false);
356   items.where.type.fullyQualifiedName.not.isAnyOf!"selectors.where.OtherStructure".and.exists.should.equal(true);
357 }
358 
359 /// Can iterate over filtered values
360 unittest {
361   import introspection.callable;
362 
363   @("test")
364   void foo() { }
365 
366   enum item = describeCallable!foo;
367   enum items = [ item ];
368 
369   /// iterate without index
370   size_t index;
371   foreach(element; items.where.any.attributes.name.equal!`"test"`) {
372     index.should.equal(0);
373     element.name.should.equal("foo");
374     index++;
375   }
376 
377   index = 0;
378   foreach(_; items.where.any.attributes.name.equal!`"other"`) {
379     index++;
380   }
381   index.should.equal(0);
382 
383   /// iterate with index
384   foreach(i, element; items.where.any.attributes.name.equal!`"test"`) {
385     i.should.equal(0);
386     element.name.should.equal("foo");
387   }
388 }
389 
390 /// query the introspection result
391 auto where(T)(T list) {
392   return Where!(T, "", void)(list);
393 }
394 
395 /// query the introspection result
396 auto where(T, U)(T rootItem, U list) {
397   return Where!(U, "", T)(list, rootItem);
398 }