1 | // Copyright (c) 2021 Lars Pontoppidan. All rights reserved. |
2 | // Use of this source code is governed by an MIT license |
3 | // that can be found in the LICENSE file. |
4 | module toml |
5 | |
6 | // Pretty much all the same builtin types as the `json2.Any` type plus `DateTime`,`Date`,`Time` |
7 | pub type Any = Date |
8 | | DateTime |
9 | | Null |
10 | | Time |
11 | | []Any |
12 | | bool |
13 | | f32 |
14 | | f64 |
15 | | i64 |
16 | | int |
17 | | map[string]Any |
18 | | string |
19 | | u64 |
20 | |
21 | // string returns `Any` as a string. |
22 | pub fn (a Any) string() string { |
23 | match a { |
24 | // NOTE if `.clone()` is not used here: |
25 | // string { return a as string } |
26 | // ... certain call-patterns to this function will cause a memory corruption. |
27 | // See `tests/toml_memory_corruption_test.v` for a matching regression test. |
28 | string { return (a as string).clone() } |
29 | DateTime { return a.str().clone() } |
30 | Date { return a.str().clone() } |
31 | Time { return a.str().clone() } |
32 | else { return a.str().clone() } |
33 | } |
34 | } |
35 | |
36 | // to_toml returns `Any` as a TOML encoded value. |
37 | pub fn (a Any) to_toml() string { |
38 | match a { |
39 | map[string]Any { |
40 | // TODO more format control? |
41 | return a.to_inline_toml() |
42 | } |
43 | []Any { |
44 | return a.to_toml() |
45 | } |
46 | bool, f32, f64, i64, int, u64 { |
47 | return a.str().clone() |
48 | } |
49 | // NOTE if `.clone()` is not used here: |
50 | // string { return a as string } |
51 | // ... certain call-patterns to this function will cause a memory corruption. |
52 | // See `tests/toml_memory_corruption_test.v` for a matching regression test. |
53 | string { |
54 | return '"' + (a as string).clone() + '"' |
55 | } |
56 | DateTime { |
57 | return a.str().clone() |
58 | } |
59 | Date { |
60 | return a.str().clone() |
61 | } |
62 | Time { |
63 | return a.str().clone() |
64 | } |
65 | else { |
66 | return a.str().clone() |
67 | } |
68 | } |
69 | } |
70 | |
71 | // int returns `Any` as an 32-bit integer. |
72 | pub fn (a Any) int() int { |
73 | match a { |
74 | int { return a } |
75 | i64, f32, f64, bool { return int(a) } |
76 | // time.Time { return int(0) } // TODO |
77 | else { return 0 } |
78 | } |
79 | } |
80 | |
81 | // i64 returns `Any` as a 64-bit integer. |
82 | pub fn (a Any) i64() i64 { |
83 | match a { |
84 | i64 { return a } |
85 | int, f32, f64, bool { return i64(a) } |
86 | // time.Time { return i64(0) } // TODO |
87 | else { return 0 } |
88 | } |
89 | } |
90 | |
91 | // u64 returns `Any` as a 64-bit unsigned integer. |
92 | pub fn (a Any) u64() u64 { |
93 | match a { |
94 | u64 { return a } |
95 | int, i64, f32, f64, bool { return u64(a) } |
96 | // time.Time { return u64(0) } // TODO |
97 | else { return 0 } |
98 | } |
99 | } |
100 | |
101 | // f32 returns `Any` as a 32-bit float. |
102 | pub fn (a Any) f32() f32 { |
103 | match a { |
104 | f32 { return a } |
105 | int, i64, f64 { return f32(a) } |
106 | // time.Time { return f32(0) } // TODO |
107 | else { return 0.0 } |
108 | } |
109 | } |
110 | |
111 | // f64 returns `Any` as a 64-bit float. |
112 | pub fn (a Any) f64() f64 { |
113 | match a { |
114 | f64 { return a } |
115 | int, i64, f32 { return f64(a) } |
116 | // time.Time { return f64(0) } // TODO |
117 | else { return 0.0 } |
118 | } |
119 | } |
120 | |
121 | // array returns `Any` as an array. |
122 | pub fn (a Any) array() []Any { |
123 | if a is []Any { |
124 | return a |
125 | } else if a is map[string]Any { |
126 | mut arr := []Any{} |
127 | for _, v in a { |
128 | arr << v |
129 | } |
130 | return arr |
131 | } |
132 | return [a] |
133 | } |
134 | |
135 | // as_map returns `Any` as a map (TOML table). |
136 | pub fn (a Any) as_map() map[string]Any { |
137 | if a is map[string]Any { |
138 | return a |
139 | } else if a is []Any { |
140 | mut mp := map[string]Any{} |
141 | for i, fi in a { |
142 | mp['${i}'] = fi |
143 | } |
144 | return mp |
145 | } |
146 | return { |
147 | '0': a |
148 | } |
149 | } |
150 | |
151 | // bool returns `Any` as a boolean. |
152 | pub fn (a Any) bool() bool { |
153 | match a { |
154 | bool { return a } |
155 | string { return a.bool() } |
156 | else { return false } |
157 | } |
158 | } |
159 | |
160 | // date returns `Any` as a `toml.Date` struct. |
161 | pub fn (a Any) date() Date { |
162 | match a { |
163 | // string { } // TODO |
164 | // NOTE `.clone()` is to avoid memory corruption see `pub fn (a Any) string() string` |
165 | Date { return Date{a.str().clone()} } |
166 | else { return Date{''} } |
167 | } |
168 | } |
169 | |
170 | // time returns `Any` as a `toml.Time` struct. |
171 | pub fn (a Any) time() Time { |
172 | match a { |
173 | // string { } // TODO |
174 | // NOTE `.clone()` is to avoid memory corruption see `pub fn (a Any) string() string` |
175 | Time { return Time{a.str().clone()} } |
176 | else { return Time{''} } |
177 | } |
178 | } |
179 | |
180 | // datetime returns `Any` as a `toml.DateTime` struct. |
181 | pub fn (a Any) datetime() DateTime { |
182 | match a { |
183 | // string { } // TODO |
184 | // NOTE `.clone()` is to avoid memory corruption see `pub fn (a Any) string() string` |
185 | DateTime { return DateTime{a.str().clone()} } |
186 | else { return DateTime{''} } |
187 | } |
188 | } |
189 | |
190 | // default_to returns `value` if `a Any` is `Null`. |
191 | // This can be used to set default values when retrieving |
192 | // values. E.g.: `toml_doc.value('wrong.key').default_to(123).int()` |
193 | pub fn (a Any) default_to(value Any) Any { |
194 | match a { |
195 | Null { return value } |
196 | else { return a } |
197 | } |
198 | } |
199 | |
200 | // value queries a value from the map. |
201 | // `key` supports a small query syntax scheme: |
202 | // Maps can be queried in "dotted" form e.g. `a.b.c`. |
203 | // quoted keys are supported as `a."b.c"` or `a.'b.c'`. |
204 | // Arrays can be queried with `a[0].b[1].[2]`. |
205 | pub fn (m map[string]Any) value(key string) Any { |
206 | return Any(m).value(key) |
207 | } |
208 | |
209 | // as_strings returns the contents of the map |
210 | // as `map[string]string` |
211 | pub fn (m map[string]Any) as_strings() map[string]string { |
212 | mut result := map[string]string{} |
213 | for k, v in m { |
214 | result[k] = v.string() |
215 | } |
216 | return result |
217 | } |
218 | |
219 | // to_toml returns the contents of the map |
220 | // as a TOML encoded `string`. |
221 | pub fn (m map[string]Any) to_toml() string { |
222 | mut toml_text := '' |
223 | for k, v in m { |
224 | key := if k.contains(' ') { '"${k}"' } else { k } |
225 | toml_text += '${key} = ${v.to_toml()}\n' |
226 | } |
227 | toml_text = toml_text.trim_right('\n') |
228 | return toml_text |
229 | } |
230 | |
231 | // to_inline_toml returns the contents of the map |
232 | // as an inline table encoded TOML `string`. |
233 | pub fn (m map[string]Any) to_inline_toml() string { |
234 | mut toml_text := '{' |
235 | mut i := 1 |
236 | for k, v in m { |
237 | key := if k.contains(' ') { '"${k}"' } else { k } |
238 | delimeter := if i < m.len { ',' } else { '' } |
239 | toml_text += ' ${key} = ${v.to_toml()}${delimeter}' |
240 | i++ |
241 | } |
242 | return toml_text + ' }' |
243 | } |
244 | |
245 | // value queries a value from the array. |
246 | // `key` supports a small query syntax scheme: |
247 | // The array can be queried with `[0].b[1].[2]`. |
248 | // Maps can be queried in "dotted" form e.g. `a.b.c`. |
249 | // quoted keys are supported as `a."b.c"` or `a.'b.c'`. |
250 | pub fn (a []Any) value(key string) Any { |
251 | return Any(a).value(key) |
252 | } |
253 | |
254 | // as_strings returns the contents of the array |
255 | // as `[]string` |
256 | pub fn (a []Any) as_strings() []string { |
257 | mut sa := []string{} |
258 | for any in a { |
259 | sa << any.string() |
260 | } |
261 | return sa |
262 | } |
263 | |
264 | // to_toml returns the contents of the array |
265 | // as a TOML encoded `string`. |
266 | pub fn (a []Any) to_toml() string { |
267 | mut toml_text := '[\n' |
268 | for any in a { |
269 | toml_text += ' ' + any.to_toml() + ',\n' |
270 | } |
271 | toml_text = toml_text.trim_right(',\n') |
272 | return toml_text + '\n]' |
273 | } |
274 | |
275 | // value queries a value from the `Any` type. |
276 | // `key` supports a small query syntax scheme: |
277 | // Maps can be queried in "dotted" form e.g. `a.b.c`. |
278 | // quoted keys are supported as `a."b.c"` or `a.'b.c'`. |
279 | // Arrays can be queried with `a[0].b[1].[2]`. |
280 | pub fn (a Any) value(key string) Any { |
281 | key_split := parse_dotted_key(key) or { return null } |
282 | return a.value_(a, key_split) |
283 | } |
284 | |
285 | // value_opt queries a value from the current element's tree. Returns an error |
286 | // if the key is not valid or there is no value for the key. |
287 | pub fn (a Any) value_opt(key string) !Any { |
288 | key_split := parse_dotted_key(key) or { return error('invalid dotted key') } |
289 | x := a.value_(a, key_split) |
290 | if x is Null { |
291 | return error('no value for key') |
292 | } |
293 | return x |
294 | } |
295 | |
296 | // value_ returns the `Any` value found at `key`. |
297 | fn (a Any) value_(value Any, key []string) Any { |
298 | if key.len == 0 { |
299 | return null |
300 | } |
301 | mut any_value := null |
302 | k, index := parse_array_key(key[0]) |
303 | if k == '' { |
304 | arr := value as []Any |
305 | any_value = arr[index] or { return null } |
306 | } |
307 | if value is map[string]Any { |
308 | any_value = value[k] or { return null } |
309 | if index > -1 { |
310 | arr := any_value as []Any |
311 | any_value = arr[index] or { return null } |
312 | } |
313 | } |
314 | if key.len <= 1 { |
315 | return any_value |
316 | } |
317 | match any_value { |
318 | map[string]Any, []Any { |
319 | return a.value_(any_value, key[1..]) |
320 | } |
321 | else { |
322 | return value |
323 | } |
324 | } |
325 | } |
326 | |
327 | // reflect returns `T` with `T.<field>`'s value set to the |
328 | // value of any 1st level TOML key by the same name. |
329 | pub fn (a Any) reflect[T]() T { |
330 | mut reflected := T{} |
331 | $for field in T.fields { |
332 | mut toml_field_name := field.name |
333 | // Remapping of field names, for example: |
334 | // TOML: 'assert = "ok"' |
335 | // V: User { asrt string [toml: 'assert'] } |
336 | // User.asrt == 'ok' |
337 | for attr in field.attrs { |
338 | if attr.starts_with('toml:') { |
339 | toml_field_name = attr.all_after(':').trim_space() |
340 | } |
341 | } |
342 | |
343 | $if field.typ is string { |
344 | reflected.$(field.name) = a.value(toml_field_name).default_to('').string() |
345 | } $else $if field.typ is bool { |
346 | reflected.$(field.name) = a.value(toml_field_name).default_to(false).bool() |
347 | } $else $if field.typ is int { |
348 | reflected.$(field.name) = a.value(toml_field_name).default_to(0).int() |
349 | } $else $if field.typ is f32 { |
350 | reflected.$(field.name) = a.value(toml_field_name).default_to(0.0).f32() |
351 | } $else $if field.typ is f64 { |
352 | reflected.$(field.name) = a.value(toml_field_name).default_to(0.0).f64() |
353 | } $else $if field.typ is i64 { |
354 | reflected.$(field.name) = a.value(toml_field_name).default_to(0).i64() |
355 | } $else $if field.typ is u64 { |
356 | reflected.$(field.name) = a.value(toml_field_name).default_to(0).u64() |
357 | } $else $if field.typ is Any { |
358 | reflected.$(field.name) = a.value(toml_field_name) |
359 | } $else $if field.typ is DateTime { |
360 | dt := DateTime{'0000-00-00T00:00:00.000'} |
361 | reflected.$(field.name) = a.value(toml_field_name).default_to(dt).datetime() |
362 | } $else $if field.typ is Date { |
363 | da := Date{'0000-00-00'} |
364 | reflected.$(field.name) = a.value(toml_field_name).default_to(da).date() |
365 | } $else $if field.typ is Time { |
366 | t := Time{'00:00:00.000'} |
367 | reflected.$(field.name) = a.value(toml_field_name).default_to(t).time() |
368 | } |
369 | // Arrays of primitive types |
370 | $else $if field.typ is []string { |
371 | any_array := a.value(toml_field_name).array() |
372 | reflected.$(field.name) = any_array.as_strings() |
373 | } $else $if field.typ is []bool { |
374 | any_array := a.value(toml_field_name).array() |
375 | mut arr := []bool{cap: any_array.len} |
376 | for any_value in any_array { |
377 | arr << any_value.bool() |
378 | } |
379 | reflected.$(field.name) = arr |
380 | } $else $if field.typ is []int { |
381 | any_array := a.value(toml_field_name).array() |
382 | mut arr := []int{cap: any_array.len} |
383 | for any_value in any_array { |
384 | arr << any_value.int() |
385 | } |
386 | reflected.$(field.name) = arr |
387 | } $else $if field.typ is []f32 { |
388 | any_array := a.value(toml_field_name).array() |
389 | mut arr := []f32{cap: any_array.len} |
390 | for any_value in any_array { |
391 | arr << any_value.f32() |
392 | } |
393 | reflected.$(field.name) = arr |
394 | } $else $if field.typ is []f64 { |
395 | any_array := a.value(toml_field_name).array() |
396 | mut arr := []f64{cap: any_array.len} |
397 | for any_value in any_array { |
398 | arr << any_value.f64() |
399 | } |
400 | reflected.$(field.name) = arr |
401 | } $else $if field.typ is []i64 { |
402 | any_array := a.value(toml_field_name).array() |
403 | mut arr := []i64{cap: any_array.len} |
404 | for any_value in any_array { |
405 | arr << any_value.i64() |
406 | } |
407 | reflected.$(field.name) = arr |
408 | } $else $if field.typ is []u64 { |
409 | any_array := a.value(toml_field_name).array() |
410 | mut arr := []u64{cap: any_array.len} |
411 | for any_value in any_array { |
412 | arr << any_value.u64() |
413 | } |
414 | reflected.$(field.name) = arr |
415 | } $else $if field.typ is []Any { |
416 | reflected.$(field.name) = a.value(toml_field_name).array() |
417 | } $else $if field.typ is []DateTime { |
418 | any_array := a.value(toml_field_name).array() |
419 | mut arr := []DateTime{cap: any_array.len} |
420 | for any_value in any_array { |
421 | arr << any_value.datetime() |
422 | } |
423 | reflected.$(field.name) = arr |
424 | } $else $if field.typ is []Date { |
425 | any_array := a.value(toml_field_name).array() |
426 | mut arr := []Date{cap: any_array.len} |
427 | for any_value in any_array { |
428 | arr << any_value.date() |
429 | } |
430 | reflected.$(field.name) = arr |
431 | } $else $if field.typ is []Time { |
432 | any_array := a.value(toml_field_name).array() |
433 | mut arr := []Time{cap: any_array.len} |
434 | for any_value in any_array { |
435 | arr << any_value.time() |
436 | } |
437 | reflected.$(field.name) = arr |
438 | } |
439 | // String key maps of primitive types |
440 | $else $if field.typ is map[string]string { |
441 | any_map := a.value(toml_field_name).as_map() |
442 | reflected.$(field.name) = any_map.as_strings() |
443 | } $else $if field.typ is map[string]bool { |
444 | any_map := a.value(toml_field_name).as_map() |
445 | mut type_map := map[string]bool{} |
446 | for k, any_value in any_map { |
447 | type_map[k] = any_value.bool() |
448 | } |
449 | reflected.$(field.name) = type_map.clone() |
450 | } $else $if field.typ is map[string]int { |
451 | any_map := a.value(toml_field_name).as_map() |
452 | mut type_map := map[string]int{} |
453 | for k, any_value in any_map { |
454 | type_map[k] = any_value.int() |
455 | } |
456 | reflected.$(field.name) = type_map.clone() |
457 | } $else $if field.typ is map[string]f32 { |
458 | any_map := a.value(toml_field_name).as_map() |
459 | mut type_map := map[string]f32{} |
460 | for k, any_value in any_map { |
461 | type_map[k] = any_value.f32() |
462 | } |
463 | reflected.$(field.name) = type_map.clone() |
464 | } $else $if field.typ is map[string]f64 { |
465 | any_map := a.value(toml_field_name).as_map() |
466 | mut type_map := map[string]f64{} |
467 | for k, any_value in any_map { |
468 | type_map[k] = any_value.f64() |
469 | } |
470 | reflected.$(field.name) = type_map.clone() |
471 | } $else $if field.typ is map[string]i64 { |
472 | any_map := a.value(toml_field_name).as_map() |
473 | mut type_map := map[string]i64{} |
474 | for k, any_value in any_map { |
475 | type_map[k] = any_value.i64() |
476 | } |
477 | reflected.$(field.name) = type_map.clone() |
478 | } $else $if field.typ is map[string]u64 { |
479 | any_map := a.value(toml_field_name).as_map() |
480 | mut type_map := map[string]u64{} |
481 | for k, any_value in any_map { |
482 | type_map[k] = any_value.u64() |
483 | } |
484 | reflected.$(field.name) = type_map.clone() |
485 | } $else $if field.typ is map[string]Any { |
486 | reflected.$(field.name) = a.value(toml_field_name).as_map() |
487 | } $else $if field.typ is map[string]DateTime { |
488 | any_map := a.value(toml_field_name).as_map() |
489 | mut type_map := map[string]DateTime{} |
490 | for k, any_value in any_map { |
491 | type_map[k] = any_value.datetime() |
492 | } |
493 | reflected.$(field.name) = type_map.clone() |
494 | } $else $if field.typ is map[string]Date { |
495 | any_map := a.value(toml_field_name).as_map() |
496 | mut type_map := map[string]Date{} |
497 | for k, any_value in any_map { |
498 | type_map[k] = any_value.date() |
499 | } |
500 | reflected.$(field.name) = type_map.clone() |
501 | } $else $if field.typ is map[string]Time { |
502 | any_map := a.value(toml_field_name).as_map() |
503 | mut type_map := map[string]Time{} |
504 | for k, any_value in any_map { |
505 | type_map[k] = any_value.time() |
506 | } |
507 | reflected.$(field.name) = type_map.clone() |
508 | } |
509 | } |
510 | return reflected |
511 | } |