v / vlib / toml
Raw file | 511 loc (484 sloc) | 14.05 KB | Latest commit hash ef5be22f8
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 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'`.
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 // 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}