| 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 | 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'`. |
| 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 | 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 | |