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