Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

@@ -0,0 +1,26 @@
import os
import toml
import toml.to
const toml_table_text = '
[[products]]
name = "Hammer"
sku = 738594937
[[products]] # empty table within the array
[[products]]
name = "Nail"
sku = 284758393
color = "gray"'
fn test_tables() {
mut toml_doc := toml.parse_text(toml_table_text) or { panic(err) }
toml_json := to.json(toml_doc)
assert toml_json == os.read_file(
os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) +
'.out') or { panic(err) }
}
@@ -0,0 +1,32 @@
import os
import toml
import toml.to
const toml_text = '[[albums]]
name = "Born to Run"
[[albums.songs]]
name = "Jungleland"
[[albums.songs]]
name = "Meeting Across the River"
[[albums]]
name = "Born in the USA"
[[albums.songs]]
name = "Glory Days"
[[albums.songs]]
name = "Dancing in the Dark"'
const fprefix = os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))
fn test_nested_array_of_tables() {
mut toml_doc := toml.parse_text(toml_text)!
toml_json := to.json(toml_doc)
eprintln(toml_json)
assert toml_json == os.read_file(fprefix + '.out')!
}
@@ -0,0 +1,23 @@
import os
import toml
import toml.to
const toml_text = '[[a]]
[[a.b]]
[a.b.c]
d = "val0"
[[a.b]]
[a.b.c]
d = "val1"
'
fn test_nested_array_of_tables() {
mut toml_doc := toml.parse_text(toml_text) or { panic(err) }
toml_json := to.json(toml_doc)
eprintln(toml_json)
assert toml_json == os.read_file(
os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) +
'.out') or { panic(err) }
}
@@ -0,0 +1,18 @@
import os
import toml
import toml.to
fn test_array_of_tables_edge_case_file() {
toml_file :=
os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) +
'.toml'
toml_doc := toml.parse_file(toml_file) or { panic(err) }
toml_json := to.json(toml_doc)
out_file :=
os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) +
'.out'
out_file_json := os.read_file(out_file) or { panic(err) }
println(toml_json)
assert toml_json == out_file_json
}
@@ -0,0 +1,15 @@
import os
import toml
import toml.to
const fprefix = os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))
fn test_array_of_tables_edge_case_file() {
toml_doc := toml.parse_file(os.real_path(fprefix + '.toml'))!
toml_json := to.json(toml_doc)
out_file_json := os.read_file(os.real_path(fprefix + '.out'))!
println(toml_json)
assert toml_json == out_file_json
}
+78
View File
@@ -0,0 +1,78 @@
import toml
// Complete text from the example in the README.md:
// https://github.com/toml-lang/toml/blob/3b11f6921da7b6f5db37af039aa021fee450c091/README.md#Example
const toml_text = '# This is a TOML document.
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00 # First class dates
[database]
server = "192.168.1.1"
ports = [ 8000, 8001, 8002 ]
connection_max = 5000
enabled = true
[servers]
# Indentation (tabs and/or spaces) is allowed but not required
[servers.alpha]
ip = "10.0.0.1"
dc = "eqdc10"
[servers.beta]
ip = "10.0.0.2"
dc = "eqdc10"
[clients]
data=[["gamma","delta"],[1,2]]
# Line breaks are OK when inside arrays
hosts = [
"alpha",
"omega"
]'
fn test_parse_compact_text() {
toml_doc := toml.parse_text(toml_text) or { panic(err) }
title := toml_doc.value('title')
assert title == toml.Any('TOML Example')
assert title as string == 'TOML Example'
database := toml_doc.value('database') as map[string]toml.Any
db_serv := database['server'] or {
panic('could not access "server" index in "database" variable')
}
assert db_serv as string == '192.168.1.1'
assert toml_doc.value('owner.name') as string == 'Tom Preston-Werner'
assert toml_doc.value('database.server') as string == '192.168.1.1'
database_ports := toml_doc.value('database.ports') as []toml.Any
assert database_ports[0] as i64 == 8000
assert database_ports[1] as i64 == 8001
assert database_ports[2] as i64 == 8002
assert database_ports[0].int() == 8000
assert database_ports[1].int() == 8001
assert database_ports[2].int() == 8002
assert toml_doc.value('database.connection_max') as i64 == 5000
assert toml_doc.value('database.enabled') as bool == true
assert toml_doc.value('servers.alpha.ip').string() == '10.0.0.1'
assert toml_doc.value('servers.alpha.dc').string() == 'eqdc10'
assert toml_doc.value('servers.beta.ip').string() == '10.0.0.2'
assert toml_doc.value('servers.beta.dc').string() == 'eqdc10'
clients_data := (toml_doc.value('clients.data') as []toml.Any)
// dump(clients_data)
// assert false
gamma_delta_array := clients_data[0] as []toml.Any
digits_array := clients_data[1] as []toml.Any
assert gamma_delta_array[0].string() == 'gamma'
assert gamma_delta_array[1].string() == 'delta'
assert digits_array[0].int() == 1
assert digits_array[1].int() == 2
clients_hosts := (toml_doc.value('clients.hosts') as []toml.Any).as_strings()
assert clients_hosts[0] == 'alpha'
assert clients_hosts[1] == 'omega'
}
+23
View File
@@ -0,0 +1,23 @@
import toml
fn test_crlf() {
str_value := 'test string'
mut toml_txt := 'crlf_string = "test string"\r\n
# Comment with CRLF is not allowed'
toml_doc := toml.parse_text(toml_txt) or { panic(err) }
value := toml_doc.value('crlf_string')
assert value == toml.Any(str_value)
assert value as string == str_value
assert value.string() == str_value
}
fn test_crlf_is_parsable_just_like_lf() {
crlf_content := '# a comment\r\ntitle = "TOML Example"\r\n[database]\r\nserver = "192.168.1.1"\r\nports = [ 8000, 8001, 8002 ]\r\n'
all := [crlf_content, crlf_content.replace('\r\n', '\n')]
for content in all {
res := toml.parse_text(content)!
assert res.value('title') == toml.Any('TOML Example')
assert (res.value('database') as map[string]toml.Any)['server']! == toml.Any('192.168.1.1')
}
}
+72
View File
@@ -0,0 +1,72 @@
import toml
fn test_dates() {
toml_txt := '
# Offset Date-Time
odt1 = 1979-05-27T07:32:00Z
odt2 = 1979-05-27T00:32:00-07:00
odt3 = 1979-05-27T00:32:00.999999-07:00
odt4 = 1979-05-27 07:32:00Z
# Local Date-Time
ldt1 = 1979-05-27T07:32:00
ldt2 = 1979-05-27T00:32:00.999999
# Local Date
ld1 = 1979-05-27
# Local Time
lt1 = 07:32:00
lt2 = 00:32:00.999999
'
toml_doc := toml.parse_text(toml_txt) or { panic(err) }
// Re-use vars
mut odt_time := toml.DateTime{'1979-05-27T07:32:00Z'}
mut odt_str := toml_doc.value('odt1').string()
// odt1 test section
assert odt_str == '1979-05-27T07:32:00Z'
odt1 := toml_doc.value('odt1')
assert odt1.datetime() == odt_time
// odt2 test section
odt_time = toml.DateTime{'1979-05-27T00:32:00-07:00'}
odt2 := toml_doc.value('odt2')
assert odt2.datetime() == odt_time
// odt3 test section
odt_time = toml.DateTime{'1979-05-27T00:32:00.999999-07:00'}
odt3 := toml_doc.value('odt3')
assert odt3.datetime() == odt_time
// odt4 test section
odt_time = toml.DateTime{'1979-05-27 07:32:00Z'}
odt4 := toml_doc.value('odt4')
assert odt4.datetime() == odt_time
// ldt1 test section
odt_time = toml.DateTime{'1979-05-27T07:32:00'}
ldt1 := toml_doc.value('ldt1')
assert ldt1.datetime() == odt_time
// ldt2 test section
odt_time = toml.DateTime{'1979-05-27T00:32:00.999999'}
ldt2 := toml_doc.value('ldt2')
assert ldt2.datetime() == odt_time
// ld1 test section
od_time := toml.Date{'1979-05-27'}
ld1 := toml_doc.value('ld1')
assert ld1.date() == od_time
assert ld1.string() == '1979-05-27'
// lt1 test section
mut ot_time := toml.Time{'07:32:00'}
lt1 := toml_doc.value('lt1')
assert lt1.time() == ot_time
assert lt1.string() == '07:32:00'
// lt2 test section
ot_time = toml.Time{'00:32:00.999999'}
lt2 := toml_doc.value('lt2')
assert lt2.time() == ot_time
assert lt2.string() == '00:32:00.999999'
}
@@ -0,0 +1,9 @@
import toml
fn test_default_to() {
default_value := 4321
mut toml_txt := 'var = 1234'
toml_doc := toml.parse_text(toml_txt) or { panic(err) }
value := toml_doc.value('tar').default_to(default_value).int()
assert value == default_value
}
@@ -0,0 +1,85 @@
import toml
const toml_text = '# This TOML can reflect/decode to a struct
val_string = "test"
val_bool = false
val_int = 456
val_i64 = 4567
val_u64 = 45678
val_f32 = 200.2
val_f64 = 2000.2
val_datetime = 2024-10-05 09:10:20.000
val_date = 2099-09-09
val_time = 22:22:22.222
'
const toml_all_default_text = '# This TOML can reflect/decode to a struct all with default values'
struct Test {
val_string string = 'abcd'
val_bool bool = true
val_int int = 123
val_i64 i64 = 1234
val_u64 u64 = 12345
val_f32 f32 = 100.1
val_f64 f64 = 1000.1
val_datetime toml.DateTime = toml.DateTime{'1980-07-11 21:23:42.123'}
val_date toml.Date = toml.Date{'1977-07-07'}
val_time toml.Time = toml.Time{'11:11:11.111'}
}
fn test_reflect_default_values() {
toml_has_values := toml.parse_text(toml_text) or { panic(err) }
test_has_values := toml_has_values.reflect[Test]()
assert test_has_values.val_string == 'test'
assert test_has_values.val_bool == false
assert test_has_values.val_int == 456
assert test_has_values.val_i64 == 4567
assert test_has_values.val_u64 == 45678
assert test_has_values.val_f32 == 200.2
assert test_has_values.val_f64 == 2000.2
assert test_has_values.val_datetime == toml.DateTime{'2024-10-05 09:10:20.000'}
assert test_has_values.val_date == toml.Date{'2099-09-09'}
assert test_has_values.val_time == toml.Time{'22:22:22.222'}
toml_all_default_values := toml.parse_text(toml_all_default_text) or { panic(err) }
test_all_default_values := toml_all_default_values.reflect[Test]()
assert test_all_default_values.val_string == 'abcd'
assert test_all_default_values.val_bool == true
assert test_all_default_values.val_int == 123
assert test_all_default_values.val_i64 == 1234
assert test_all_default_values.val_u64 == 12345
assert test_all_default_values.val_f32 == 100.1
assert test_all_default_values.val_f64 == 1000.1
assert test_all_default_values.val_datetime == toml.DateTime{'1980-07-11 21:23:42.123'}
assert test_all_default_values.val_date == toml.Date{'1977-07-07'}
assert test_all_default_values.val_time == toml.Time{'11:11:11.111'}
}
fn test_decode_struct_default_values() {
test_has_values := toml.decode[Test](toml_text) or { panic(err) }
assert test_has_values.val_string == 'test'
assert test_has_values.val_bool == false
assert test_has_values.val_int == 456
assert test_has_values.val_i64 == 4567
assert test_has_values.val_u64 == 45678
assert test_has_values.val_f32 == 200.2
assert test_has_values.val_f64 == 2000.2
assert test_has_values.val_datetime == toml.DateTime{'2024-10-05 09:10:20.000'}
assert test_has_values.val_date == toml.Date{'2099-09-09'}
assert test_has_values.val_time == toml.Time{'22:22:22.222'}
test_all_default_values := toml.decode[Test](toml_all_default_text) or { panic(err) }
assert test_all_default_values.val_string == 'abcd'
assert test_all_default_values.val_bool == true
assert test_all_default_values.val_int == 123
assert test_all_default_values.val_i64 == 1234
assert test_all_default_values.val_u64 == 12345
assert test_all_default_values.val_f32 == 100.1
assert test_all_default_values.val_f64 == 1000.1
assert test_all_default_values.val_datetime == toml.DateTime{'1980-07-11 21:23:42.123'}
assert test_all_default_values.val_date == toml.Date{'1977-07-07'}
assert test_all_default_values.val_time == toml.Time{'11:11:11.111'}
}
@@ -0,0 +1,403 @@
import toml
enum JobTitle {
worker
executive
manager
}
struct Pet {
name string
nicknames []string
age u64
income int
height f32
has_furr bool
title JobTitle
address Address
meal_frequency map[string]int
// *¹ Currently it is only possible to decode a single nested struct generically.
// As soon as we decode another nested struct (e.g. within this struct, like `contact` below)
// or only one nested struct within another struct, it results in wrong values or errors.
// Related issue: https://github.com/vlang/v/issues/18110
// contact Contact
}
struct Address {
street string
city string
}
// *¹
/*
struct Contact {
phone string
}*/
struct AnyStruct {
val toml.Any
}
struct Employee {
mut:
name string
age int
salary f32
is_human bool
title JobTitle
}
struct Arrs {
strs []string
bools []bool
ints []int
i64s []i64
u64s []u64
f32s []f32
f64s []f64
dts []toml.DateTime
dates []toml.Date
times []toml.Time
}
// individual because toml.decode[Foo](str)! == foo is false
struct AnyArr {
arr []toml.Any
}
struct Item {
name string
}
struct Doc {
items []Item
}
struct MapDoc {
items map[string]Item
}
fn test_encode_and_decode() {
// *¹
// p := Pet{'Mr. Scratchy McEvilPaws', ['Freddy', 'Fred', 'Charles'], 8, -1, 0.8, true, .manager, Address{'1428 Elm Street', 'Springwood'}, Contact{'123-456-7890'}}
p := Pet{'Mr. Scratchy McEvilPaws', ['Freddy', 'Fred', 'Charles'], 8, -1, 0.8, true, .manager, Address{'1428 Elm Street', 'Springwood'}, {
'bones': 2
'kibble': 5
}}
s := 'name = "Mr. Scratchy McEvilPaws"
nicknames = [
"Freddy",
"Fred",
"Charles"
]
age = 8
income = -1
height = 0.8
has_furr = true
title = 2
address = { street = "1428 Elm Street", city = "Springwood" }
meal_frequency = { bones = 2, kibble = 5 }'
// contact = { phone = "123-456-7890" }' // *¹
assert toml.encode[Pet](p) == s
assert toml.decode[Pet](s)! == p
}
fn test_encode_and_decode_any() {
a := AnyStruct{toml.Any(10)}
s := 'val = 10'
assert toml.encode[AnyStruct](a) == s
assert toml.decode[AnyStruct](s)!.val.int() == 10
}
pub fn (e Employee) to_toml() string {
mut mp := map[string]toml.Any{}
mp['name'] = toml.Any(e.name)
mp['age'] = toml.Any(e.age)
mp['is_human'] = toml.Any(e.is_human)
// Change some values to assert that the custom method is used instead of generic encoding.
mp['salary'] = toml.Any(f32(e.salary) + 5000.0)
mp['title'] = toml.Any(int(e.title) + 1)
return mp.to_toml()
}
pub fn (mut e Employee) from_toml(any toml.Any) {
mp := any.as_map()
e.name = mp['name'] or { toml.Any('') }.string()
e.age = mp['age'] or { toml.Any(0) }.int()
e.is_human = mp['is_human'] or { toml.Any(false) }.bool()
// Change some values to assert that the custom method is used instead of generic decoding.
e.salary = mp['salary'] or { toml.Any(0) }.f32() - 15000.0
e.title = unsafe { JobTitle(mp['title'] or { toml.Any(0) }.int() - 2) }
}
fn test_custom_encode_and_decode() {
x := Employee{'Peter', 28, 95000.5, true, .executive}
s := toml.encode[Employee](x)
eprintln('Employee x: ${s}')
assert s == r'name = "Peter"
age = 28
is_human = true
salary = 100000.5
title = 2'
y := toml.decode[Employee](s) or {
println(err)
assert false
return
}
eprintln('Employee y: ${y}')
assert y.name == 'Peter'
assert y.age == 28
assert y.salary == 85000.5
assert y.is_human == true
assert y.title == .worker
}
struct Example1 {
arr []Problem
}
struct Example2 {
arr []Problem
}
struct Problem {
x int
}
pub fn (example Example1) to_toml() string {
return '[This is Valid]'
}
pub fn (problem Problem) to_toml() string {
return 'a problem'
}
fn test_custom_encode_of_complex_struct() {
assert toml.encode(Example1{}) == '[This is Valid]'
assert toml.encode(Example2{[Problem{}, Problem{}]}) == 'arr = [
"a problem",
"a problem"
]'
}
struct Example3 {
arr_arr [][]Problem
}
struct Example4 {
mp map[string]Problem
}
pub fn (example Example3) to_toml() string {
return '[This is Valid]'
}
pub fn (example Example4) to_toml() string {
return '[This is Valid]'
}
fn test_custom_encode_of_nested_complex_struct() {
assert toml.encode(Example3{}) == '[This is Valid]'
assert toml.encode(Example4{}) == '[This is Valid]'
}
struct Example5 {
mp map[string]Problem
}
fn test_map_encode_of_complex_struct() {
mut mp := map[string]Problem{}
mp['key_one'] = Problem{}
mp['key_two'] = Problem{}
assert toml.encode(Example5{ mp: mp }) == 'mp = { key_one = "a problem", key_two = "a problem" }'
}
struct Example6 {
ptr voidptr
r rune
}
fn test_encode_for_exotic_types() {
assert toml.encode(Example6{ ptr: unsafe { &voidptr(nil) }, r: `🚀` }) == 'ptr = "0x0"\nr = "🚀"'
}
fn test_array_encode_decode() {
a := Arrs{
strs: ['foo', 'bar']
bools: [true, false]
ints: [-1, 2]
i64s: [i64(-2)]
u64s: [u64(123)]
f32s: [f32(1.0), f32(2.5)]
f64s: [100000.5, -123.0]
dts: [toml.DateTime{'1979-05-27T07:32:00Z'}, toml.DateTime{'1979-05-27T07:32:00Z'}]
dates: [toml.Date{'1979-05-27'}, toml.Date{'2022-12-31'}]
times: [toml.Time{'07:32:59'}, toml.Time{'17:32:04'}]
}
s := 'strs = [
"foo",
"bar"
]
bools = [
true,
false
]
ints = [
-1,
2
]
i64s = [
-2
]
u64s = [
123
]
f32s = [
1.0,
2.5
]
f64s = [
100000.5,
-123.0
]
dts = [
1979-05-27T07:32:00Z,
1979-05-27T07:32:00Z
]
dates = [
1979-05-27,
2022-12-31
]
times = [
07:32:59,
17:32:04
]'
assert toml.encode[Arrs](a) == s
assert toml.decode[Arrs](s)! == a
any_a := AnyArr{[toml.Any(10), 20, 30]}
any_s := 'arr = [
10,
20,
30
]'
assert toml.encode[AnyArr](any_a) == any_s
assert toml.decode[AnyArr](any_s)!.arr.map(it.int()) == [10, 20, 30]
}
fn test_array_of_struct_decode() {
doc := Doc{
items: [
Item{
name: 'item #1'
},
Item{
name: 'item #2'
},
Item{
name: 'item #3'
},
]
}
encoded := toml.encode[Doc](doc)
assert toml.decode[Doc](encoded)! == doc
array_of_tables := '[[items]]
name = "item #1"
[[items]]
name = "item #2"
[[items]]
name = "item #3"'
assert toml.decode[Doc](array_of_tables)! == doc
}
fn test_map_of_struct_decode() {
doc := MapDoc{
items: {
'first': Item{
name: 'first one'
}
'second': Item{
name: 'second one'
}
}
}
encoded := toml.encode[MapDoc](doc)
assert toml.decode[MapDoc](encoded)! == doc
table_doc := '[items.first]
name = "first one"
[items.second]
name = "second one"'
assert toml.decode[MapDoc](table_doc)! == doc
}
fn test_decode_doc() {
doc := toml.parse_text('name = "Peter"
age = 28
is_human = true
salary = 100000.5
title = 2')!
e := doc.decode[Employee]()!
assert e.name == 'Peter'
assert e.age == 28
assert e.salary == 100000.5
assert e.is_human == true
assert e.title == .manager
}
fn test_unsupported_type() {
s := 'name = "Peter"'
err_msg := 'toml.decode: expected struct, found '
if _ := toml.decode[string](s) {
assert false
} else {
assert err.msg() == err_msg + 'string'
}
if _ := toml.decode[[]string](s) {
assert false
} else {
assert err.msg() == err_msg + '[]string'
}
if _ := toml.decode[int](s) {
assert false
} else {
assert err.msg() == err_msg + 'int'
}
if _ := toml.decode[[]f32](s) {
assert false
} else {
assert err.msg() == err_msg + '[]f32'
}
// ...
doc := toml.parse_text('name = "Peter"')!
assert doc.value('name').string() == 'Peter'
if _ := doc.decode[string]() {
assert false
} else {
assert err.msg() == 'Doc.decode: expected struct, found string'
}
}
fn test_date_array_decode_with_spaces() {
// test space after yyyy-mm-dd is not date-time separator but white to consume. issue # 25279
s := '
dates = [ 1979-05-27 , 2022-12-31 ]
'
a := Arrs{
dates: [toml.Date{'1979-05-27'}, toml.Date{'2022-12-31'}]
}
assert toml.decode[Arrs](s)! == a
}
@@ -0,0 +1,400 @@
import os
import toml
import toml.ast
import x.json2
const hide_oks = os.getenv('VTEST_HIDE_OK') == '1'
// Can be set to `true` to process tests that stress test the parser
// by having large data amounts - these pass - but slow down the test run
const do_large_files = os.getenv('VTEST_TOML_DO_LARGE_FILES') == '1'
// Can be set to `true` to process tests that triggers a slow conversion
// process that uses `python` to convert from YAML to JSON.
const do_yaml_conversion = os.getenv('VTEST_TOML_DO_YAML_CONVERSION') == '1'
// Instructions for developers:
// The actual tests and data can be obtained by doing:
// `git clone --depth 1 https://github.com/iarna/toml-spec-tests.git vlib/toml/tests/testdata/iarna/toml-test`
// See also the CI toml tests
// Kept for easier handling of future updates to the tests
const valid_exceptions = []string{}
const invalid_exceptions = []string{}
const valid_value_exceptions = [
'values/spec-date-time-3.toml',
'values/spec-date-time-4.toml',
'values/spec-readme-example.toml',
'values/spec-date-time-6.toml',
'values/spec-date-time-5.toml',
'values/spec-date-time-1.toml',
'values/spec-date-time-2.toml',
'values/qa-table-inline-nested-1000.toml',
'values/qa-array-inline-nested-1000.toml',
]
const yaml_value_exceptions = [
'values/spec-float-5.toml', // YAML: "1e6", V: 1000000
'values/spec-float-9.toml', // YAML: "-0e0", V: 0
'values/spec-float-6.toml', // YAML: "-2E-2", V: -0.02
'values/spec-float-4.toml', // YAML: "5e+22", V: 50000000000000004000000
]
const jq = os.find_abs_path_of_executable('jq') or { '' }
const python = os.find_abs_path_of_executable('python') or { '' }
const compare_work_dir_root = os.join_path(os.vtmp_dir(), 'toml_iarna')
// From: https://stackoverflow.com/a/38266731/1904615
const jq_normalize = r'# Apply f to composite entities recursively using keys[], and to atoms
def sorted_walk(f):
. as $in
| if type == "object" then
reduce keys[] as $key
( {}; . + { ($key): ($in[$key] | sorted_walk(f)) } ) | f
elif type == "array" then map( sorted_walk(f) ) | f
else f
end;
def normalize: sorted_walk(if type == "array" then sort else . end);
normalize'
fn run(args []string) !string {
res := os.execute(args.join(' '))
if res.exit_code != 0 {
return error('${args[0]} failed with return code ${res.exit_code}.\n${res.output}')
}
return res.output
}
// test_iarna_toml_spec_tests run though 'testdata/iarna/toml-test/*' if found.
fn test_iarna_toml_spec_tests() {
this_file := @FILE
test_root := os.join_path(os.dir(this_file), 'testdata', 'iarna')
if os.is_dir(test_root) {
valid_test_files := os.walk_ext(os.join_path(test_root, 'values'), '.toml')
println('Testing ${valid_test_files.len} valid TOML files...')
mut valid := 0
mut e := 0
for i, valid_test_file in valid_test_files {
mut relative := valid_test_file.all_after('iarna').trim_left(os.path_separator)
$if windows {
relative = relative.replace('/', '\\')
}
if !do_large_files && valid_test_file.contains('qa-') {
e++
println('SKIP [${i + 1}/${valid_test_files.len}] "${valid_test_file}" LARGE FILE...')
continue
}
if relative in valid_exceptions {
e++
idx := valid_exceptions.index(relative) + 1
println('SKIP [${i + 1}/${valid_test_files.len}] "${valid_test_file}" VALID EXCEPTION [${idx}/${valid_exceptions.len}]...')
continue
}
if !hide_oks {
println('OK [${i + 1}/${valid_test_files.len}] "${valid_test_file}"...')
}
toml_doc := toml.parse_file(valid_test_file)!
valid++
}
println('${valid}/${valid_test_files.len} TOML files were parsed correctly')
if valid_exceptions.len > 0 {
println('TODO Skipped parsing of ${e} valid TOML files...')
}
// If the command-line tool `jq` is installed, value tests can be run as well.
if jq != '' {
println('Testing value output of ${valid_test_files.len} valid TOML files using "${jq}"...')
if os.exists(compare_work_dir_root) {
os.rmdir_all(compare_work_dir_root)!
}
os.mkdir_all(compare_work_dir_root)!
jq_normalize_path := os.join_path(compare_work_dir_root, 'normalize.jq')
os.write_file(jq_normalize_path, jq_normalize)!
valid = 0
e = 0
for i, valid_test_file in valid_test_files {
mut relative := valid_test_file.all_after('iarna').trim_left(os.path_separator)
$if windows {
relative = relative.replace('/', '\\')
}
// Skip the file if we know it can't be parsed or we know that the value retrieval needs work.
if relative in valid_exceptions {
e++
idx := valid_exceptions.index(relative) + 1
println('SKIP [${i + 1}/${valid_test_files.len}] "${valid_test_file}" VALID EXCEPTION [${idx}/${valid_exceptions.len}]...')
continue
}
if relative in valid_value_exceptions {
e++
idx := valid_value_exceptions.index(relative) + 1
println('SKIP [${i + 1}/${valid_test_files.len}] "${valid_test_file}" VALID VALUE EXCEPTION [${idx}/${valid_value_exceptions.len}]...')
continue
}
valid_test_file_name := os.file_name(valid_test_file).all_before_last('.')
uses_json_format := os.exists(valid_test_file.all_before_last('.') + '.json')
// Use python to convert the YAML files to json - it yields some inconsistencies
// so we skip some of them
mut converted_from_yaml := false
mut converted_json_path := ''
if !uses_json_format {
$if windows {
println('N/A [${i + 1}/${valid_test_files.len}] "${valid_test_file}"...')
continue
}
if python == '' {
println('N/A [${i + 1}/${valid_test_files.len}] "${valid_test_file}"...')
continue
}
if !do_yaml_conversion || relative in yaml_value_exceptions {
e++
idx := yaml_value_exceptions.index(relative) + 1
println('SKIP [${i + 1}/${valid_test_files.len}] "${valid_test_file}" YAML VALUE EXCEPTION [${idx}/${valid_value_exceptions.len}]...')
continue
}
if !do_large_files && valid_test_file.contains('qa-') {
e++
println('SKIP [${i + 1}/${valid_test_files.len}] "${valid_test_file}" LARGE FILE...')
continue
}
iarna_yaml_path := valid_test_file.all_before_last('.') + '.yaml'
if os.exists(iarna_yaml_path) {
converted_json_path = os.join_path(compare_work_dir_root,
'${valid_test_file_name}.yaml.json')
run([python, '-c',
"'import sys, yaml, json; json.dump(yaml.load(sys.stdin, Loader=yaml.FullLoader), sys.stdout, indent=4)'",
'<', iarna_yaml_path, '>', converted_json_path]) or {
contents := os.read_file(iarna_yaml_path)!
// NOTE there's known errors with the python convention method.
// For now we just ignore them as it's a broken tool - not a wrong test-case.
// Uncomment this print to see/check them.
// eprintln(err.msg() + '\n${contents}')
e++
println('ERR [${i + 1}/${valid_test_files.len}] "${valid_test_file}" EXCEPTION [${e}/${valid_value_exceptions.len}]...')
continue
}
converted_from_yaml = true
}
}
if !hide_oks {
println('OK [${i + 1}/${valid_test_files.len}] "${valid_test_file}"...')
}
toml_doc := toml.parse_file(valid_test_file)!
v_toml_json_path := os.join_path(compare_work_dir_root,
'${valid_test_file_name}.v.json')
iarna_toml_json_path := os.join_path(compare_work_dir_root,
'${valid_test_file_name}.json')
os.write_file(v_toml_json_path, to_iarna(toml_doc.ast.table, converted_from_yaml))!
if converted_json_path == '' {
converted_json_path = valid_test_file.all_before_last('.') + '.json'
}
iarna_json := os.read_file(converted_json_path)!
os.write_file(iarna_toml_json_path, iarna_json)!
v_normalized_json := run([jq, '-S', '-f "${jq_normalize_path}"', v_toml_json_path]) or {
contents := os.read_file(v_toml_json_path)!
panic(err.msg() + '\n${contents}')
}
cmd := [jq, '-S', '-f "${jq_normalize_path}"', iarna_toml_json_path]
iarna_normalized_json := run(cmd) or {
contents := os.read_file(v_toml_json_path)!
panic(err.msg() + '\n${contents}\n\ncmd: ${cmd.join(' ')}')
}
assert iarna_normalized_json == v_normalized_json
valid++
}
println('${valid}/${valid_test_files.len} TOML files were parsed correctly and value checked')
if valid_value_exceptions.len > 0 {
println('TODO Skipped value checks of ${e} valid TOML files...')
}
}
invalid_test_files := os.walk_ext(os.join_path(test_root, 'errors'), '.toml')
println('Testing ${invalid_test_files.len} invalid TOML files...')
mut invalid := 0
e = 0
for i, invalid_test_file in invalid_test_files {
mut relative := invalid_test_file.all_after('iarna').trim_left(os.path_separator)
$if windows {
relative = relative.replace('/', '\\')
}
if relative in invalid_exceptions {
e++
idx := invalid_exceptions.index(relative) + 1
println('SKIP [${i + 1}/${invalid_test_files.len}] "${invalid_test_file}" INVALID EXCEPTION [${idx}/${invalid_exceptions.len}]...')
continue
}
if !hide_oks {
println('OK [${i + 1}/${invalid_test_files.len}] "${invalid_test_file}"...')
}
if toml_doc := toml.parse_file(invalid_test_file) {
content_that_should_have_failed := os.read_file(invalid_test_file)!
println(' This TOML should have failed:\n${'-'.repeat(40)}\n${content_that_should_have_failed}\n${'-'.repeat(40)}')
assert false
} else {
if !hide_oks {
println(' ${err.msg()}')
}
assert true
}
invalid++
}
println('${invalid}/${invalid_test_files.len} TOML files were parsed correctly')
if invalid_exceptions.len > 0 {
println('TODO Skipped parsing of ${invalid_exceptions.len} invalid TOML files...')
}
} else {
println('No test data directory found in "${test_root}"')
assert true
}
}
// to_iarna_time
fn to_iarna_time(time_str string) string {
if time_str.contains('.') {
date_and_time := time_str.all_before('.')
mut ms := time_str.all_after('.')
z := if ms.contains('Z') { 'Z' } else { '' }
ms = ms.replace('Z', '')
if ms.len > 3 {
ms = ms[..3]
}
return date_and_time + '.' + ms + z
} else {
return time_str + '.000'
}
}
// to_iarna returns a iarna compatible json string converted from the `value` ast.Value.
fn to_iarna(value ast.Value, skip_value_map bool) string {
match value {
ast.Quoted {
json_text := json2.Any(value.text).json_str()
if skip_value_map {
return json_text
}
return '{ "type": "string", "value": ${json_text} }'
}
ast.DateTime {
// Normalization for json
mut json_text := json2.Any(value.text).json_str().to_upper().replace(' ', 'T')
typ := if json_text.ends_with('Z"') || json_text.all_after('T').contains('-')
|| json_text.all_after('T').contains('+') {
'datetime'
} else {
'datetime-local'
}
// NOTE test suite inconsistency.
// It seems it's implementation specific how time and
// date-time values are represented in detail. For now we follow the toml-lang format
// that expands to 6 digits which is also a valid RFC 3339 representation.
json_text = to_iarna_time(json_text[1..json_text.len - 1])
if skip_value_map {
return json_text
}
return '{ "type": "${typ}", "value": "${json_text}" }'
}
ast.Date {
json_text := json2.Any(value.text).json_str()
if skip_value_map {
return json_text
}
return '{ "type": "date", "value": ${json_text} }'
}
ast.Time {
mut json_text := json2.Any(value.text).json_str()
// Note: Removes the quotes of the encoded JSON string - Ned
json_text = to_iarna_time(json_text[1..json_text.len - 1])
if skip_value_map {
return json_text
}
return '{ "type": "time", "value": "${json_text}" }'
}
ast.Bool {
json_text := json2.Any(value.text.bool()).json_str()
if skip_value_map {
return json_text
}
return '{ "type": "bool", "value": "${json_text}" }'
}
ast.Null {
json_text := json2.Any(value.text).json_str()
if skip_value_map {
return json_text
}
return '{ "type": "null", "value": ${json_text} }'
}
ast.Number {
if value.text.contains('inf') {
mut json_text :=
value.text.replace('inf', '1.7976931348623157e+308') // Inconsistency ???
if skip_value_map {
return '${json_text}'
}
return '{ "type": "float", "value": "${json_text}" }'
}
if value.text.contains('nan') {
mut json_text := 'null'
if skip_value_map {
return '${json_text}'
}
return '{ "type": "float", "value": "${json_text}" }'
}
if !value.text.starts_with('0x')
&& (value.text.contains('.') || value.text.to_lower().contains('e')) {
mut val := '${value.f64()}'.replace('.e+', '.0e') // json notation
if !val.contains('.') && val != '0' { // json notation
val += '.0'
}
if skip_value_map {
return '${val}'
}
return '{ "type": "float", "value": "${val}" }'
}
v := value.i64()
if skip_value_map {
return '${v}'
}
return '{ "type": "integer", "value": "${v}" }'
}
map[string]ast.Value {
mut str := '{ '
for key, val in value {
json_key := json2.Any(key).json_str()
str += ' ${json_key}: ${to_iarna(val, skip_value_map)},'
}
str = str.trim_right(',')
str += ' }'
return str
}
[]ast.Value {
mut str := '[ '
for val in value {
str += ' ${to_iarna(val, skip_value_map)},'
}
str = str.trim_right(',')
str += ' ]\n'
return str
}
}
return '<error>'
}
+16
View File
@@ -0,0 +1,16 @@
import toml
struct Address {
street string
city string
}
fn test_inline() {
a := Address{'Elm Street', 'Springwood'}
mut mp := map[string]toml.Any{}
mp['street'] = toml.Any(a.street)
mp['city'] = toml.Any(a.city)
assert mp.to_inline_toml() == '{ street = "Elm Street", city = "Springwood" }'
}
@@ -0,0 +1,15 @@
import os
import toml
import toml.to
const fprefix = os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))
fn test_parse() {
toml_doc := toml.parse_file(os.real_path(fprefix + '.toml'))!
toml_json := to.json(toml_doc)
println(toml_json)
out_file_json := os.read_file(os.real_path(fprefix + '.out'))!
assert toml_json == out_file_json
}
+18
View File
@@ -0,0 +1,18 @@
import os
import toml
import toml.to
fn test_parse() {
toml_file :=
os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) +
'.toml'
toml_doc := toml.parse_file(toml_file) or { panic(err) }
toml_json := to.json(toml_doc)
out_file :=
os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) +
'.out'
out_file_json := os.read_file(out_file) or { panic(err) }
println(toml_json)
assert toml_json == out_file_json
}
+48
View File
@@ -0,0 +1,48 @@
import os
import toml
import toml.to
fn path_by_extension(ext string) string {
return os.join_path(os.dir(@VEXE), 'vlib/toml/tests/testdata/key_test.${ext}')
}
fn test_keys() {
toml_doc := toml.parse_file(path_by_extension('toml'))!
mut value := toml_doc.value('34-11')
assert value.int() == 23
value = toml_doc.value('1.2')
assert value.int() == 3
value = toml_doc.value('34-12.2')
assert value.int() == 42
toml_json := to.json(toml_doc)
out_file_json := os.read_file(path_by_extension('out'))!
println(toml_json)
assert toml_json == out_file_json
if x := toml_doc.value_opt('unknown key') {
assert false
} else {
assert err.msg() == 'no value for key'
}
if x := toml_doc.value_opt("'a") {
assert false
} else {
assert err.msg() == 'invalid dotted key'
}
}
fn test_parse_dotted_key() {
assert toml.parse_dotted_key('')! == []
assert toml.parse_dotted_key('abc')! == ['abc']
assert toml.parse_dotted_key('tube.test."test.test".h."i.j."."k"')! == ['tube', 'test',
'test.test', 'h', 'i.j.', 'k']
if x := toml.parse_dotted_key("'some unclosed string") {
assert false
} else {
assert err.msg().starts_with('parse_dotted_key: could not parse key, missing closing string delimiter')
}
}
@@ -0,0 +1,20 @@
import os
import toml
// Instructions for developers:
// The large (1MB) TOML file can be obtained by doing:
// `wget https://gist.githubusercontent.com/Larpon/89b0e3d94c6903851ff15559e5df7a05/raw/62a1f87a4e37bf157f2e0bfb32d85d840c98e422/large_toml_file_test.toml`
// `mv large_toml_file_test.toml vlib/toml/tests/testdata/`
const toml_file = os.join_path_single(@VEXEROOT,
'vlib/toml/tests/testdata/large_toml_file_test.toml')
fn test_large_file() {
if !os.exists(toml_file) {
eprintln('skipping ${@FILE} since ${toml_file} is missing.')
return
}
println('Testing parsing of large (${os.file_size(toml_file)} bytes) "${toml_file}"...')
doc := toml.parse_file(toml_file) or { panic(err) }
assert true
}
+43
View File
@@ -0,0 +1,43 @@
import toml
const toml_text = '
[db]
enabled = true
[servers]
# Indentation (tabs and/or spaces) is allowed but not required
[servers.alpha]
ip = "10.0.0.1"
dc = "eqdc10"
[servers.beta]
ip = "10.0.0.2"
dc = "eqdc10"
[servers.alpha.tricky]
ip = "10.0.0.100"
[firewall.rules]
block = true
[firewall.rules.limit]
ip = "10.0.0.101"
'
fn test_parse() {
toml_doc := toml.parse_text(toml_text) or { panic(err) }
// dump(toml_doc.ast)
// assert false
assert toml_doc.value('db.enabled').bool()
// TODO: make this work
assert toml_doc.value('servers.alpha.ip').string() == '10.0.0.1'
assert toml_doc.value('servers.alpha.dc').string() == 'eqdc10'
assert toml_doc.value('servers.beta.ip').string() == '10.0.0.2'
assert toml_doc.value('servers.beta.dc').string() == 'eqdc10'
assert toml_doc.value('servers.alpha.tricky.ip').string() == '10.0.0.100'
assert toml_doc.value('firewall.rules.limit.ip').string() == '10.0.0.101'
assert toml_doc.value('firewall.rules.block').bool() == true
}
@@ -0,0 +1,13 @@
// vtest retry: 3
import toml
fn test_quoted_keys() {
str_value := 'V rocks!'
toml_txt := 'a."b.c" = "V rocks!"'
toml_doc := toml.parse_text(toml_txt) or { panic(err) }
value := toml_doc.value('a."b.c"')
assert value == toml.Any(str_value)
assert value as string == str_value
assert value.string() == str_value
}
@@ -0,0 +1,9 @@
import toml
fn test_quoted_string_crlf() {
toml_txt := 'str1 = """tcc \\\r\nabc \\\r\n123"""'
toml_doc := toml.parse_text(toml_txt) or { panic(err) }
value := toml_doc.value('str1').string()
assert value == 'tcc abc 123'
}
+108
View File
@@ -0,0 +1,108 @@
import toml
const toml_text = '# This TOML can reflect to a struct
name = "Tom"
age = 45
height = 1.97
birthday = 1980-04-23
strings = [
"v matures",
"like rings",
"spread in the",
"water"
]
bools = [true, false, true, true]
bools_br = [
true,
false,
true,
true
]
floats = [0.0, 1.0, 2.0, 3.0]
floats_br = [
0.0,
1.05,
2.025,
3.5001
]
int_map = {"a" = 0, "b" = 1, "c" = 2, "d" = 3}
[bio]
text = "Tom has done many great things"
years_of_service = 5
[field_remap]
txt = "I am remapped"
uint64 = 100
[config]
data = [ 1, 2, 3 ]
levels = { "info" = 1, "warn" = 2, "critical" = 3 }
'
struct FieldRemap {
text string @[toml: 'txt']
num u64 @[toml: 'uint64']
}
struct Bio {
text string
years_of_service int
}
struct User {
name string
age int
height f64
birthday toml.Date
strings []string
bools []bool
bools_br []bool
floats []f32
floats_br []f32
int_map map[string]int
config toml.Any
mut:
bio Bio
remap FieldRemap
}
fn test_reflect() {
toml_doc := toml.parse_text(toml_text) or { panic(err) }
mut user := toml_doc.reflect[User]()
user.bio = toml_doc.value('bio').reflect[Bio]()
user.remap = toml_doc.value('field_remap').reflect[FieldRemap]()
assert user.name == 'Tom'
assert user.age == 45
assert user.height == 1.97
assert user.birthday.str() == '1980-04-23'
assert user.strings == ['v matures', 'like rings', 'spread in the', 'water']
assert user.bools == [true, false, true, true]
assert user.bools_br == [true, false, true, true]
assert user.floats == [f32(0.0), 1.0, 2.0, 3.0]
assert user.floats_br == [f32(0.0), 1.05, 2.025, 3.5001]
assert user.int_map == {
'a': 0
'b': 1
'c': 2
'd': 3
}
assert user.bio.text == 'Tom has done many great things'
assert user.bio.years_of_service == 5
assert user.remap.text == 'I am remapped'
assert user.remap.num == 100
assert user.config.value('data[0]').int() == 1
assert user.config.value('levels.warn').int() == 2
}
@@ -0,0 +1,26 @@
import toml
fn test_spaced_keys() {
str_value := 'V rocks!'
toml_txt := '
"o" . pq . r = "Yuk"
[[ a . "b.c" ]]
d . e = "V rocks!"
[ tube . test . "test.test" ]
h . "i.j." . "k" = "Cryptic"
'
toml_doc := toml.parse_text(toml_txt) or { panic(err) }
mut value := toml_doc.value('a."b.c"[0].d.e')
assert value == toml.Any(str_value)
assert value as string == str_value
assert value.string() == str_value
value = toml_doc.value('"o".pq.r')
assert value.string() == 'Yuk'
value = toml_doc.value('tube.test."test.test".h."i.j."."k"')
assert value.string() == 'Cryptic'
}
+97
View File
@@ -0,0 +1,97 @@
import os
import toml
const toml_multiline_text_1 = 'multi1 = """one"""
multi2 = """one
two"""
multi3 = """
one
two
three"""
multi4 = """
one
two
three
four
"""'
const toml_multiline_text_2 = "multi1 = '''one'''
multi2 = '''one
two'''
multi3 = '''
one
two
three'''
multi4 = '''
one
two
three
four
'''"
const toml_unicode_escapes = r'short = "\u03B4"
long = "\U000003B4"'
fn test_multiline_strings() {
mut toml_doc := toml.parse_text(toml_multiline_text_1) or { panic(err) }
mut value := toml_doc.value('multi1')
assert value.string() == 'one'
value = toml_doc.value('multi2')
assert value.string() == 'one\ntwo'
value = toml_doc.value('multi3')
assert value.string() == 'one\ntwo\nthree'
value = toml_doc.value('multi4')
assert value.string() == 'one\ntwo\nthree\nfour\n'
toml_doc = toml.parse_text(toml_multiline_text_2) or { panic(err) }
value = toml_doc.value('multi1')
assert value.string() == 'one'
value = toml_doc.value('multi2')
assert value.string() == 'one\ntwo'
value = toml_doc.value('multi3')
assert value.string() == 'one\ntwo\nthree'
value = toml_doc.value('multi4')
assert value.string() == 'one\ntwo\nthree\nfour\n'
toml_file :=
os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) +
'.toml'
toml_doc = toml.parse_file(toml_file) or { panic(err) }
value = toml_doc.value('lit_one')
assert value.string() == "'one quote'"
value = toml_doc.value('lit_two')
assert value.string() == "''two quotes''"
value = toml_doc.value('mismatch1')
assert value.string() == 'aaa' + "'''" + 'bbb'
value = toml_doc.value('mismatch2')
assert value.string() == 'aaa' + '"""' + 'bbb'
}
fn test_unicode_escapes() {
mut toml_doc := toml.parse_text(toml_unicode_escapes) or { panic(err) }
mut value := toml_doc.value('short')
assert value.string() == '\u03B4' // <- This escape is handled by V
value = toml_doc.value('long')
assert value.string() == 'δ' // <- for the long escape we compare with the unicode point
}
fn test_literal_strings() {
toml_file :=
os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) +
'.toml'
toml_doc := toml.parse_file(toml_file) or { panic(err) }
assert toml_doc.value('lit1').string() == r'\' // '\'
assert toml_doc.value('lit2').string() == r'\\' // '\\'
assert toml_doc.value('lit3').string() == r'\tricky\' // '\tricky\'
// NOTE to Windows users: git is set to use Unix EOLs for all TOML files (*.toml) in the repo.
// See `.gitattributes` in the project root for the rule in action.
// These lines would look like this on Windows:
// assert toml_doc.value('ml_lit1').string() == '\r\n\\'
assert toml_doc.value('ml_lit1').string() == '\\'
assert toml_doc.value('ml_lit2').string() == '\\\n\\'
assert toml_doc.value('ml_lit3').string() == '\\\ntricky\\\n'
}
+89
View File
@@ -0,0 +1,89 @@
import toml
const toml_table_text = 'inline = {a.b = 42}
many.dots.here.dot.dot.dot = {a.b.c = 1, a.b.d = 2}
a = { a.b = 1 }
b = { "a"."b" = 1 }
c = { a . b = 1 }
d = { \'a\' . "b" = 1 }
e = {a.b=1}
[tbl]
a.b.c = {d.e=1}
[tbl.x]
a.b.c = {d.e=1}
[[arr]]
t = {a.b=1}
T = {a.b=1}
[[arr]]
t = {a.b=2}
T = {a.b=2}'
fn test_tables() {
mut toml_doc := toml.parse_text(toml_table_text) or { panic(err) }
mut value := toml_doc.value('inline.a.b')
assert value.int() == 42
value = toml_doc.value('many.dots.here.dot.dot.dot.a.b.c')
assert value.int() == 1
value = toml_doc.value('many.dots.here.dot.dot.dot.a.b.d')
assert value.int() == 2
value = toml_doc.value('a.a.b')
assert value.int() == 1
value = toml_doc.value('b.a.b')
assert value.int() == 1
value = toml_doc.value('c.a.b')
assert value.int() == 1
value = toml_doc.value('d.a.b')
assert value.int() == 1
value = toml_doc.value('e.a.b')
assert value.int() == 1
value = toml_doc.value('tbl.a.b.c.d.e')
assert value.int() == 1
value = toml_doc.value('tbl.x.a.b.c.d.e')
assert value.int() == 1
mut m := toml_doc.value('tbl') as map[string]toml.Any
value = m.value('a.b.c.d.e')
assert value.int() == 1
value = m.value('x.a.b.c.d.e')
assert value.int() == 1
arr := toml_doc.value('arr') as []toml.Any
for i := 0; i < arr.len; i++ {
entry := (arr[i] as map[string]toml.Any)
value = entry.value('t.a.b')
assert value.int() == i + 1
value = entry.value('T.a.b')
assert value.int() == i + 1
}
arr0 := arr[0] as map[string]toml.Any
value = arr0.value('t.a.b')
assert value.int() == 1
value = arr0.value('T.a.b')
assert value.int() == 1
arr1 := arr[1] as map[string]toml.Any
value = arr1.value('t.a.b')
assert value.int() == 2
value = arr1.value('T.a.b')
assert value.int() == 2
}
@@ -0,0 +1,4 @@
iarna/
toml_lang/
toml_rs/
large_toml_file_test.toml
@@ -0,0 +1 @@
{ "products": [ { "name": "Hammer", "sku": 738594937 }, { }, { "name": "Nail", "sku": 284758393, "color": "gray" } ] }
@@ -0,0 +1 @@
{ "albums": [ { "name": "Born to Run", "songs": [ { "name": "Jungleland" }, { "name": "Meeting Across the River" } ] }, { "name": "Born in the USA", "songs": [ { "name": "Glory Days" }, { "name": "Dancing in the Dark" } ] } ] }
@@ -0,0 +1 @@
{ "a": [ { "b": [ { "c": { "d": "val0" } }, { "c": { "d": "val1" } } ] } ] }
@@ -0,0 +1 @@
{ "Toml": [ { "ID": "1", "Index": 0, "Range": [ 0, 1, 2, 3 ], "Greeting": "Hello!", "Name": { "First": "Dolores", "Last": "Alvarado" }, "Friends": [ { "ID": 0, "Name": "Tom" }, { "ID": 1, "Name": "Dollie" } ] }, { "ID": "2", "Index": 1, "Range": [ 0, 1, 2, 3 ], "Greeting": "Hep!", "Name": { "First": "Rush", "Last": "Washington" }, "Friends": [ { "ID": 0, "Name": "Staci" }, { "ID": 1, "Name": "Dollie" } ] } ] }
@@ -0,0 +1,33 @@
[[Toml]]
ID = "1"
Index = 0
Range = [0, 1, 2, 3]
Greeting = "Hello!"
[Toml.Name]
First = "Dolores"
Last = "Alvarado"
[[Toml.Friends]]
ID = 0
Name = "Tom"
[[Toml.Friends]]
ID = 1
Name = "Dollie"
[[Toml]]
ID = "2"
Index = 1
Range = [0, 1, 2, 3]
Greeting = "Hep!"
[Toml.Name]
First = "Rush"
Last = "Washington"
[[Toml.Friends]]
ID = 0
Name = "Staci"
[[Toml.Friends]]
ID = 1
Name = "Dollie"
@@ -0,0 +1 @@
{ "albums": [ { "songs": [ { }, { } ] } ], "artists": [ { "home": { "address": { } } } ] }
@@ -0,0 +1,6 @@
[[ albums ]]
[[ albums.songs ]]
[[ albums.songs ]]
[[ artists ]]
[ artists.home.address ]
@@ -0,0 +1 @@
{ "ひらがな": "ひら" }
@@ -0,0 +1 @@
"ひらがな" = 'ひら'
+1
View File
@@ -0,0 +1 @@
{ "v": true, "animal": { "type": { "name": "pug" } }, "inline": { "a": 4, "b.c": 6, "b": { "c": 7 } }, "db": { "t": true }, "ij": { "a": { "i": 1, "j": 2 }, "b": { "i": "3", "j": "4" } }, "fruit": { "apple": { "color": "red", "taste": { "sweet": true }, "texture": { "smooth": true } } } }
+25
View File
@@ -0,0 +1,25 @@
v = true
animal = { type.name = "pug" }
inline = { "a" = 4, "b.c" = 6, b.c = 7 }
[db]
t = true
[ij]
[ij.a]
i = 1
j = 2
[ij.b]
i = "3"
j = "4"
[fruit]
apple.color = "red"
apple.taste.sweet = true
[fruit.apple.texture]
smooth = true
+1
View File
@@ -0,0 +1 @@
{ "34-11": 23, "1": { "2": 3 }, "34-12": { "2": 42 }, "34-20": { }, "5": { "6": { } }, "34-13": { "2": { } } }
+9
View File
@@ -0,0 +1,9 @@
34-11 = 23 # came out as "34-11 " = 23
1.2 = 3 # came out as "1.2" = 3
34-12.2 = 42 # came out as "34-12.2" = 42
[34-20]
[5.6]
[34-13.2]
+27
View File
@@ -0,0 +1,27 @@
# Make sure that quotes inside multiline strings are allowed, including right
# after the opening '''/""" and before the closing '''/"""
lit_one = ''''one quote''''
lit_two = '''''two quotes'''''
lit_one_space = ''' 'one quote' '''
lit_two_space = ''' ''two quotes'' '''
one = """"one quote""""
two = """""two quotes"""""
one_space = """ "one quote" """
two_space = """ ""two quotes"" """
mismatch1 = """aaa'''bbb"""
mismatch2 = '''aaa"""bbb'''
lit1 = '\'
lit2 = '\\'
lit3 = '\tricky\'
ml_lit1 = '''
\'''
ml_lit2 = '''\
\'''
ml_lit3 = '''\
tricky\
'''
+1
View File
@@ -0,0 +1 @@
{ "title": "TOML Example", "owner": { "name": "Tom Preston-Werner", "dob": "1979-05-27T07:32:00-08:00" }, "database": { "server": "192.168.1.1", "ports": [ 8000, 8001, 8002 ], "connection_max": 5000, "enabled": true }, "servers": { "alpha": { "ip": "10.0.0.1", "dc": "eqdc10" }, "beta": { "ip": "10.0.0.2", "dc": "eqdc10" } }, "clients": { "data": [ [ "gamma", "delta" ], [ 1, 2 ] ], "hosts": [ "alpha", "omega" ] } }
+33
View File
@@ -0,0 +1,33 @@
# This is a TOML document.
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00 # First class dates
[database]
server = "192.168.1.1"
ports = [ 8000, 8001, 8002 ]
connection_max = 5000
enabled = true
[servers]
# Indentation (tabs and/or spaces) is allowed but not required
[servers.alpha]
ip = "10.0.0.1"
dc = "eqdc10"
[servers.beta]
ip = "10.0.0.2"
dc = "eqdc10"
[clients]
data = [ ["gamma", "delta"], [1, 2] ]
# Line breaks are OK when inside arrays
hosts = [
"alpha",
"omega"
]
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,33 @@
# This is a TOML document with an UTF-8 BOM header.
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00 # First class dates
[database]
server = "192.168.1.1"
ports = [ 8000, 8001, 8002 ]
connection_max = 5000
enabled = true
[servers]
# Indentation (tabs and/or spaces) is allowed but not required
[servers.alpha]
ip = "10.0.0.1"
dc = "eqdc10"
[servers.beta]
ip = "10.0.0.2"
dc = "eqdc10"
[clients]
data = [ ["gamma", "delta"], [1, 2] ]
# Line breaks are OK when inside arrays
hosts = [
"alpha",
"omega"
]
+15
View File
@@ -0,0 +1,15 @@
import toml
struct TestStruct {
foo int
bar bool @[skip]
baz string = 'def' @[toml: barbaz]
}
fn test_toml_attr_encode() {
assert toml.encode(TestStruct{}) == 'foo = 0\nbarbaz = "def"'
}
fn test_toml_attr_decode() {
assert toml.decode[TestStruct]('foo = 0\nbarbaz = "def"')! == TestStruct{}
}
+51
View File
@@ -0,0 +1,51 @@
import os
import toml
import toml.to
import toml.ast
const empty_toml_document = toml.Doc{
ast: &ast.Root(unsafe { nil })
}
const toml_text_with_utf8_bom = os.read_file(os.real_path(os.join_path(os.dir(@FILE), 'testdata',
'toml_with_utf8_bom' + '.toml'))) or { panic(err) }
const toml_text_with_utf16_bom = os.read_file(os.real_path(os.join_path(os.dir(@FILE), 'testdata',
'toml_with_utf16_bom' + '.toml'))) or { panic(err) }
const toml_text_with_utf32_bom = os.read_file(os.real_path(os.join_path(os.dir(@FILE), 'testdata',
'toml_with_utf32_bom' + '.toml'))) or { panic(err) }
fn test_toml_with_bom() {
toml_doc := toml.parse_text(toml_text_with_utf8_bom) or { panic(err) }
toml_json := to.json(toml_doc)
title := toml_doc.value('title')
assert title == toml.Any('TOML Example')
assert title as string == 'TOML Example'
owner := toml_doc.value('owner') as map[string]toml.Any
any_name := owner.value('name')
assert any_name.string() == 'Tom Preston-Werner'
database := toml_doc.value('database') as map[string]toml.Any
db_serv := database['server'] or {
panic('could not access "server" index in "database" variable')
}
assert db_serv as string == '192.168.1.1'
// Re-cycle bad_toml_doc
mut bad_toml_doc := empty_toml_document
bad_toml_doc = toml.parse_text(toml_text_with_utf16_bom) or {
println(' ${err.msg()}')
assert true
empty_toml_document
}
bad_toml_doc = toml.parse_text(toml_text_with_utf32_bom) or {
println(' ${err.msg()}')
assert true
empty_toml_document
}
}
+305
View File
@@ -0,0 +1,305 @@
// Instructions for developers:
// The actual tests and data can be obtained by doing:
// `git clone -n https://github.com/toml-lang/toml-test.git vlib/toml/tests/testdata/toml_lang`
// `git -C vlib/toml/tests/testdata/toml_lang reset --hard 229ce2e
// See also the CI toml tests
import os
import toml
import toml.ast
import x.json2
const test_root = os.join_path(@VEXEROOT, 'vlib/toml/tests/testdata/toml_lang/tests')
const test_files_file = os.join_path(test_root, 'files-toml-1.0.0')
const hide_oks = os.getenv('VTEST_HIDE_OK') == '1'
const no_jq = os.getenv('VNO_JQ') == '1'
// Kept for easier lookup and handling of future updates to the tests.
// NOTE: entries in this list, except `do_not_remove`, are valid TOML that the parser should work with, but currently does not.
const valid_exceptions = [
'do_not_remove',
]
// NOTE: entries in this list, except `do_not_remove`, are tests of invalid TOML that should have the parser fail, but currently does not.
const invalid_exceptions = [
'do_not_remove',
]
const valid_value_exceptions = [
'do_not_remove',
]
const jq_not_equal = [
'do_not_remove',
]
const jq = os.find_abs_path_of_executable('jq') or { '' }
const compare_work_dir_root = os.join_path(os.vtmp_dir(), 'toml_toml_lang')
// From: https://stackoverflow.com/a/38266731/1904615
const jq_normalize = r'# Apply f to composite entities recursively using keys[], and to atoms
def sorted_walk(f):
. as $in
| if type == "object" then
reduce keys[] as $key
( {}; . + { ($key): ($in[$key] | sorted_walk(f)) } ) | f
elif type == "array" then map( sorted_walk(f) ) | f
else f
end;
def normalize: sorted_walk(if type == "array" then sort else . end);
normalize'
fn run(args []string) !string {
res := os.execute(args.join(' '))
if res.exit_code != 0 {
return error('${args[0]} failed with return code ${res.exit_code}.\n${res.output}')
}
return res.output
}
// test_toml_lang_tomltest run though 'testdata/toml_lang/toml-test/*' if found.
fn test_toml_lang_tomltest() {
eprintln('> running ${@LOCATION}')
if !os.is_dir(test_root) {
println('No test data directory found in "${test_root}"')
assert true
return
}
test_files_list := os.read_lines(test_files_file) or {
panic('Could not read "${test_files_file}" with test files list. It should reside in "${test_root}": ${err.msg()}')
}
valid_folder := 'valid'
valid_test_files :=
test_files_list.filter(it.starts_with('valid/') && it.ends_with('.toml')).map(it.replace('\\', '/'))
invalid_folder := 'invalid'
invalid_test_files :=
test_files_list.filter(it.starts_with('invalid/') && it.ends_with('.toml')).map(it.replace('\\', '/'))
assert valid_test_files.len > 0, 'Expected a list of *valid* test files'
assert invalid_test_files.len > 0, 'Expected a list of *invalid* test files'
assert os.is_file(os.join_path(test_root, valid_test_files[0])), 'Expected at least one file from the valid list to be present'
assert os.is_file(os.join_path(test_root, invalid_test_files[0])), 'Expected at least one file from the invalid list to present'
println('\nTesting ${valid_test_files.len} valid TOML files...')
mut valid := 0
mut e := 0
for i, relative_valid_test_file in valid_test_files {
relative := relative_valid_test_file.all_after(valid_folder).trim_left('/')
valid_test_file := os.join_path(test_root, relative_valid_test_file)
if relative in valid_exceptions {
e++
idx := valid_exceptions.index(relative) + 1
println('SKIP [${i + 1}/${valid_test_files.len}] "${valid_test_file}" VALID EXCEPTION [${idx}/${valid_exceptions.len}]...')
continue
}
// eprintln('>>> trying to parse: ${valid_test_file} | relative: ${relative}')
toml_doc := toml.parse_file(valid_test_file) or {
eprintln('>>> error while parsing: ${valid_test_file}')
panic(err)
}
valid++
if !hide_oks {
println('OK [${i + 1}/${valid_test_files.len}] "${valid_test_file}"...')
}
}
println('${valid}/${valid_test_files.len} TOML files were parsed correctly')
if valid_exceptions.len > 0 {
println('TODO Skipped parsing of ${valid_exceptions.len} valid TOML files...')
}
// If the command-line tool `jq` is installed, value tests can be run as well.
if jq != '' && !no_jq {
println('\nTesting value output of ${valid_test_files.len} valid TOML files using "${jq}"...')
if os.exists(compare_work_dir_root) {
os.rmdir_all(compare_work_dir_root)!
}
os.mkdir_all(compare_work_dir_root)!
jq_normalize_path := os.join_path(compare_work_dir_root, 'normalize.jq')
os.write_file(jq_normalize_path, jq_normalize)!
valid = 0
e = 0
for i, relative_valid_test_file in valid_test_files {
relative := relative_valid_test_file.all_after(valid_folder).trim_left('/')
valid_test_file := os.join_path(test_root, relative_valid_test_file)
// Skip the file if we know it can't be parsed or we know that the value retrieval needs work.
if relative in valid_exceptions {
e++
idx := valid_exceptions.index(relative) + 1
println('SKIP [${i + 1}/${valid_test_files.len}] "${valid_test_file}" VALID EXCEPTION [${idx}/${valid_exceptions.len}]...')
continue
}
if relative in valid_value_exceptions {
e++
idx := valid_value_exceptions.index(relative) + 1
println('SKIP [${i + 1}/${valid_test_files.len}] "${valid_test_file}" VALID VALUE EXCEPTION [${idx}/${valid_value_exceptions.len}]...')
continue
}
if !hide_oks {
println('OK [${i + 1}/${valid_test_files.len}] "${valid_test_file}"...')
}
toml_doc := toml.parse_file(valid_test_file)!
// eprintln(' relative: ${relative} parsed')
v_toml_json_path := os.join_path(compare_work_dir_root,
os.file_name(valid_test_file).all_before_last('.') + '.v.json')
bs_toml_json_path := os.join_path(compare_work_dir_root,
os.file_name(valid_test_file).all_before_last('.') + '.json')
os.write_file(v_toml_json_path, to_toml_lang(toml_doc.ast.table))!
bs_json := os.read_file(valid_test_file.all_before_last('.') + '.json')!
os.write_file(bs_toml_json_path, bs_json)!
v_normalized_json := run([jq, '-S', '-f "${jq_normalize_path}"', v_toml_json_path]) or {
contents := os.read_file(v_toml_json_path)!
panic(err.msg() + '\n${contents}')
}
bs_normalized_json := run([jq, '-S', '-f "${jq_normalize_path}"', bs_toml_json_path]) or {
contents := os.read_file(v_toml_json_path)!
panic(err.msg() + '\n${contents}')
}
if relative in jq_not_equal {
e++
eprintln('>>> skipped: relative: ${relative} in jq_not_equal, bs_normalized_json != bs_normalized_json')
continue
}
if bs_normalized_json != v_normalized_json {
e++
eprintln('>>> error: relative: ${relative}, bs_normalized_json != bs_normalized_json')
continue
}
assert bs_normalized_json == v_normalized_json
valid++
}
} else {
println('> Skipping json conversion tests, since jq: ${jq} | no_jq: ${no_jq}')
}
println('${valid}/${valid_test_files.len} TOML files were parsed correctly and value checked')
if valid_value_exceptions.len > 0 {
println('TODO Skipped value checks of ${valid_value_exceptions.len} valid TOML files...')
}
println('\nTesting ${invalid_test_files.len} invalid TOML files...')
mut invalid := 0
e = 0
for i, relative_invalid_test_file in invalid_test_files {
relative := relative_invalid_test_file.all_after(invalid_folder).trim_left('/')
invalid_test_file := os.join_path(test_root, relative_invalid_test_file)
if relative in invalid_exceptions {
e++
idx := invalid_exceptions.index(relative) + 1
println('SKIP [${i + 1}/${invalid_test_files.len}] "${invalid_test_file}" INVALID EXCEPTION [${idx}/${invalid_exceptions.len}]...')
continue
}
if !hide_oks {
println('OK [${i + 1}/${invalid_test_files.len}] "${invalid_test_file}"...')
}
if toml_doc := toml.parse_file(invalid_test_file) {
content_that_should_have_failed := os.read_file(invalid_test_file)!
println(' This TOML from relative: ${relative}, invalid_test_file: ${invalid_test_file}, should have failed:\n${'-'.repeat(100)}\n${content_that_should_have_failed}\n${'-'.repeat(100)}')
assert false
} else {
if !hide_oks {
println(' ${err.msg()}')
}
assert true
}
invalid++
}
println('${invalid}/${invalid_test_files.len} invalid TOML files correctly had parsing errors')
if invalid_exceptions.len > 0 {
println('TODO Skipped parsing of ${invalid_exceptions.len} invalid TOML files...')
}
}
// to_toml_lang returns a toml-lang compatible json string converted from the `value` ast.Value.
fn to_toml_lang(value ast.Value) string {
match value {
ast.Quoted {
json_text := json2.Any(value.text).json_str()
return '{ "type": "string", "value": ${json_text} }'
}
ast.DateTime {
// Normalization for json
json_text := json2.Any(value.text).json_str().to_upper().replace(' ', 'T')
// Note: Since encoding strings in JSON now automatically includes quotes,
// I added a somewhat a workaround by adding an ending quote in order to
// recognize properly the date time type. - Ned
typ := if json_text.ends_with('Z"') || json_text.all_after('T').contains('-')
|| json_text.all_after('T').contains('+') {
'datetime'
} else {
'datetime-local'
}
return '{ "type": "${typ}", "value": ${json_text} }'
}
ast.Date {
json_text := json2.Any(value.text).json_str()
return '{ "type": "date-local", "value": ${json_text} }'
}
ast.Time {
json_text := json2.Any(value.text).json_str()
return '{ "type": "time-local", "value": ${json_text} }'
}
ast.Bool {
json_text := json2.Any(value.text.bool()).json_str()
return '{ "type": "bool", "value": "${json_text}" }'
}
ast.Null {
json_text := json2.Any(value.text).json_str()
return '{ "type": "null", "value": ${json_text} }'
}
ast.Number {
if value.text.contains('inf') || value.text.contains('nan') {
return '{ "type": "float", "value": "${value.text}" }'
}
if !value.text.starts_with('0x')
&& (value.text.contains('.') || value.text.to_lower().contains('e')) {
mut val := '${value.f64()}'.replace('.e+', '.0e') // JSON notation
if !val.contains('.') && val != '0' { // JSON notation
val += '.0'
}
// Since https://github.com/vlang/v/pull/16079 V's string conversion of a zero (0) will
// output "0.0" for float types - the JSON test suite data, however, expects "0" for floats
// The following is a correction for that inconsistency
if val == '0.0' {
val = '0'
}
return '{ "type": "float", "value": "${val}" }'
}
return '{ "type": "integer", "value": "${value.i64()}" }'
}
map[string]ast.Value {
mut str := '{ '
for key, val in value {
json_key := json2.Any(key).json_str()
str += ' ${json_key}: ${to_toml_lang(val)},'
}
str = str.trim_right(',')
str += ' }'
return str
}
[]ast.Value {
mut str := '[ '
for val in value {
str += ' ${to_toml_lang(val)},'
}
str = str.trim_right(',')
str += ' ]\n'
return str
}
}
return '<error>'
}
@@ -0,0 +1,63 @@
// This tests the `toml` module for a known memory corruption.
// The BUG shows below if no string `.clone()` nor any garbage-collection is done...
import os
import toml
const toml_text = os.read_file(os.real_path(os.join_path(os.dir(@FILE), 'testdata', 'toml_test')) +
'.toml') or { panic(err) }
fn test_toml_known_memory_corruption() {
toml_doc := toml.parse_text(toml_text) or { panic(err) }
owner := toml_doc.value('owner') as map[string]toml.Any
any_name := owner.value('name')
// This assert code path will cause the corruption.
assert any_name.string() == 'Tom Preston-Werner'
// This code then triggered the bug before the fix.
// Also see note in toml/any.v in function `pub fn (a Any) string() string`
assert toml_doc.value('owner.name') as string == 'Tom Preston-Werner'
// Repeat the pattern
assert any_name.string() == 'Tom Preston-Werner'
assert toml_doc.value('owner.name') as string == 'Tom Preston-Werner'
}
fn test_toml_known_memory_corruption_2() {
toml_txt := '
# Local Date-Time
ldt1 = 1979-05-27T07:32:00
ldt2 = 1979-05-27T00:32:00.999999
# Local Date
ld1 = 1979-05-27
# Local Time
lt1 = 07:32:00
lt2 = 00:32:00.999999
'
toml_doc := toml.parse_text(toml_txt) or { panic(err) }
// ldt1 test section
odt_time := toml.DateTime{'1979-05-27T07:32:00'}
ldt1 := toml_doc.value('ldt1')
assert ldt1.string() == '1979-05-27T07:32:00' // Running this before yielded a double free segfault
assert ldt1.datetime() == odt_time
assert ldt1.string() == '1979-05-27T07:32:00' // Used to yield a memory corruption
// ld1 test section
od_time := toml.Date{'1979-05-27'}
ld1 := toml_doc.value('ld1')
assert ld1.date() == od_time
assert ld1.string() == '1979-05-27' // Used to yield a memory corruption
// lt1 test section
mut ot_time := toml.Time{'07:32:00'}
lt1 := toml_doc.value('lt1')
assert lt1.time() == ot_time
assert lt1.string() == '07:32:00' // Used to yield a memory corruption
// lt2 test section
ot_time = toml.Time{'00:32:00.999999'}
lt2 := toml_doc.value('lt2')
assert lt2.time() == ot_time
assert lt2.string() == '00:32:00.999999' // Used to yield a memory corruption
}
+321
View File
@@ -0,0 +1,321 @@
import os
import toml
import toml.ast
import x.json2
const hide_oks = os.getenv('VTEST_HIDE_OK') == '1'
const no_jq = os.getenv('VNO_JQ') == '1'
// Instructions for developers:
// The actual tests and data can be obtained by doing:
// `git clone -n https://github.com/toml-rs/toml.git vlib/toml/tests/testdata/toml_rs`
// `git -C vlib/toml/tests/testdata/toml_rs reset --hard 9bd454c`
// See also the CI toml tests
// Kept for easier handling of future updates to the tests
const valid_exceptions = [
'valid/example-v0.3.0.toml',
'valid/example-v0.4.0.toml',
'valid/datetime-truncate.toml', // Not considered valid since RFC 3339 doesn't permit > 6 ms digits ??
]
const invalid_exceptions = []string{}
const valid_value_exceptions = [
// These have correct values, and should've passed, but the format of arrays is *mixed* in the JSON ??
'valid/datetime-truncate.toml',
'valid/example2.toml',
'valid/example-v0.4.0.toml',
'valid/example-v0.3.0.toml',
]
// These have correct values, and should've passed as-is, but the format of arrays changes in the JSON ??
// We account for that here
const use_type_2_arrays = [
'valid/table-array-implicit.toml',
'valid/table-array-many.toml',
'valid/table-array-one.toml',
'valid/table-array-nest.toml',
'valid/table-array-nest-no-keys.toml',
]
const tests_folder = os.join_path('crates', 'test-suite', 'tests')
const jq = os.find_abs_path_of_executable('jq') or { '' }
const compare_work_dir_root = os.join_path(os.vtmp_dir(), 'toml_toml_rs')
// From: https://stackoverflow.com/a/38266731/1904615
const jq_normalize = r'# Apply f to composite entities recursively using keys[], and to atoms
def sorted_walk(f):
. as $in
| if type == "object" then
reduce keys[] as $key
( {}; . + { ($key): ($in[$key] | sorted_walk(f)) } ) | f
elif type == "array" then map( sorted_walk(f) ) | f
else f
end;
def normalize: sorted_walk(if type == "array" then sort else . end);
normalize'
fn run(args []string) !string {
res := os.execute(args.join(' '))
if res.exit_code != 0 {
return error('${args[0]} failed with return code ${res.exit_code}.\n${res.output}')
}
return res.output
}
// test_toml_rs_toml_rs run though 'testdata/toml_rs/crates/test-suite/tests/*' if found.
fn test_toml_rs_toml_rs() {
eprintln('> running ${@LOCATION}')
test_root := '${@VEXEROOT}/vlib/toml/tests/testdata/toml_rs/crates/test-suite/tests'
if os.is_dir(test_root) {
valid_test_files := os.walk_ext(os.join_path(test_root, 'valid'), '.toml')
invalid_test_files := os.walk_ext(os.join_path(test_root, 'invalid'), '.toml')
println('Testing ${valid_test_files.len} valid TOML files...')
mut valid := 0
mut e := 0
for i, valid_test_file in valid_test_files {
mut relative := valid_test_file.all_after(tests_folder).trim_left(os.path_separator)
$if windows {
relative = relative.replace('/', '\\')
}
if relative in valid_exceptions {
e++
idx := valid_exceptions.index(relative) + 1
println('SKIP [${i + 1}/${valid_test_files.len}] "${valid_test_file}" VALID EXCEPTION [${idx}/${valid_exceptions.len}]...')
continue
}
if !hide_oks {
println('OK [${i + 1}/${valid_test_files.len}] "${valid_test_file}"...')
}
toml_doc := toml.parse_file(valid_test_file)!
valid++
}
println('${valid}/${valid_test_files.len} TOML files were parsed correctly')
if valid_exceptions.len > 0 {
println('TODO Skipped parsing of ${e} valid TOML files...')
}
// If the command-line tool `jq` is installed, value tests can be run as well.
if jq != '' && !no_jq {
println('Testing value output of ${valid_test_files.len} valid TOML files using "${jq}"...')
if os.exists(compare_work_dir_root) {
os.rmdir_all(compare_work_dir_root)!
}
os.mkdir_all(compare_work_dir_root)!
jq_normalize_path := os.join_path(compare_work_dir_root, 'normalize.jq')
os.write_file(jq_normalize_path, jq_normalize)!
valid = 0
e = 0
for i, valid_test_file in valid_test_files {
mut relative := valid_test_file.all_after(tests_folder).trim_left(os.path_separator)
$if windows {
relative = relative.replace('/', '\\')
}
if !os.exists(valid_test_file.all_before_last('.') + '.json') {
println('N/A [${i + 1}/${valid_test_files.len}] "${valid_test_file}"...')
continue
}
// Skip the file if we know it can't be parsed or we know that the value retrieval needs work.
if relative in valid_exceptions {
e++
idx := valid_exceptions.index(relative) + 1
println('SKIP [${i + 1}/${valid_test_files.len}] "${valid_test_file}" VALID EXCEPTION [${idx}/${valid_exceptions.len}]...')
continue
}
if relative in valid_value_exceptions {
e++
idx := valid_value_exceptions.index(relative) + 1
println('SKIP [${i + 1}/${valid_test_files.len}] "${valid_test_file}" VALID VALUE EXCEPTION [${idx}/${valid_value_exceptions.len}]...')
continue
}
if !hide_oks {
println('OK [${i + 1}/${valid_test_files.len}] "${valid_test_file}"...')
}
toml_doc := toml.parse_file(valid_test_file)!
v_toml_json_path := os.join_path(compare_work_dir_root,
os.file_name(valid_test_file).all_before_last('.') + '.v.json')
toml_rs_toml_json_path := os.join_path(compare_work_dir_root,
os.file_name(valid_test_file).all_before_last('.') + '.json')
mut array_type := 1
if relative in use_type_2_arrays {
array_type = 2
}
os.write_file(v_toml_json_path, to_toml_rs(toml_doc.ast.table, array_type))!
toml_rs_json := os.read_file(valid_test_file.all_before_last('.') + '.json')!
os.write_file(toml_rs_toml_json_path, toml_rs_json)!
v_normalized_json := run([jq, '-S', '-f "${jq_normalize_path}"', v_toml_json_path]) or {
contents := os.read_file(v_toml_json_path)!
panic(err.msg() + '\n${contents}')
}
toml_rs_normalized_json := run([jq, '-S', '-f "${jq_normalize_path}"',
toml_rs_toml_json_path]) or {
contents := os.read_file(v_toml_json_path)!
panic(err.msg() + '\n${contents}')
}
assert toml_rs_normalized_json == v_normalized_json
valid++
}
println('${valid}/${valid_test_files.len} TOML files were parsed correctly and value checked')
if valid_value_exceptions.len > 0 {
println('TODO Skipped value checks of ${e} valid TOML files...')
}
}
println('Testing ${invalid_test_files.len} invalid TOML files...')
mut invalid := 0
e = 0
for i, invalid_test_file in invalid_test_files {
mut relative := invalid_test_file.all_after(tests_folder).trim_left(os.path_separator)
$if windows {
relative = relative.replace('/', '\\')
}
if relative in invalid_exceptions {
e++
idx := invalid_exceptions.index(relative) + 1
println('SKIP [${i + 1}/${invalid_test_files.len}] "${invalid_test_file}" INVALID EXCEPTION [${idx}/${invalid_exceptions.len}]...')
continue
}
if !hide_oks {
println('OK [${i + 1}/${invalid_test_files.len}] "${invalid_test_file}"...')
}
if toml_doc := toml.parse_file(invalid_test_file) {
content_that_should_have_failed := os.read_file(invalid_test_file)!
println(' This TOML should have failed:\n${'-'.repeat(40)}\n${content_that_should_have_failed}\n${'-'.repeat(40)}')
assert false
} else {
if !hide_oks {
println(' ${err.msg()}')
}
assert true
}
invalid++
}
println('${invalid}/${invalid_test_files.len} TOML files were parsed correctly')
if invalid_exceptions.len > 0 {
println('TODO Skipped parsing of ${invalid_exceptions.len} invalid TOML files...')
}
} else {
println('No test data directory found in "${test_root}"')
assert true
}
}
// to_toml_rs_time
fn to_toml_rs_time(time_str string) string {
if time_str.contains('.') {
date_and_time := time_str.all_before('.')
mut ms := time_str.all_after('.')
z := if ms.contains('Z') { 'Z' } else { '' }
ms = ms.replace('Z', '')
if ms.len > 3 {
ms = ms[..3]
}
return date_and_time + '.' + ms + z
} else {
return time_str
}
}
// to_toml_rs returns an toml_rs compatible json string converted from the `value` ast.Value.
fn to_toml_rs(value ast.Value, array_type int) string {
match value {
ast.Quoted {
json_text := json2.Any(value.text).json_str()
return '{ "type": "string", "value": ${json_text} }'
}
ast.DateTime {
// Normalization for json
mut json_text := json2.Any(value.text).json_str().to_upper().replace(' ', 'T')
typ := if json_text.ends_with('Z"') || json_text.all_after('T').contains('-')
|| json_text.all_after('T').contains('+') {
'datetime'
} else {
'datetime-local'
}
// NOTE test suite inconsistency.
// It seems it's implementation specific how time and
// date-time values are represented in detail. For now we follow the toml-lang format
// that expands to 6 digits which is also a valid RFC 3339 representation.
json_text = to_toml_rs_time(json_text[1..json_text.len - 1])
return '{ "type": "${typ}", "value": "${json_text}" }'
}
ast.Date {
json_text := json2.Any(value.text).json_str()
return '{ "type": "date", "value": ${json_text} }'
}
ast.Time {
mut json_text := json2.Any(value.text).json_str()
json_text = to_toml_rs_time(json_text[1..json_text.len - 1])
return '{ "type": "time", "value": "${json_text}" }'
}
ast.Bool {
json_text := json2.Any(value.text.bool()).json_str()
return '{ "type": "bool", "value": "${json_text}" }'
}
ast.Null {
json_text := json2.Any(value.text).json_str()
return '{ "type": "null", "value": ${json_text} }'
}
ast.Number {
text := value.text
if text.contains('inf') || text.contains('nan') {
return '{ "type": "float", "value": ${value.text} }'
}
if !text.starts_with('0x') && (text.contains('.') || text.to_lower().contains('e')) {
mut val := ''
if text.to_lower().contains('e') && !text.contains('-') {
val = '${value.f64():.1f}'
} else {
val = '${value.f64()}'
}
return '{ "type": "float", "value": "${val}" }'
}
return '{ "type": "integer", "value": "${value.i64()}" }'
}
map[string]ast.Value {
mut str := '{ '
for key, val in value {
json_key := json2.Any(key).json_str()
str += ' ${json_key}: ${to_toml_rs(val, array_type)},'
}
str = str.trim_right(',')
str += ' }'
return str
}
[]ast.Value {
mut str := ''
if array_type == 1 {
str = '{ "type": "array", "value": [ '
} else {
str = '[ '
}
for val in value {
str += ' ${to_toml_rs(val, array_type)},'
}
str = str.trim_right(',')
if array_type == 1 {
str += ' ] }\n'
} else {
str += ' ]\n'
}
return str
}
}
return '<error>'
}
+106
View File
@@ -0,0 +1,106 @@
import os
import toml
import toml.to
const toml_text = os.read_file(
os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) +
'.toml') or { panic(err) }
fn test_toml() {
// File containing the complete text from the example in the official TOML project README.md:
// https://github.com/toml-lang/toml/blob/3b11f6921da7b6f5db37af039aa021fee450c091/README.md#Example
toml_doc := toml.parse_text(toml_text) or { panic(err) }
toml_json := to.json(toml_doc)
// NOTE Kept for easier debugging:
// dump(toml_doc.ast)
// println(toml_json)
// assert false
assert toml_json == os.read_file(
os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) +
'.out') or { panic(err) }
title := toml_doc.value('title')
assert title == toml.Any('TOML Example')
assert title as string == 'TOML Example'
database := toml_doc.value('database') as map[string]toml.Any
db_serv := database['server'] or {
panic('could not access "server" index in "database" variable')
}
assert db_serv as string == '192.168.1.1'
assert toml_doc.value('owner.name') as string == 'Tom Preston-Werner'
assert toml_doc.value('database.server') as string == '192.168.1.1'
database_ports := toml_doc.value('database.ports') as []toml.Any
assert database_ports[0] as i64 == 8000
assert database_ports[1] as i64 == 8001
assert database_ports[2] as i64 == 8002
assert database_ports[0].int() == 8000
assert database_ports[1].int() == 8001
assert database_ports[2].int() == 8002
assert toml_doc.value('database.connection_max') as i64 == 5000
assert toml_doc.value('database.enabled') as bool == true
assert toml_doc.value('servers.alpha.ip').string() == '10.0.0.1'
assert toml_doc.value('servers.alpha.dc').string() == 'eqdc10'
assert toml_doc.value('servers.beta.ip').string() == '10.0.0.2'
assert toml_doc.value('servers.beta.dc').string() == 'eqdc10'
clients_data := (toml_doc.value('clients.data') as []toml.Any)
// dump(clients_data)
// assert false
gamma_delta_array := clients_data[0] as []toml.Any
digits_array := clients_data[1] as []toml.Any
assert gamma_delta_array[0].string() == 'gamma'
assert gamma_delta_array[1].string() == 'delta'
assert digits_array[0].int() == 1
assert digits_array[1].int() == 2
clients_hosts := (toml_doc.value('clients.hosts') as []toml.Any).as_strings()
assert clients_hosts[0] == 'alpha'
assert clients_hosts[1] == 'omega'
}
fn test_toml_file() {
out_path := os.join_path(os.vtmp_dir(), 'toml_tests')
test_file := os.join_path(out_path, 'toml_example.toml')
os.mkdir_all(out_path) or { assert false }
defer {
os.rmdir_all(out_path) or {}
}
os.write_file(test_file, toml_text) or { assert false }
toml_doc := toml.parse_file(test_file) or { panic(err) }
toml_json := to.json(toml_doc)
// NOTE Kept for easier debugging:
// dump(toml_doc.ast)
// println(toml_json)
// assert false
assert toml_json == os.read_file(
os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) +
'.out') or { panic(err) }
}
fn test_toml_parse_text() {
toml_doc := toml.parse_text(toml_text) or { panic(err) }
toml_json := to.json(toml_doc)
assert toml_json == os.read_file(
os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) +
'.out') or { panic(err) }
}
fn test_toml_parse() {
toml_doc := toml.parse_text(toml_text) or { panic(err) }
toml_json := to.json(toml_doc)
assert toml_json == os.read_file(
os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) +
'.out') or { panic(err) }
}
+123
View File
@@ -0,0 +1,123 @@
import toml
import strconv
fn test_string() {
str_value := 'test string'
toml_txt := 'string = "test string"'
toml_doc := toml.parse_text(toml_txt) or { panic(err) }
value := toml_doc.value('string')
assert value == toml.Any(str_value)
assert value as string == str_value
assert value.string() == str_value
}
fn test_i64() {
toml_txt := 'i64 = 120'
toml_doc := toml.parse_text(toml_txt) or { panic(err) }
value := toml_doc.value('i64')
assert value == toml.Any(i64(120))
assert value as i64 == 120
assert value.i64() == i64(120)
}
fn test_bool() {
toml_txt := '
bool_true = true
bool_false = false'
toml_doc := toml.parse_text(toml_txt) or { panic(err) }
value_true := toml_doc.value('bool_true')
assert value_true == toml.Any(true)
assert value_true as bool == true
assert value_true != toml.Any(false)
assert value_true as bool != false
assert value_true.bool() == true
value_false := toml_doc.value('bool_false')
assert value_false == toml.Any(false)
assert value_false as bool == false
assert value_false != toml.Any(true)
assert value_false as bool != true
assert value_false.bool() == false
}
fn test_bool_key_is_not_value() {
toml_txt := 'true = true
false = false'
toml_doc := toml.parse_text(toml_txt) or { panic(err) }
value_true := toml_doc.value('true')
assert value_true == toml.Any(true)
assert value_true as bool == true
assert value_true != toml.Any(false)
assert value_true as bool != false
value_false := toml_doc.value('false')
assert value_false == toml.Any(false)
assert value_false as bool == false
assert value_false != toml.Any(true)
assert value_false as bool != true
}
fn test_single_letter_key() {
toml_txt := '[v]
open_sourced = "Jun 22 2019 20:20:28"'
toml_doc := toml.parse_text(toml_txt) or { panic(err) }
value := toml_doc.value('v.open_sourced').string()
assert value == 'Jun 22 2019 20:20:28'
}
fn test_hex_values() {
// Regression test
// '0xb' is carefully chosen to include the 'b' character that also denotes binary via 0b prefix.
toml_txt := 'hex = 0xb'
toml_doc := toml.parse_text(toml_txt) or { panic(err) }
value := toml_doc.value('hex')
assert value as i64 == 11
assert value.i64() == 11
}
fn test_comment_as_last_value() {
toml_txt := '
test = 42
# this line has comment as last thing'
toml_doc := toml.parse_text(toml_txt) or { panic(err) }
value := toml_doc.value('test')
assert value as i64 == 42
assert value.i64() == 42
}
fn test_nan_and_inf_values() {
mut toml_doc := toml.parse_text('nan = nan') or { panic(err) }
mut value := toml_doc.value('nan')
assert value.string() == 'nan'
toml_doc = toml.parse_text('nan = nan#comment') or { panic(err) }
value = toml_doc.value('nan')
assert value.string() == 'nan'
toml_doc = toml.parse_text('nan = -nan') or { panic(err) }
value = toml_doc.value('nan')
assert value.string() == 'nan'
toml_doc = toml.parse_text('nan = +nan') or { panic(err) }
value = toml_doc.value('nan')
assert value.string() == 'nan'
toml_doc = toml.parse_text('inf = inf') or { panic(err) }
value = toml_doc.value('inf')
assert value.u64() == strconv.double_plus_infinity
toml_doc = toml.parse_text('inf = +inf') or { panic(err) }
value = toml_doc.value('inf')
assert value.u64() == strconv.double_plus_infinity
toml_doc = toml.parse_text('inf = -inf') or { panic(err) }
value = toml_doc.value('inf')
assert value.u64() == strconv.double_minus_infinity
}
+131
View File
@@ -0,0 +1,131 @@
import toml
import strconv
const toml_text = '
modules = [ "ui", "toml" ]
errors = []
[[themes]]
name = "Dracula"
colors = [ "red", "black", "white" ]
[[themes]]
name = "Lemon"
colors = [
"green",
"yellow",
[ "transparent" ]
]
[[tests]]
id = 1
[[tests]]
id = 2
[values]
nan = nan
inf = inf
test = 2
minus-inf = -inf
[[themes]]
name = "Ice"
colors = [
"blue",
"white"
]
'
const toml_text_2 = "
[defaults]
run.flags = ['-f 1']
[[defaults.env]]
'RUN_PATH' = '\$OUT_PATH'
'RUN_TIME' = 5
'TEST_PATH' = '/tmp/test'
"
const toml_text_3 = '
[[foo.bar]]
baz = 1
bzz = 1
[[foo.bar]]
baz = 2
bzz = 2
'
fn test_value_query_in_array() {
toml_doc := toml.parse_text(toml_text) or { panic(err) }
mut value := toml_doc.value('themes[0].colors[1]').string()
assert value == 'black'
value = toml_doc.value('themes[1].colors[0]').string()
assert value == 'green'
value = toml_doc.value('themes[1].colors[2].[0]').string()
assert value == 'transparent'
value = toml_doc.value('modules[1]').string()
assert value == 'toml'
value = toml_doc.value('errors[11]').default_to('<none>').string()
assert value == '<none>'
value = toml_doc.value('themes[2].colors[0]').string()
assert value == 'blue'
}
fn test_any_value_query() {
toml_doc := toml.parse_text(toml_text) or { panic(err) }
themes := toml_doc.value('themes')
assert themes.value('[0].colors[0]').string() == 'red'
themes_arr := toml_doc.value('themes') as []toml.Any
assert themes_arr[0].value('colors[0]').string() == 'red'
mut any := themes
assert any.value('[1].name').string() == 'Lemon'
any = any.value('[1]')
assert any.value('name').string() == 'Lemon'
any = toml_doc.value('themes').value('[1].colors').value('[1]')
assert any.string() == 'yellow'
any = toml_doc.value('themes[1]').value('colors[1]')
assert any.string() == 'yellow'
any = toml_doc.value('themes[1].colors[0]')
assert any.string() == 'green'
any = toml_doc.value('values')
any = any.value('test')
assert any.int() == 2
}
fn test_inf_and_nan_query() {
toml_doc := toml.parse_text(toml_text) or { panic(err) }
value := toml_doc.value('values.nan').string()
assert value == 'nan'
mut value_u64 := toml_doc.value('values.inf').u64()
assert value_u64 == strconv.double_plus_infinity
value_u64 = toml_doc.value('values.minus-inf').u64()
assert value_u64 == strconv.double_minus_infinity
}
fn test_any_value_query_2() {
toml_doc := toml.parse_text(toml_text_2) or { panic(err) }
defaults := toml_doc.value('defaults')
assert defaults.value('run.flags[0]').string() == '-f 1'
assert defaults.value('env[0].RUN_TIME').int() == 5
}
fn test_any_value_query_for_nested_tables_array() {
toml_doc := toml.parse_text(toml_text_3) or { panic(err) }
items := toml_doc.value('foo.bar').array()
assert items.len == 2
assert items[0].value('baz').int() == 1
assert items[0].value('bzz').int() == 1
assert items[1].value('baz').int() == 2
assert items[1].value('bzz').int() == 2
}