1 2 module hashes; 3 4 import std.algorithm; 5 import std.conv; 6 import std.stdio; 7 import std.traits; 8 import std.variant; 9 10 /++ 11 + Tests if the given type is a hash. 12 +/ 13 enum isHash(Type : Hash!args, args...) = true; 14 /++ 15 + Ditto. 16 +/ 17 enum isHash(Type) = false; 18 19 /++ 20 + A templated datatype which stores key => value pairs. 21 + Keys and values stored in hash need not be unique. 22 + 23 + Querying hashes can be done with both compile-time and run-time values. 24 + --- 25 + Hash!(a => 1) hash; 26 + 27 + assert(hash["a"] == Variant(1)); 28 + assert(hash.value!"a" == 1); 29 + --- 30 + 31 + Hashes also allow for duplicate keys, and value selection by type. 32 + --- 33 + int xInt; 34 + string xString; 35 + Hash!(x => 5, x => "10") hash; 36 + 37 + hash.get("x", xInt); // Or xInt = hash.value!("x", int); 38 + hash.get("x", xString); // Or xString = hash.value!("x", string); 39 + 40 + assert(xInt == 5); 41 + assert(xString == "10"); 42 + --- 43 +/ 44 struct Hash(args...) 45 { 46 // Internal use only. 47 private struct HashType 48 { 49 // Nothing. 50 } 51 52 // Internal use only. 53 private enum _valid = _isValidArgs; 54 private static bool _isValidArgs() 55 { 56 enum invalidHashKey = "Type `%s` in Hash[index => %d] is not a valid key."; 57 58 foreach(index, arg; args) 59 { 60 static if(!is(typeof(arg!HashType))) 61 { 62 import std.string : format; 63 static assert(false, invalidHashKey.format(typeof(arg).stringof, index + 1)); 64 return false; 65 } 66 } 67 68 return true; 69 } 70 71 /++ 72 + Applies the hash onto a value which is a class or struct given by `T`. 73 + 74 + Fields in the destination value are assigned for which there exists 75 + an element with a matching key and type. 76 + 77 + For example, 78 + --- 79 + struct User 80 + { 81 + string name; 82 + int age; 83 + bool admin; 84 + bool active; 85 + } 86 + 87 + User user; 88 + Hash!(name => "Jesse", age => 24, active => "yes").apply(user); 89 + 90 + assert(user.name == "Jesse"); // name => "Jesse" 91 + assert(user.age == 24); // age => 24 92 + assert(user.admin == false); // Unchanged (no key) 93 + assert(user.active == false); // Unchanged (type mismatch) 94 + --- 95 +/ 96 static T apply(T)(auto ref T dest) if(is(T == class) || is(T == struct)) 97 { 98 foreach(member; __traits(allMembers, T)) 99 { 100 static if(hasKey(member)) 101 { 102 alias T = typeof(__traits(getMember, dest, member)); 103 __traits(getMember, dest, member) = value!(member, T); 104 } 105 } 106 107 return dest; 108 } 109 110 /++ 111 + Concatenates two hashes. Duplicate keys and values are preserved. 112 +/ 113 static auto concat(other...)(Hash!other) 114 { 115 return Hash!(args, other).init; 116 } 117 118 /++ 119 + Tests for an empty hash. 120 +/ 121 @property 122 enum bool empty = args.length == 0; 123 124 /++ 125 + Fetches an element by its runtime name and stores it into the 126 + destination parameter. 127 +/ 128 static bool get(T)(string name, out T dest) 129 { 130 foreach(arg; args) 131 { 132 alias key = arg!HashType; 133 static if(is(FunctionTypeOf!key Types == __parameters)) 134 { 135 static if(isAssignable!(T, typeof(key(HashType.init)))) 136 { 137 if(name == __traits(identifier, Types)) 138 { 139 dest = key(HashType.init); 140 return true; 141 } 142 } 143 else static if(is(T == Variant)) 144 { 145 if(name == __traits(identifier, Types)) 146 { 147 dest = Variant(key(HashType.init)); 148 return true; 149 } 150 } 151 } 152 } 153 154 return false; 155 } 156 157 /++ 158 + Checks for the presence of a key in the hash. 159 +/ 160 static bool hasKey()(string name) 161 { 162 return keys.countUntil(name) != -1; 163 } 164 165 /++ 166 + Ditto, but accepts a template parameter. 167 +/ 168 enum bool hasKey(string name) = hasKey(name); 169 170 /++ 171 + Unique set of key names in the hash. The order of keys is unspecified. 172 +/ 173 @property 174 enum string[] keys = _keys; 175 176 private static string[] _keys() 177 { 178 bool[string] keySet; 179 180 foreach(arg; args) 181 { 182 alias key = arg!HashType; 183 static if(is(FunctionTypeOf!key Types == __parameters)) 184 { 185 enum name = __traits(identifier, Types); 186 if(name !in keySet) 187 { 188 keySet[name] = true; 189 } 190 } 191 } 192 193 return keySet.keys; 194 } 195 196 /++ 197 + Returns the number of values in the hash. 198 +/ 199 @property 200 enum size_t length = args.length; 201 202 /++ 203 + Iterates over the values in the hash as Variants. 204 +/ 205 static int opApply(scope int delegate(Variant) dg) 206 { 207 foreach(arg; args) 208 { 209 alias key = arg!HashType; 210 static if(is(FunctionTypeOf!key Types == __parameters)) 211 { 212 auto value = Variant(key(HashType.init)); 213 214 if(int result = dg(value)) 215 { 216 return result; 217 } 218 } 219 } 220 221 return 0; 222 } 223 224 /++ 225 + Concatenates two hashes. Duplicate keys and values are preserved. 226 +/ 227 static auto opBinary(string op : "~", other...)(Hash!other o) 228 { 229 return concat(o); 230 } 231 232 /++ 233 + Ditto, but also iterates over keys. 234 +/ 235 static int opApply(scope int delegate(string, Variant) dg) 236 { 237 foreach(arg; args) 238 { 239 alias key = arg!HashType; 240 static if(is(FunctionTypeOf!key Types == __parameters)) 241 { 242 enum name = __traits(identifier, Types); 243 auto value = Variant(key(HashType.init)); 244 245 if(int result = dg(name, value)) 246 { 247 return result; 248 } 249 } 250 } 251 252 return 0; 253 } 254 255 /++ 256 + Fetches a value from the hash as a Variant. 257 +/ 258 static Variant opIndex(string name) 259 { 260 Variant value; 261 get(name, value); 262 return value; 263 } 264 265 /++ 266 + Returns a value from the hash by name. 267 + 268 + If the type parameter `T` is given, the value returned must match `T`. 269 + If no values with with matching a matching key and type exist within the 270 + hash, `T.init` is returned instead. 271 + 272 + Params: 273 + name = The name of the key to search for. 274 + T = An optional type constraint. 275 +/ 276 @property 277 static auto value(string name, T...)() if(hasKey(name) && T.length < 2) 278 { 279 static if(T.length == 1) 280 { 281 T[0] result = T[0].init; 282 } 283 284 foreach(arg; args) 285 { 286 alias key = arg!HashType; 287 static if(is(FunctionTypeOf!key Types == __parameters)) 288 { 289 static if(name == __traits(identifier, Types)) 290 { 291 static if(T.length == 0) 292 { 293 return key(HashType.init); 294 } 295 else static if(isAssignable!(T, typeof(key(HashType.init)))) 296 { 297 result = key(HashType.init); 298 } 299 } 300 } 301 } 302 303 static if(T.length == 1) 304 { 305 return result; 306 } 307 } 308 309 /++ 310 + Returns all values in the hash converted to a type given by `T`. 311 +/ 312 @property 313 static T[] values(T = Variant)() 314 { 315 T[] values; 316 317 foreach(arg; args) 318 { 319 alias key = arg!HashType; 320 values ~= to!T(key(HashType.init)); 321 } 322 323 return values; 324 } 325 } 326 327 /++ 328 + Automatically initializes static fields from Hash-style parameters. 329 + Only valid in structs and classes. 330 +/ 331 mixin template Hashify(args...) 332 { 333 @property 334 enum hashof = Hash!args.init; 335 336 static auto opDispatch(string op)() if(hashof.hasKey(op)) 337 { 338 return hashof.value!op; 339 } 340 } 341 342 /++ 343 + Shortcut for `Hash!(args).init` 344 +/ 345 auto hash(args...)() 346 { 347 return Hash!(args).init; 348 } 349 350 unittest 351 { 352 assert( isHash!(Hash!(a => 1, b => 2))); 353 assert(!isHash!(bool)); 354 assert(!isHash!(string)); 355 assert(!isHash!(bool[string])); 356 assert( isHash!(Hash!())); 357 assert( isHash!(Hash!(a => 1, a => 2, a => 3))); 358 assert(!isHash!(Object)); 359 } 360 361 unittest 362 { 363 assert(!__traits(compiles, { 364 Hash!( 365 a => 1, 366 b => 2, 367 c => 3, 368 false 369 ) hash; 370 })); 371 } 372 373 unittest 374 { 375 int x, y, z; 376 Hash!( 377 x => 1, 378 y => 2, 379 z => 3 380 ) hash; 381 382 assert(hash.hasKey("x")); 383 assert(hash.hasKey("y")); 384 assert(hash.hasKey("z")); 385 386 assert(hash.length == 3); 387 assert(hash.keys == [ "x", "y", "z" ]); 388 389 assert(hash.values == [ Variant(1), Variant(2), Variant(3) ]); 390 assert(hash.values!string == [ "1", "2", "3" ]); 391 assert(hash.values!int == [ 1, 2, 3 ]); 392 393 assert(hash.get("x", x)); 394 assert(hash.get("y", y)); 395 assert(hash.get("z", z)); 396 397 assert(hash["x"] == Variant(1)); 398 assert(hash["y"] == Variant(2)); 399 assert(hash["z"] == Variant(3)); 400 401 assert(x == 1); 402 assert(y == 2); 403 assert(z == 3); 404 } 405 406 unittest 407 { 408 auto test = hash!( 409 x => "foo", 410 y => "bar", 411 z => hash!( 412 a => 1, 413 b => 2 414 ) 415 ); 416 417 assert(test.value!"x" == "foo"); 418 assert(test.value!"y" == "bar"); 419 assert(test.value!"z".value!"a" == 1); 420 assert(test.value!"z".value!"b" == 2); 421 } 422 423 unittest 424 { 425 struct Test 426 { 427 int x; 428 int y; 429 int z; 430 } 431 432 Test test; 433 Hash!( 434 x => 1, 435 y => 2, 436 z => 3 437 ) hash; 438 439 hash.apply(test); 440 assert(test.x == 1); 441 assert(test.y == 2); 442 assert(test.z == 3); 443 } 444 445 unittest 446 { 447 static struct Test(args...) 448 { 449 mixin Hashify!args; 450 451 int a = hashof.value!"a"; 452 int b = hashof.value!"b"; 453 int c = hashof.value!"c"; 454 } 455 456 Test!( 457 a => 1, 458 b => 2, 459 c => 3 460 ) test; 461 462 assert(test.a == 1); 463 assert(test.b == 2); 464 assert(test.c == 3); 465 } 466 467 unittest 468 { 469 static struct Column(args...) 470 { 471 mixin Hashify!args; 472 473 string name = hashof.value!("name", string); 474 bool nullable = hashof.value!("nullable", bool); 475 } 476 477 class User 478 { 479 @Column!( 480 name => "nick_name", 481 nullable => true 482 ) 483 string nickname; 484 } 485 486 import std.meta : Alias; 487 static assert(__traits(getAttributes, User.nickname).length == 1); 488 alias NicknameColumn = Alias!(__traits(getAttributes, User.nickname)[0]); 489 490 NicknameColumn column; 491 assert(column.name == "nick_name"); 492 assert(column.nullable == true); 493 } 494 495 unittest 496 { 497 struct Test 498 { 499 int x; 500 int y; 501 int z; 502 503 this(H)(H hash) if(isHash!H) 504 { 505 hash.apply(this); 506 } 507 } 508 509 Test test = Test(hash!( 510 x => 2, 511 y => 3, 512 z => 4 513 )); 514 515 assert(test.x == 2); 516 assert(test.y == 3); 517 assert(test.z == 4); 518 } 519 520 unittest 521 { 522 auto a = hash!(a => 1, b => 2); 523 auto b = hash!(b => 3, c => 4); 524 auto c = a ~ b; 525 526 assert(c.length == 4); 527 assert(c.values!int == [ 1, 2, 3, 4 ]); 528 }