Initial project import
This commit is contained in:
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
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
@@ -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>'
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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 @@
|
||||
"ひらがな" = 'ひら'
|
||||
@@ -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
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
{ "34-11": 23, "1": { "2": 3 }, "34-12": { "2": 42 }, "34-20": { }, "5": { "6": { } }, "34-13": { "2": { } } }
|
||||
@@ -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]
|
||||
@@ -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\
|
||||
'''
|
||||
@@ -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
@@ -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"
|
||||
]
|
||||
@@ -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{}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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>'
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user