// Copyright (c) 2021 Lars Pontoppidan. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module toml // Pretty much all the same builtin types as the `json2.Any` type plus `DateTime`,`Date`,`Time` pub type Any = Date | DateTime | Null | Time | []Any | bool | f32 | f64 | i64 | int | map[string]Any | string | u64 // string returns `Any` as a string. pub fn (a Any) string() string { match a { // NOTE if `.clone()` is not used here: // string { return a as string } // ... certain call-patterns to this function will cause a memory corruption. // See `tests/toml_memory_corruption_test.v` for a matching regression test. string { return (a as string).clone() } DateTime { return a.str().clone() } Date { return a.str().clone() } Time { return a.str().clone() } else { return a.str().clone() } } } // to_toml returns `Any` as a TOML encoded value. pub fn (a Any) to_toml() string { match a { map[string]Any { // TODO more format control? return a.to_inline_toml() } []Any { return a.to_toml() } bool, f32, f64, i64, int, u64 { return a.str().clone() } // NOTE if `.clone()` is not used here: // string { return a as string } // ... certain call-patterns to this function will cause a memory corruption. // See `tests/toml_memory_corruption_test.v` for a matching regression test. string { return '"' + (a as string).clone() + '"' } DateTime { return a.str().clone() } Date { return a.str().clone() } Time { return a.str().clone() } else { return a.str().clone() } } } // int returns `Any` as an 32-bit integer. pub fn (a Any) int() int { match a { int { return a } i64, f32, f64, bool { return int(a) } // time.Time { return int(0) } // TODO else { return 0 } } } // i64 returns `Any` as a 64-bit integer. pub fn (a Any) i64() i64 { match a { i64 { return a } int, f32, f64, bool { return i64(a) } // time.Time { return i64(0) } // TODO else { return 0 } } } // u64 returns `Any` as a 64-bit unsigned integer. pub fn (a Any) u64() u64 { match a { u64 { return a } int, i64, f32, f64, bool { return u64(a) } // time.Time { return u64(0) } // TODO else { return 0 } } } // f32 returns `Any` as a 32-bit float. pub fn (a Any) f32() f32 { match a { f32 { return a } int, i64, f64 { return f32(a) } // time.Time { return f32(0) } // TODO else { return 0.0 } } } // f64 returns `Any` as a 64-bit float. pub fn (a Any) f64() f64 { match a { f64 { return a } int, i64, f32 { return f64(a) } // time.Time { return f64(0) } // TODO else { return 0.0 } } } // array returns `Any` as an array. pub fn (a Any) array() []Any { if a is []Any { return a } else if a is map[string]Any { mut arr := []Any{} for _, v in a { arr << v } return arr } return [a] } // as_map returns `Any` as a map (TOML table). pub fn (a Any) as_map() map[string]Any { if a is map[string]Any { return a } else if a is []Any { mut mp := map[string]Any{} for i, fi in a { mp['${i}'] = fi } return mp } return { '0': a } } // bool returns `Any` as a boolean. pub fn (a Any) bool() bool { match a { bool { return a } string { return a.bool() } else { return false } } } // date returns `Any` as a `toml.Date` struct. pub fn (a Any) date() Date { match a { // string { } // TODO // NOTE `.clone()` is to avoid memory corruption see `pub fn (a Any) string() string` Date { return Date{a.str().clone()} } else { return Date{''} } } } // time returns `Any` as a `toml.Time` struct. pub fn (a Any) time() Time { match a { // string { } // TODO // NOTE `.clone()` is to avoid memory corruption see `pub fn (a Any) string() string` Time { return Time{a.str().clone()} } else { return Time{''} } } } // datetime returns `Any` as a `toml.DateTime` struct. pub fn (a Any) datetime() DateTime { match a { // string { } // TODO // NOTE `.clone()` is to avoid memory corruption see `pub fn (a Any) string() string` DateTime { return DateTime{a.str().clone()} } else { return DateTime{''} } } } // default_to returns `value` if `a Any` is `Null`. // This can be used to set default values when retrieving // values. E.g.: `toml_doc.value('wrong.key').default_to(123).int()` pub fn (a Any) default_to(value Any) Any { match a { Null { return value } else { return a } } } // value queries a value from the map. // `key` supports a small query syntax scheme: // Maps can be queried in "dotted" form e.g. `a.b.c`. // quoted keys are supported as `a."b.c"` or `a.'b.c'`. // Arrays can be queried with `a[0].b[1].[2]`. pub fn (m map[string]Any) value(key string) Any { return Any(m).value(key) } // as_strings returns the contents of the map // as `map[string]string` pub fn (m map[string]Any) as_strings() map[string]string { mut result := map[string]string{} for k, v in m { result[k] = v.string() } return result } // to_toml returns the contents of the map // as a TOML encoded `string`. pub fn (m map[string]Any) to_toml() string { mut toml_text := '' for k, v in m { key := if k.contains(' ') { '"${k}"' } else { k } toml_text += '${key} = ${v.to_toml()}\n' } toml_text = toml_text.trim_right('\n') return toml_text } // to_inline_toml returns the contents of the map // as an inline table encoded TOML `string`. pub fn (m map[string]Any) to_inline_toml() string { mut toml_text := '{' mut i := 1 for k, v in m { key := if k.contains(' ') { '"${k}"' } else { k } delimeter := if i < m.len { ',' } else { '' } toml_text += ' ${key} = ${v.to_toml()}${delimeter}' i++ } return toml_text + ' }' } // value queries a value from the array. // `key` supports a small query syntax scheme: // The array can be queried with `[0].b[1].[2]`. // Maps can be queried in "dotted" form e.g. `a.b.c`. // quoted keys are supported as `a."b.c"` or `a.'b.c'`. pub fn (a []Any) value(key string) Any { return Any(a).value(key) } // as_strings returns the contents of the array // as `[]string` pub fn (a []Any) as_strings() []string { mut sa := []string{} for any in a { sa << any.string() } return sa } // to_toml returns the contents of the array // as a TOML encoded `string`. pub fn (a []Any) to_toml() string { mut toml_text := '[\n' for any in a { toml_text += ' ' + any.to_toml() + ',\n' } toml_text = toml_text.trim_right(',\n') return toml_text + '\n]' } // value queries a value from the `Any` type. // `key` supports a small query syntax scheme: // Maps can be queried in "dotted" form e.g. `a.b.c`. // quoted keys are supported as `a."b.c"` or `a.'b.c'`. // Arrays can be queried with `a[0].b[1].[2]`. pub fn (a Any) value(key string) Any { key_split := parse_dotted_key(key) or { return null } return a.value_(a, key_split) } // value_opt queries a value from the current element's tree. Returns an error // if the key is not valid or there is no value for the key. pub fn (a Any) value_opt(key string) !Any { key_split := parse_dotted_key(key) or { return error('invalid dotted key') } x := a.value_(a, key_split) if x is Null { return error('no value for key') } return x } // value_ returns the `Any` value found at `key`. fn (a Any) value_(value Any, key []string) Any { if key.len == 0 { return null } mut any_value := null k, index := parse_array_key(key[0]) if k == '' { arr := value as []Any any_value = arr[index] or { return null } } if value is map[string]Any { any_value = value[k] or { return null } if index > -1 { arr := any_value as []Any any_value = arr[index] or { return null } } } if key.len <= 1 { return any_value } match any_value { map[string]Any, []Any { return a.value_(any_value, key[1..]) } else { return value } } } // reflect returns `T` with `T.`'s value set to the // value of any 1st level TOML key by the same name. pub fn (a Any) reflect[T]() T { mut reflected := T{} $for field in T.fields { mut toml_field_name := field.name // Remapping of field names, for example: // TOML: 'assert = "ok"' // V: User { asrt string [toml: 'assert'] } // User.asrt == 'ok' for attr in field.attrs { if attr.starts_with('toml:') { toml_field_name = attr.all_after(':').trim_space() } } $if field.typ is string { reflected.$(field.name) = a.value(toml_field_name).default_to('').string() } $else $if field.typ is bool { reflected.$(field.name) = a.value(toml_field_name).default_to(false).bool() } $else $if field.typ is int { reflected.$(field.name) = a.value(toml_field_name).default_to(0).int() } $else $if field.typ is f32 { reflected.$(field.name) = a.value(toml_field_name).default_to(0.0).f32() } $else $if field.typ is f64 { reflected.$(field.name) = a.value(toml_field_name).default_to(0.0).f64() } $else $if field.typ is i64 { reflected.$(field.name) = a.value(toml_field_name).default_to(0).i64() } $else $if field.typ is u64 { reflected.$(field.name) = a.value(toml_field_name).default_to(0).u64() } $else $if field.typ is Any { reflected.$(field.name) = a.value(toml_field_name) } $else $if field.typ is DateTime { dt := DateTime{'0000-00-00T00:00:00.000'} reflected.$(field.name) = a.value(toml_field_name).default_to(dt).datetime() } $else $if field.typ is Date { da := Date{'0000-00-00'} reflected.$(field.name) = a.value(toml_field_name).default_to(da).date() } $else $if field.typ is Time { t := Time{'00:00:00.000'} reflected.$(field.name) = a.value(toml_field_name).default_to(t).time() } // Arrays of primitive types $else $if field.typ is []string { any_array := a.value(toml_field_name).array() reflected.$(field.name) = any_array.as_strings() } $else $if field.typ is []bool { any_array := a.value(toml_field_name).array() mut arr := []bool{cap: any_array.len} for any_value in any_array { arr << any_value.bool() } reflected.$(field.name) = arr } $else $if field.typ is []int { any_array := a.value(toml_field_name).array() mut arr := []int{cap: any_array.len} for any_value in any_array { arr << any_value.int() } reflected.$(field.name) = arr } $else $if field.typ is []f32 { any_array := a.value(toml_field_name).array() mut arr := []f32{cap: any_array.len} for any_value in any_array { arr << any_value.f32() } reflected.$(field.name) = arr } $else $if field.typ is []f64 { any_array := a.value(toml_field_name).array() mut arr := []f64{cap: any_array.len} for any_value in any_array { arr << any_value.f64() } reflected.$(field.name) = arr } $else $if field.typ is []i64 { any_array := a.value(toml_field_name).array() mut arr := []i64{cap: any_array.len} for any_value in any_array { arr << any_value.i64() } reflected.$(field.name) = arr } $else $if field.typ is []u64 { any_array := a.value(toml_field_name).array() mut arr := []u64{cap: any_array.len} for any_value in any_array { arr << any_value.u64() } reflected.$(field.name) = arr } $else $if field.typ is []Any { reflected.$(field.name) = a.value(toml_field_name).array() } $else $if field.typ is []DateTime { any_array := a.value(toml_field_name).array() mut arr := []DateTime{cap: any_array.len} for any_value in any_array { arr << any_value.datetime() } reflected.$(field.name) = arr } $else $if field.typ is []Date { any_array := a.value(toml_field_name).array() mut arr := []Date{cap: any_array.len} for any_value in any_array { arr << any_value.date() } reflected.$(field.name) = arr } $else $if field.typ is []Time { any_array := a.value(toml_field_name).array() mut arr := []Time{cap: any_array.len} for any_value in any_array { arr << any_value.time() } reflected.$(field.name) = arr } // String key maps of primitive types $else $if field.typ is map[string]string { any_map := a.value(toml_field_name).as_map() reflected.$(field.name) = any_map.as_strings() } $else $if field.typ is map[string]bool { any_map := a.value(toml_field_name).as_map() mut type_map := map[string]bool{} for k, any_value in any_map { type_map[k] = any_value.bool() } reflected.$(field.name) = type_map.clone() } $else $if field.typ is map[string]int { any_map := a.value(toml_field_name).as_map() mut type_map := map[string]int{} for k, any_value in any_map { type_map[k] = any_value.int() } reflected.$(field.name) = type_map.clone() } $else $if field.typ is map[string]f32 { any_map := a.value(toml_field_name).as_map() mut type_map := map[string]f32{} for k, any_value in any_map { type_map[k] = any_value.f32() } reflected.$(field.name) = type_map.clone() } $else $if field.typ is map[string]f64 { any_map := a.value(toml_field_name).as_map() mut type_map := map[string]f64{} for k, any_value in any_map { type_map[k] = any_value.f64() } reflected.$(field.name) = type_map.clone() } $else $if field.typ is map[string]i64 { any_map := a.value(toml_field_name).as_map() mut type_map := map[string]i64{} for k, any_value in any_map { type_map[k] = any_value.i64() } reflected.$(field.name) = type_map.clone() } $else $if field.typ is map[string]u64 { any_map := a.value(toml_field_name).as_map() mut type_map := map[string]u64{} for k, any_value in any_map { type_map[k] = any_value.u64() } reflected.$(field.name) = type_map.clone() } $else $if field.typ is map[string]Any { reflected.$(field.name) = a.value(toml_field_name).as_map() } $else $if field.typ is map[string]DateTime { any_map := a.value(toml_field_name).as_map() mut type_map := map[string]DateTime{} for k, any_value in any_map { type_map[k] = any_value.datetime() } reflected.$(field.name) = type_map.clone() } $else $if field.typ is map[string]Date { any_map := a.value(toml_field_name).as_map() mut type_map := map[string]Date{} for k, any_value in any_map { type_map[k] = any_value.date() } reflected.$(field.name) = type_map.clone() } $else $if field.typ is map[string]Time { any_map := a.value(toml_field_name).as_map() mut type_map := map[string]Time{} for k, any_value in any_map { type_map[k] = any_value.time() } reflected.$(field.name) = type_map.clone() } } return reflected }