v / vlib / toml / any.v
516 lines · 490 sloc · 13.63 KB · 3c0358cce78c7722cfadeabe12a86d34f6dc1ea4
Raw
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.
4module toml
5
6// Pretty much all the same builtin types as the `json2.Any` type plus `DateTime`,`Date`,`Time`
7pub 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.
22pub 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.
37pub 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.
72pub 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.
82pub 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.
92pub 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.
102pub 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.
112pub 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.
122pub 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).
136pub 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.
152pub 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.
161pub 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.
171pub 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.
181pub 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()`
193pub 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]`.
205pub 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`
211pub 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`.
221pub 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`.
233pub 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 delimiter := if i < m.len { ',' } else { '' }
239 toml_text += ' ${key} = ${v.to_toml()}${delimiter}'
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'`.
250pub 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`
256pub 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`.
266pub 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]`.
280pub 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.
287pub 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`.
297fn (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.
329pub fn (a Any) reflect[T]() T {
330 mut reflected := T{}
331 $for field in T.fields {
332 mut toml_field_name := field.name
333 mut skip := false
334 // Remapping of field names, for example:
335 // TOML: 'assert = "ok"'
336 // V: User { asrt string @[toml: 'assert'] }
337 // User.asrt == 'ok'
338 for attr in field.attrs {
339 if attr == 'skip' {
340 skip = true
341 break
342 }
343 if attr.starts_with('toml:') {
344 toml_field_name = attr.all_after(':').trim_space()
345 }
346 }
347 value := a.value(toml_field_name)
348 // only set the field's value when value != null and !skip, else field got it's default value
349 if !skip && value != null {
350 $if field.typ is string {
351 reflected.$(field.name) = value.string()
352 } $else $if field.typ is bool {
353 reflected.$(field.name) = value.bool()
354 } $else $if field.typ is int {
355 reflected.$(field.name) = value.int()
356 } $else $if field.typ is f32 {
357 reflected.$(field.name) = value.f32()
358 } $else $if field.typ is f64 {
359 reflected.$(field.name) = value.f64()
360 } $else $if field.typ is i64 {
361 reflected.$(field.name) = value.i64()
362 } $else $if field.typ is u64 {
363 reflected.$(field.name) = value.u64()
364 } $else $if field.typ is Any {
365 reflected.$(field.name) = value
366 } $else $if field.typ is DateTime {
367 reflected.$(field.name) = value.datetime()
368 } $else $if field.typ is Date {
369 reflected.$(field.name) = value.date()
370 } $else $if field.typ is Time {
371 reflected.$(field.name) = value.time()
372 }
373 // Arrays of primitive types
374 $else $if field.typ is []string {
375 any_array := value.array()
376 reflected.$(field.name) = any_array.as_strings()
377 } $else $if field.typ is []bool {
378 any_array := value.array()
379 mut arr := []bool{cap: any_array.len}
380 for any_value in any_array {
381 arr << any_value.bool()
382 }
383 reflected.$(field.name) = arr
384 } $else $if field.typ is []int {
385 any_array := value.array()
386 mut arr := []int{cap: any_array.len}
387 for any_value in any_array {
388 arr << any_value.int()
389 }
390 reflected.$(field.name) = arr
391 } $else $if field.typ is []f32 {
392 any_array := value.array()
393 mut arr := []f32{cap: any_array.len}
394 for any_value in any_array {
395 arr << any_value.f32()
396 }
397 reflected.$(field.name) = arr
398 } $else $if field.typ is []f64 {
399 any_array := value.array()
400 mut arr := []f64{cap: any_array.len}
401 for any_value in any_array {
402 arr << any_value.f64()
403 }
404 reflected.$(field.name) = arr
405 } $else $if field.typ is []i64 {
406 any_array := value.array()
407 mut arr := []i64{cap: any_array.len}
408 for any_value in any_array {
409 arr << any_value.i64()
410 }
411 reflected.$(field.name) = arr
412 } $else $if field.typ is []u64 {
413 any_array := value.array()
414 mut arr := []u64{cap: any_array.len}
415 for any_value in any_array {
416 arr << any_value.u64()
417 }
418 reflected.$(field.name) = arr
419 } $else $if field.typ is []Any {
420 reflected.$(field.name) = value.array()
421 } $else $if field.typ is []DateTime {
422 any_array := value.array()
423 mut arr := []DateTime{cap: any_array.len}
424 for any_value in any_array {
425 arr << any_value.datetime()
426 }
427 reflected.$(field.name) = arr
428 } $else $if field.typ is []Date {
429 any_array := value.array()
430 mut arr := []Date{cap: any_array.len}
431 for any_value in any_array {
432 arr << any_value.date()
433 }
434 reflected.$(field.name) = arr
435 } $else $if field.typ is []Time {
436 any_array := value.array()
437 mut arr := []Time{cap: any_array.len}
438 for any_value in any_array {
439 arr << any_value.time()
440 }
441 reflected.$(field.name) = arr
442 }
443 // String key maps of primitive types
444 $else $if field.typ is map[string]string {
445 any_map := value.as_map()
446 reflected.$(field.name) = any_map.as_strings()
447 } $else $if field.typ is map[string]bool {
448 any_map := value.as_map()
449 mut type_map := map[string]bool{}
450 for k, any_value in any_map {
451 type_map[k] = any_value.bool()
452 }
453 reflected.$(field.name) = type_map.clone()
454 } $else $if field.typ is map[string]int {
455 any_map := value.as_map()
456 mut type_map := map[string]int{}
457 for k, any_value in any_map {
458 type_map[k] = any_value.int()
459 }
460 reflected.$(field.name) = type_map.clone()
461 } $else $if field.typ is map[string]f32 {
462 any_map := value.as_map()
463 mut type_map := map[string]f32{}
464 for k, any_value in any_map {
465 type_map[k] = any_value.f32()
466 }
467 reflected.$(field.name) = type_map.clone()
468 } $else $if field.typ is map[string]f64 {
469 any_map := value.as_map()
470 mut type_map := map[string]f64{}
471 for k, any_value in any_map {
472 type_map[k] = any_value.f64()
473 }
474 reflected.$(field.name) = type_map.clone()
475 } $else $if field.typ is map[string]i64 {
476 any_map := value.as_map()
477 mut type_map := map[string]i64{}
478 for k, any_value in any_map {
479 type_map[k] = any_value.i64()
480 }
481 reflected.$(field.name) = type_map.clone()
482 } $else $if field.typ is map[string]u64 {
483 any_map := value.as_map()
484 mut type_map := map[string]u64{}
485 for k, any_value in any_map {
486 type_map[k] = any_value.u64()
487 }
488 reflected.$(field.name) = type_map.clone()
489 } $else $if field.typ is map[string]Any {
490 reflected.$(field.name) = value.as_map()
491 } $else $if field.typ is map[string]DateTime {
492 any_map := value.as_map()
493 mut type_map := map[string]DateTime{}
494 for k, any_value in any_map {
495 type_map[k] = any_value.datetime()
496 }
497 reflected.$(field.name) = type_map.clone()
498 } $else $if field.typ is map[string]Date {
499 any_map := value.as_map()
500 mut type_map := map[string]Date{}
501 for k, any_value in any_map {
502 type_map[k] = any_value.date()
503 }
504 reflected.$(field.name) = type_map.clone()
505 } $else $if field.typ is map[string]Time {
506 any_map := value.as_map()
507 mut type_map := map[string]Time{}
508 for k, any_value in any_map {
509 type_map[k] = any_value.time()
510 }
511 reflected.$(field.name) = type_map.clone()
512 }
513 }
514 }
515 return reflected
516}
517