1 module toml_foolery.decode.set_data; 2 3 import std.algorithm; 4 import std.conv; 5 import std.datetime; 6 import std.range.primitives; 7 import std.traits; 8 import std.meta; 9 10 import toml_foolery.attributes.toml_name; 11 import toml_foolery.decode.exceptions; 12 13 14 /// A magical function which puts `value` into `dest`, inside the field indicated by `address`. 15 package void setData(S, T)(ref S dest, string[] address, const T value) 16 if (is(S == struct)) 17 in (address.length > 0, "`address` may not be empty") 18 { 19 switch (address[0]) 20 { 21 enum isPublic(string e) = __traits(getProtection, __traits(getMember, dest, e)) == "public"; 22 enum isFieldOrPropertyOfDest(string e) = isFieldOrProperty!(dest, e); 23 enum isNotThis(string e) = e != "this"; 24 25 alias publicFieldsAndProperties = 26 Filter!(isPublic, 27 Filter!(isFieldOrPropertyOfDest, 28 Filter!(isNotThis, 29 __traits(allMembers, S) 30 ) 31 ) 32 ); 33 34 static foreach (string member; publicFieldsAndProperties) 35 { 36 // Create a `case` for each public field (or property) of S, except for `this`. 37 case dFieldToTomlKey!(S, member): 38 { 39 static assert( 40 !( 41 isCallable!(__traits(getMember, S, member)) && 42 is(ReturnType!(__traits(getMember, S, member)) == struct) && 43 hasFunctionAttributes!(__traits(getMember, S, member), "@property") 44 ), 45 `Field "` ~ member ~ `" of struct "` ~ S.stringof ~ `" is a public property. ` ~ 46 `Make it private to ignore it, or make it a regular field to allow placing data inside.` 47 ); 48 49 if (address.length == 1) 50 { 51 static if ( 52 __traits( 53 compiles, 54 __traits(getMember, dest, member) = value.to!(typeof(__traits(getMember, dest, member))) 55 ) 56 ) 57 { 58 try 59 { 60 __traits(getMember, dest, member) = value.to!(typeof(__traits(getMember, dest, member))); 61 } 62 catch (ConvOverflowException e) 63 { 64 throw new TomlTypeException( 65 `Key "` ~ dFieldToTomlKey!(S, member) ~ `"` ~ 66 ` has value ` ~ value.to!string ~ ` which cannot fit in field ` ~ 67 S.stringof ~ `.` ~ member ~ 68 ` of type ` ~ typeof(__traits(getMember, dest, member)).stringof, 69 e 70 ); 71 } 72 return; 73 } 74 else static if (__traits(compiles, typeof(__traits(getMember, dest, member)))) 75 { 76 throw new TomlTypeException( 77 `Member "` ~ member ~ `" of struct "` ~ S.stringof ~ 78 `" is of type "` ~ typeof(__traits(getMember, dest, member)).stringof ~ 79 `", but given value is type "` ~ typeof(value).stringof ~ `".` 80 ); 81 } 82 else 83 { 84 throw new TomlDecodingException(`Member "` ~ member ~ `" is not a valid destination.`); 85 } 86 } 87 else 88 { 89 // ...is a struct or array (allowing a recursive call), but isn't a @property 90 // (since those return structs as rvalues which cannot be passed as ref) 91 92 // Do nothing if it's a specially-handled struct. 93 static if ( 94 (is(typeof(__traits(getMember, dest, member)) == TimeOfDay)) || 95 (is(typeof(__traits(getMember, dest, member)) == SysTime)) || 96 (is(typeof(__traits(getMember, dest, member)) == DateTime)) || 97 (is(typeof(__traits(getMember, dest, member)) == Date)) 98 ) 99 { 100 return; 101 } 102 else static if(__traits(compiles, setData(__traits(getMember, dest, member), address[1..$], value))) 103 { 104 setData! 105 (typeof(__traits(getMember, dest, member)), T) 106 (__traits(getMember, dest, member), address[1..$], value); 107 108 return; 109 } 110 else 111 { 112 throw new TomlDecodingException( 113 `Could not place ` ~ T.stringof ~ ` "` ~ value.to!string ~ 114 `" into ` ~ S.stringof ~ 115 `'s field "` ~ typeof(__traits(getMember, S, member)).stringof ~ ` ` ~ member ~ 116 `" at address ` ~ address.to!string ~ 117 `. This might be a bug — please file a report.` 118 ); 119 } 120 } 121 } 122 } 123 124 default: 125 break; 126 } 127 128 } 129 130 /// ditto 131 package void setData(S, T)(ref S dest, string[] address, const T value) 132 if (isArray!S) 133 in (address.length > 0, "`address` may not be empty") 134 in (address[0].isSizeT, `address[0] = "` ~ address[0] ~ `" which is not convertible to size_t.`) 135 { 136 size_t idx = address[0].to!size_t; 137 if (idx >= dest.length) 138 { 139 static if (isStaticArray!S) 140 { 141 // ignore 142 return; 143 } 144 else 145 { 146 static assert(isDynamicArray!S, "Encountered an array that is neither static nor dynamic (???)"); 147 dest.length = idx + 1; 148 } 149 } 150 151 if (address.length == 1) 152 { 153 // Defined as a function so that it can go inside a __traits(compiles, ...) 154 static void setIndex(S, T)(ref S dest, size_t idx, T value) 155 { 156 dest[idx] = value; 157 } 158 159 static if (__traits(compiles, setIndex(dest, idx, value.to!(ElementType!S)))) 160 { 161 setIndex(dest, idx, value.to!(ElementType!S)); 162 } 163 else 164 { 165 assert ( 166 false, 167 `Invalid operation: ` ~ 168 dest.to!string ~ `[` ~ idx.to!string ~ `] = 169 ` ~ value.to!string ~ `.to!` ~ (ElementType!S).stringof 170 ); 171 } 172 } 173 else 174 { 175 static if (isArray!(typeof(dest[idx])) || is(typeof(dest[idx]) == struct)) 176 { 177 // Do nothing if it's a specially-handled struct. 178 static if ( 179 !(is(typeof(dest[idx]) == TimeOfDay)) && 180 !(is(typeof(dest[idx]) == SysTime)) && 181 !(is(typeof(dest[idx]) == DateTime)) && 182 !(is(typeof(dest[idx]) == Date)) 183 ) 184 { 185 setData(dest[idx], address[1 .. $], value); 186 } 187 } 188 } 189 } 190 191 private bool isSizeT(string s) 192 { 193 import std.conv : ConvException; 194 195 try 196 { 197 s.to!size_t; 198 return true; 199 } 200 catch (ConvException e) 201 { 202 return false; 203 } 204 } 205 206 @("isSizeT") 207 unittest 208 { 209 import std.bigint; 210 import exceeds_expectations; 211 expect(size_t.min.to!string.isSizeT).toEqual(true); 212 expect(size_t.max.to!string.isSizeT).toEqual(true); 213 expect((BigInt(size_t.max) + 1).to!string.isSizeT).toEqual(false); 214 expect((-1).to!string.isSizeT).toEqual(false); 215 } 216 217 private enum isFieldOrProperty(alias dest, string member) = ( 218 !is(__traits(getMember, dest, member)) && // it can't be a nested struct 219 ( 220 !isCallable!(__traits(getMember, dest, member)) || // and it can't be callable 221 hasFunctionAttributes!(__traits(getMember, dest, member), "@property") // unless it's a @property function 222 ) 223 );