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 );