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