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 }