├── .gitignore ├── tests ├── valid │ ├── empty.toml │ ├── empty.json │ ├── table-empty.toml │ ├── key-space.toml │ ├── bool.toml │ ├── string-empty.toml │ ├── array-empty.toml │ ├── array-nospaces.toml │ ├── key-equals-nospace.toml │ ├── table-sub-empty.toml │ ├── table-whitespace.toml │ ├── unicode-literal.toml │ ├── arrays-nested.toml │ ├── float.toml │ ├── table-empty.json │ ├── implicit-groups.toml │ ├── integer.toml │ ├── datetime.toml │ ├── table-with-pound.toml │ ├── table-sub-empty.json │ ├── table-whitespace.json │ ├── arrays-hetergeneous.toml │ ├── key-special-chars.toml │ ├── string-simple.toml │ ├── table-array-implicit.toml │ ├── unicode-escape.toml │ ├── key-space.json │ ├── long-float.toml │ ├── unicode-literal.json │ ├── implicit-and-explicit-after.toml │ ├── implicit-and-explicit-before.toml │ ├── key-equals-nospace.json │ ├── long-integer.toml │ ├── table-array-one.toml │ ├── datetime.json │ ├── string-empty.json │ ├── bool.json │ ├── example.toml │ ├── float.json │ ├── table-with-pound.json │ ├── integer.json │ ├── key-special-chars.json │ ├── string-with-pound.toml │ ├── unicode-escape.json │ ├── string-simple.json │ ├── long-float.json │ ├── table-array-implicit.json │ ├── long-integer.json │ ├── arrays.toml │ ├── implicit-groups.json │ ├── table-array-one.json │ ├── string-with-pound.json │ ├── table-array-many.toml │ ├── raw-multiline-string.toml │ ├── implicit-and-explicit-after.json │ ├── implicit-and-explicit-before.json │ ├── array-nospaces.json │ ├── array-empty.json │ ├── comments-everywhere.json │ ├── table-array-nest.toml │ ├── arrays-nested.json │ ├── raw-string.toml │ ├── example.json │ ├── raw-multiline-string.json │ ├── multiline-string.toml │ ├── table-array-many.json │ ├── comments-everywhere.toml │ ├── arrays-hetergeneous.json │ ├── table-array-nest.json │ ├── string-escapes.toml │ ├── multiline-string.json │ ├── raw-string.json │ ├── arrays.json │ └── string-escapes.json ├── invalid │ ├── empty-table.toml │ ├── key-empty.toml │ ├── key-hash.toml │ ├── key-space.toml │ ├── table-empty.toml │ ├── key-single-open-bracket.toml │ ├── key-newline.toml │ ├── key-open-bracket.toml │ ├── key-two-equals.toml │ ├── table-whitespace.toml │ ├── duplicate-tables.toml │ ├── string-byte-escapes.toml │ ├── empty-implicit-table.toml │ ├── string-bad-byte-escape.toml │ ├── table-with-pound.toml │ ├── duplicate-keys.toml │ ├── key-start-bracket.toml │ ├── table-nested-brackets-open.toml │ ├── datetime-malformed-no-t.toml │ ├── table-nested-brackets-close.toml │ ├── text-after-table.toml │ ├── datetime-malformed-no-secs.toml │ ├── text-after-integer.toml │ ├── array-mixed-types-ints-and-floats.toml │ ├── datetime-malformed-no-leads.toml │ ├── float-no-trailing-digits.toml │ ├── string-no-close.toml │ ├── table-array-malformed-empty.toml │ ├── array-mixed-types-strings-and-ints.toml │ ├── datetime-malformed-with-milli.toml │ ├── float-no-leading-zero.toml │ ├── table-array-malformed-bracket.toml │ ├── text-after-string.toml │ ├── string-bad-escape.toml │ ├── text-in-array.toml │ ├── array-mixed-types-arrays-and-ints.toml │ ├── duplicate-key-table.toml │ ├── text-after-array-entries.toml │ ├── text-before-array-separator.toml │ └── table-array-implicit.toml └── invalid-encoder │ └── array-mixed-types-ints-and-floats.json ├── COPYING ├── toml.go ├── json.go ├── results.go ├── main.go └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | -------------------------------------------------------------------------------- /tests/valid/empty.toml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/valid/empty.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /tests/invalid/empty-table.toml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /tests/invalid/key-empty.toml: -------------------------------------------------------------------------------- 1 | = 1 2 | -------------------------------------------------------------------------------- /tests/invalid/key-hash.toml: -------------------------------------------------------------------------------- 1 | a# = 1 2 | -------------------------------------------------------------------------------- /tests/invalid/key-space.toml: -------------------------------------------------------------------------------- 1 | a b = 1 -------------------------------------------------------------------------------- /tests/invalid/table-empty.toml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /tests/valid/table-empty.toml: -------------------------------------------------------------------------------- 1 | [a] 2 | -------------------------------------------------------------------------------- /tests/invalid/key-single-open-bracket.toml: -------------------------------------------------------------------------------- 1 | [ -------------------------------------------------------------------------------- /tests/valid/key-space.toml: -------------------------------------------------------------------------------- 1 | "a b" = 1 2 | -------------------------------------------------------------------------------- /tests/invalid/key-newline.toml: -------------------------------------------------------------------------------- 1 | a 2 | = 1 3 | -------------------------------------------------------------------------------- /tests/invalid/key-open-bracket.toml: -------------------------------------------------------------------------------- 1 | [abc = 1 2 | -------------------------------------------------------------------------------- /tests/invalid/key-two-equals.toml: -------------------------------------------------------------------------------- 1 | key= = 1 2 | -------------------------------------------------------------------------------- /tests/invalid/table-whitespace.toml: -------------------------------------------------------------------------------- 1 | [invalid key] -------------------------------------------------------------------------------- /tests/valid/bool.toml: -------------------------------------------------------------------------------- 1 | t = true 2 | f = false 3 | -------------------------------------------------------------------------------- /tests/valid/string-empty.toml: -------------------------------------------------------------------------------- 1 | answer = "" 2 | -------------------------------------------------------------------------------- /tests/invalid/duplicate-tables.toml: -------------------------------------------------------------------------------- 1 | [a] 2 | [a] 3 | -------------------------------------------------------------------------------- /tests/valid/array-empty.toml: -------------------------------------------------------------------------------- 1 | thevoid = [[[[[]]]]] 2 | -------------------------------------------------------------------------------- /tests/valid/array-nospaces.toml: -------------------------------------------------------------------------------- 1 | ints = [1,2,3] 2 | -------------------------------------------------------------------------------- /tests/valid/key-equals-nospace.toml: -------------------------------------------------------------------------------- 1 | answer=42 2 | -------------------------------------------------------------------------------- /tests/valid/table-sub-empty.toml: -------------------------------------------------------------------------------- 1 | [a] 2 | [a.b] 3 | -------------------------------------------------------------------------------- /tests/valid/table-whitespace.toml: -------------------------------------------------------------------------------- 1 | ["valid key"] 2 | -------------------------------------------------------------------------------- /tests/valid/unicode-literal.toml: -------------------------------------------------------------------------------- 1 | answer = "δ" 2 | -------------------------------------------------------------------------------- /tests/invalid/string-byte-escapes.toml: -------------------------------------------------------------------------------- 1 | answer = "\x33" 2 | -------------------------------------------------------------------------------- /tests/valid/arrays-nested.toml: -------------------------------------------------------------------------------- 1 | nest = [["a"], ["b"]] 2 | -------------------------------------------------------------------------------- /tests/valid/float.toml: -------------------------------------------------------------------------------- 1 | pi = 3.14 2 | negpi = -3.14 3 | -------------------------------------------------------------------------------- /tests/valid/table-empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": {} 3 | } 4 | -------------------------------------------------------------------------------- /tests/invalid/empty-implicit-table.toml: -------------------------------------------------------------------------------- 1 | [naughty..naughty] 2 | -------------------------------------------------------------------------------- /tests/invalid/string-bad-byte-escape.toml: -------------------------------------------------------------------------------- 1 | naughty = "\xAg" 2 | -------------------------------------------------------------------------------- /tests/invalid/table-with-pound.toml: -------------------------------------------------------------------------------- 1 | [key#group] 2 | answer = 42 -------------------------------------------------------------------------------- /tests/valid/implicit-groups.toml: -------------------------------------------------------------------------------- 1 | [a.b.c] 2 | answer = 42 3 | -------------------------------------------------------------------------------- /tests/valid/integer.toml: -------------------------------------------------------------------------------- 1 | answer = 42 2 | neganswer = -42 3 | -------------------------------------------------------------------------------- /tests/invalid/duplicate-keys.toml: -------------------------------------------------------------------------------- 1 | dupe = false 2 | dupe = true 3 | -------------------------------------------------------------------------------- /tests/invalid/key-start-bracket.toml: -------------------------------------------------------------------------------- 1 | [a] 2 | [xyz = 5 3 | [b] 4 | -------------------------------------------------------------------------------- /tests/invalid/table-nested-brackets-open.toml: -------------------------------------------------------------------------------- 1 | [a[b] 2 | zyx = 42 3 | -------------------------------------------------------------------------------- /tests/valid/datetime.toml: -------------------------------------------------------------------------------- 1 | bestdayever = 1987-07-05T17:45:00Z 2 | -------------------------------------------------------------------------------- /tests/valid/table-with-pound.toml: -------------------------------------------------------------------------------- 1 | ["key#group"] 2 | answer = 42 3 | -------------------------------------------------------------------------------- /tests/invalid/datetime-malformed-no-t.toml: -------------------------------------------------------------------------------- 1 | no-t = 1987-07-0517:45:00Z 2 | -------------------------------------------------------------------------------- /tests/invalid/table-nested-brackets-close.toml: -------------------------------------------------------------------------------- 1 | [a]b] 2 | zyx = 42 3 | -------------------------------------------------------------------------------- /tests/invalid/text-after-table.toml: -------------------------------------------------------------------------------- 1 | [error] this shouldn't be here 2 | -------------------------------------------------------------------------------- /tests/valid/table-sub-empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": { "b": {} } 3 | } 4 | -------------------------------------------------------------------------------- /tests/valid/table-whitespace.json: -------------------------------------------------------------------------------- 1 | { 2 | "valid key": {} 3 | } 4 | -------------------------------------------------------------------------------- /tests/invalid/datetime-malformed-no-secs.toml: -------------------------------------------------------------------------------- 1 | no-secs = 1987-07-05T17:45Z 2 | -------------------------------------------------------------------------------- /tests/invalid/text-after-integer.toml: -------------------------------------------------------------------------------- 1 | answer = 42 the ultimate answer? 2 | -------------------------------------------------------------------------------- /tests/invalid/array-mixed-types-ints-and-floats.toml: -------------------------------------------------------------------------------- 1 | ints-and-floats = [1, 1.1] 2 | -------------------------------------------------------------------------------- /tests/invalid/datetime-malformed-no-leads.toml: -------------------------------------------------------------------------------- 1 | no-leads = 1987-7-05T17:45:00Z 2 | -------------------------------------------------------------------------------- /tests/invalid/float-no-trailing-digits.toml: -------------------------------------------------------------------------------- 1 | answer = 1. 2 | neganswer = -1. 3 | -------------------------------------------------------------------------------- /tests/invalid/string-no-close.toml: -------------------------------------------------------------------------------- 1 | no-ending-quote = "One time, at band camp 2 | -------------------------------------------------------------------------------- /tests/invalid/table-array-malformed-empty.toml: -------------------------------------------------------------------------------- 1 | [[]] 2 | name = "Born to Run" 3 | -------------------------------------------------------------------------------- /tests/valid/arrays-hetergeneous.toml: -------------------------------------------------------------------------------- 1 | mixed = [[1, 2], ["a", "b"], [1.1, 2.1]] 2 | -------------------------------------------------------------------------------- /tests/valid/key-special-chars.toml: -------------------------------------------------------------------------------- 1 | "~!@$^&*()_+-`1234567890[]|/?><.,;:'" = 1 2 | -------------------------------------------------------------------------------- /tests/valid/string-simple.toml: -------------------------------------------------------------------------------- 1 | answer = "You are not drinking enough whisky." 2 | -------------------------------------------------------------------------------- /tests/valid/table-array-implicit.toml: -------------------------------------------------------------------------------- 1 | [[albums.songs]] 2 | name = "Glory Days" 3 | -------------------------------------------------------------------------------- /tests/valid/unicode-escape.toml: -------------------------------------------------------------------------------- 1 | answer4 = "\u03B4" 2 | answer8 = "\U000003B4" 3 | -------------------------------------------------------------------------------- /tests/invalid/array-mixed-types-strings-and-ints.toml: -------------------------------------------------------------------------------- 1 | strings-and-ints = ["hi", 42] 2 | -------------------------------------------------------------------------------- /tests/invalid/datetime-malformed-with-milli.toml: -------------------------------------------------------------------------------- 1 | with-milli = 1987-07-5T17:45:00.12Z 2 | -------------------------------------------------------------------------------- /tests/invalid/float-no-leading-zero.toml: -------------------------------------------------------------------------------- 1 | answer = .12345 2 | neganswer = -.12345 3 | -------------------------------------------------------------------------------- /tests/invalid/table-array-malformed-bracket.toml: -------------------------------------------------------------------------------- 1 | [[albums] 2 | name = "Born to Run" 3 | -------------------------------------------------------------------------------- /tests/invalid/text-after-string.toml: -------------------------------------------------------------------------------- 1 | string = "Is there life after strings?" No. 2 | -------------------------------------------------------------------------------- /tests/valid/key-space.json: -------------------------------------------------------------------------------- 1 | { 2 | "a b": {"type": "integer", "value": "1"} 3 | } 4 | -------------------------------------------------------------------------------- /tests/valid/long-float.toml: -------------------------------------------------------------------------------- 1 | longpi = 3.141592653589793 2 | neglongpi = -3.141592653589793 3 | -------------------------------------------------------------------------------- /tests/valid/unicode-literal.json: -------------------------------------------------------------------------------- 1 | { 2 | "answer": {"type": "string", "value": "δ"} 3 | } 4 | -------------------------------------------------------------------------------- /tests/invalid/string-bad-escape.toml: -------------------------------------------------------------------------------- 1 | invalid-escape = "This string has a bad \a escape character." 2 | -------------------------------------------------------------------------------- /tests/valid/implicit-and-explicit-after.toml: -------------------------------------------------------------------------------- 1 | [a.b.c] 2 | answer = 42 3 | 4 | [a] 5 | better = 43 6 | -------------------------------------------------------------------------------- /tests/valid/implicit-and-explicit-before.toml: -------------------------------------------------------------------------------- 1 | [a] 2 | better = 43 3 | 4 | [a.b.c] 5 | answer = 42 6 | -------------------------------------------------------------------------------- /tests/valid/key-equals-nospace.json: -------------------------------------------------------------------------------- 1 | { 2 | "answer": {"type": "integer", "value": "42"} 3 | } 4 | -------------------------------------------------------------------------------- /tests/valid/long-integer.toml: -------------------------------------------------------------------------------- 1 | answer = 9223372036854775807 2 | neganswer = -9223372036854775808 3 | -------------------------------------------------------------------------------- /tests/valid/table-array-one.toml: -------------------------------------------------------------------------------- 1 | [[people]] 2 | first_name = "Bruce" 3 | last_name = "Springsteen" 4 | -------------------------------------------------------------------------------- /tests/invalid/text-in-array.toml: -------------------------------------------------------------------------------- 1 | array = [ 2 | "Entry 1", 3 | I don't belong, 4 | "Entry 2", 5 | ] 6 | -------------------------------------------------------------------------------- /tests/invalid/array-mixed-types-arrays-and-ints.toml: -------------------------------------------------------------------------------- 1 | arrays-and-ints = [1, ["Arrays are not integers."]] 2 | -------------------------------------------------------------------------------- /tests/invalid/duplicate-key-table.toml: -------------------------------------------------------------------------------- 1 | [fruit] 2 | type = "apple" 3 | 4 | [fruit.type] 5 | apple = "yes" 6 | -------------------------------------------------------------------------------- /tests/valid/datetime.json: -------------------------------------------------------------------------------- 1 | { 2 | "bestdayever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"} 3 | } 4 | -------------------------------------------------------------------------------- /tests/valid/string-empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "answer": { 3 | "type": "string", 4 | "value": "" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/invalid/text-after-array-entries.toml: -------------------------------------------------------------------------------- 1 | array = [ 2 | "Is there life after an array separator?", No 3 | "Entry" 4 | ] 5 | -------------------------------------------------------------------------------- /tests/invalid/text-before-array-separator.toml: -------------------------------------------------------------------------------- 1 | array = [ 2 | "Is there life before an array separator?" No, 3 | "Entry" 4 | ] 5 | -------------------------------------------------------------------------------- /tests/valid/bool.json: -------------------------------------------------------------------------------- 1 | { 2 | "f": {"type": "bool", "value": "false"}, 3 | "t": {"type": "bool", "value": "true"} 4 | } 5 | -------------------------------------------------------------------------------- /tests/valid/example.toml: -------------------------------------------------------------------------------- 1 | best-day-ever = 1987-07-05T17:45:00Z 2 | 3 | [numtheory] 4 | boring = false 5 | perfection = [6, 28, 496] 6 | -------------------------------------------------------------------------------- /tests/valid/float.json: -------------------------------------------------------------------------------- 1 | { 2 | "pi": {"type": "float", "value": "3.14"}, 3 | "negpi": {"type": "float", "value": "-3.14"} 4 | } 5 | -------------------------------------------------------------------------------- /tests/valid/table-with-pound.json: -------------------------------------------------------------------------------- 1 | { 2 | "key#group": { 3 | "answer": {"type": "integer", "value": "42"} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/valid/integer.json: -------------------------------------------------------------------------------- 1 | { 2 | "answer": {"type": "integer", "value": "42"}, 3 | "neganswer": {"type": "integer", "value": "-42"} 4 | } 5 | -------------------------------------------------------------------------------- /tests/valid/key-special-chars.json: -------------------------------------------------------------------------------- 1 | { 2 | "~!@$^&*()_+-`1234567890[]|/?><.,;:'": { 3 | "type": "integer", "value": "1" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/valid/string-with-pound.toml: -------------------------------------------------------------------------------- 1 | pound = "We see no # comments here." 2 | poundcomment = "But there are # some comments here." # Did I # mess you up? 3 | -------------------------------------------------------------------------------- /tests/valid/unicode-escape.json: -------------------------------------------------------------------------------- 1 | { 2 | "answer4": {"type": "string", "value": "\u03B4"}, 3 | "answer8": {"type": "string", "value": "\u03B4"} 4 | } 5 | -------------------------------------------------------------------------------- /tests/valid/string-simple.json: -------------------------------------------------------------------------------- 1 | { 2 | "answer": { 3 | "type": "string", 4 | "value": "You are not drinking enough whisky." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/valid/long-float.json: -------------------------------------------------------------------------------- 1 | { 2 | "longpi": {"type": "float", "value": "3.141592653589793"}, 3 | "neglongpi": {"type": "float", "value": "-3.141592653589793"} 4 | } 5 | -------------------------------------------------------------------------------- /tests/valid/table-array-implicit.json: -------------------------------------------------------------------------------- 1 | { 2 | "albums": { 3 | "songs": [ 4 | {"name": {"type": "string", "value": "Glory Days"}} 5 | ] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/valid/long-integer.json: -------------------------------------------------------------------------------- 1 | { 2 | "answer": {"type": "integer", "value": "9223372036854775807"}, 3 | "neganswer": {"type": "integer", "value": "-9223372036854775808"} 4 | } 5 | -------------------------------------------------------------------------------- /tests/valid/arrays.toml: -------------------------------------------------------------------------------- 1 | ints = [1, 2, 3] 2 | floats = [1.1, 2.1, 3.1] 3 | strings = ["a", "b", "c"] 4 | dates = [ 5 | 1987-07-05T17:45:00Z, 6 | 1979-05-27T07:32:00Z, 7 | 2006-06-01T11:00:00Z, 8 | ] 9 | -------------------------------------------------------------------------------- /tests/valid/implicit-groups.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": { 3 | "b": { 4 | "c": { 5 | "answer": {"type": "integer", "value": "42"} 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/valid/table-array-one.json: -------------------------------------------------------------------------------- 1 | { 2 | "people": [ 3 | { 4 | "first_name": {"type": "string", "value": "Bruce"}, 5 | "last_name": {"type": "string", "value": "Springsteen"} 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /tests/valid/string-with-pound.json: -------------------------------------------------------------------------------- 1 | { 2 | "pound": {"type": "string", "value": "We see no # comments here."}, 3 | "poundcomment": { 4 | "type": "string", 5 | "value": "But there are # some comments here." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/valid/table-array-many.toml: -------------------------------------------------------------------------------- 1 | [[people]] 2 | first_name = "Bruce" 3 | last_name = "Springsteen" 4 | 5 | [[people]] 6 | first_name = "Eric" 7 | last_name = "Clapton" 8 | 9 | [[people]] 10 | first_name = "Bob" 11 | last_name = "Seger" 12 | -------------------------------------------------------------------------------- /tests/valid/raw-multiline-string.toml: -------------------------------------------------------------------------------- 1 | oneline = '''This string has a ' quote character.''' 2 | firstnl = ''' 3 | This string has a ' quote character.''' 4 | multiline = ''' 5 | This string 6 | has ' a quote character 7 | and more than 8 | one newline 9 | in it.''' 10 | -------------------------------------------------------------------------------- /tests/valid/implicit-and-explicit-after.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": { 3 | "better": {"type": "integer", "value": "43"}, 4 | "b": { 5 | "c": { 6 | "answer": {"type": "integer", "value": "42"} 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/valid/implicit-and-explicit-before.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": { 3 | "better": {"type": "integer", "value": "43"}, 4 | "b": { 5 | "c": { 6 | "answer": {"type": "integer", "value": "42"} 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/valid/array-nospaces.json: -------------------------------------------------------------------------------- 1 | { 2 | "ints": { 3 | "type": "array", 4 | "value": [ 5 | {"type": "integer", "value": "1"}, 6 | {"type": "integer", "value": "2"}, 7 | {"type": "integer", "value": "3"} 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/valid/array-empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "thevoid": { "type": "array", "value": [ 3 | {"type": "array", "value": [ 4 | {"type": "array", "value": [ 5 | {"type": "array", "value": [ 6 | {"type": "array", "value": []} 7 | ]} 8 | ]} 9 | ]} 10 | ]} 11 | } 12 | -------------------------------------------------------------------------------- /tests/valid/comments-everywhere.json: -------------------------------------------------------------------------------- 1 | { 2 | "group": { 3 | "answer": {"type": "integer", "value": "42"}, 4 | "more": { 5 | "type": "array", 6 | "value": [ 7 | {"type": "integer", "value": "42"}, 8 | {"type": "integer", "value": "42"} 9 | ] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/valid/table-array-nest.toml: -------------------------------------------------------------------------------- 1 | [[albums]] 2 | name = "Born to Run" 3 | 4 | [[albums.songs]] 5 | name = "Jungleland" 6 | 7 | [[albums.songs]] 8 | name = "Meeting Across the River" 9 | 10 | [[albums]] 11 | name = "Born in the USA" 12 | 13 | [[albums.songs]] 14 | name = "Glory Days" 15 | 16 | [[albums.songs]] 17 | name = "Dancing in the Dark" 18 | -------------------------------------------------------------------------------- /tests/valid/arrays-nested.json: -------------------------------------------------------------------------------- 1 | { 2 | "nest": { 3 | "type": "array", 4 | "value": [ 5 | {"type": "array", "value": [ 6 | {"type": "string", "value": "a"} 7 | ]}, 8 | {"type": "array", "value": [ 9 | {"type": "string", "value": "b"} 10 | ]} 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/invalid-encoder/array-mixed-types-ints-and-floats.json: -------------------------------------------------------------------------------- 1 | { 2 | "ints-and-floats": { 3 | "type": "array", 4 | "value": [ 5 | { 6 | "type": "integer", 7 | "value": "1" 8 | }, 9 | { 10 | "type": "float", 11 | "value": "1.1" 12 | } 13 | ] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/valid/raw-string.toml: -------------------------------------------------------------------------------- 1 | backspace = 'This string has a \b backspace character.' 2 | tab = 'This string has a \t tab character.' 3 | newline = 'This string has a \n new line character.' 4 | formfeed = 'This string has a \f form feed character.' 5 | carriage = 'This string has a \r carriage return character.' 6 | slash = 'This string has a \/ slash character.' 7 | backslash = 'This string has a \\ backslash character.' 8 | -------------------------------------------------------------------------------- /tests/valid/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "best-day-ever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"}, 3 | "numtheory": { 4 | "boring": {"type": "bool", "value": "false"}, 5 | "perfection": { 6 | "type": "array", 7 | "value": [ 8 | {"type": "integer", "value": "6"}, 9 | {"type": "integer", "value": "28"}, 10 | {"type": "integer", "value": "496"} 11 | ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/valid/raw-multiline-string.json: -------------------------------------------------------------------------------- 1 | { 2 | "oneline": { 3 | "type": "string", 4 | "value": "This string has a ' quote character." 5 | }, 6 | "firstnl": { 7 | "type": "string", 8 | "value": "This string has a ' quote character." 9 | }, 10 | "multiline": { 11 | "type": "string", 12 | "value": "This string\nhas ' a quote character\nand more than\none newline\nin it." 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/valid/multiline-string.toml: -------------------------------------------------------------------------------- 1 | multiline_empty_one = """""" 2 | multiline_empty_two = """ 3 | """ 4 | multiline_empty_three = """\ 5 | """ 6 | multiline_empty_four = """\ 7 | \ 8 | \ 9 | """ 10 | 11 | equivalent_one = "The quick brown fox jumps over the lazy dog." 12 | equivalent_two = """ 13 | The quick brown \ 14 | 15 | 16 | fox jumps over \ 17 | the lazy dog.""" 18 | 19 | equivalent_three = """\ 20 | The quick brown \ 21 | fox jumps over \ 22 | the lazy dog.\ 23 | """ 24 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | -------------------------------------------------------------------------------- /tests/valid/table-array-many.json: -------------------------------------------------------------------------------- 1 | { 2 | "people": [ 3 | { 4 | "first_name": {"type": "string", "value": "Bruce"}, 5 | "last_name": {"type": "string", "value": "Springsteen"} 6 | }, 7 | { 8 | "first_name": {"type": "string", "value": "Eric"}, 9 | "last_name": {"type": "string", "value": "Clapton"} 10 | }, 11 | { 12 | "first_name": {"type": "string", "value": "Bob"}, 13 | "last_name": {"type": "string", "value": "Seger"} 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /tests/valid/comments-everywhere.toml: -------------------------------------------------------------------------------- 1 | # Top comment. 2 | # Top comment. 3 | # Top comment. 4 | 5 | # [no-extraneous-groups-please] 6 | 7 | [group] # Comment 8 | answer = 42 # Comment 9 | # no-extraneous-keys-please = 999 10 | # Inbetween comment. 11 | more = [ # Comment 12 | # What about multiple # comments? 13 | # Can you handle it? 14 | # 15 | # Evil. 16 | # Evil. 17 | 42, 42, # Comments within arrays are fun. 18 | # What about multiple # comments? 19 | # Can you handle it? 20 | # 21 | # Evil. 22 | # Evil. 23 | # ] Did I fool you? 24 | ] # Hopefully not. 25 | -------------------------------------------------------------------------------- /tests/valid/arrays-hetergeneous.json: -------------------------------------------------------------------------------- 1 | { 2 | "mixed": { 3 | "type": "array", 4 | "value": [ 5 | {"type": "array", "value": [ 6 | {"type": "integer", "value": "1"}, 7 | {"type": "integer", "value": "2"} 8 | ]}, 9 | {"type": "array", "value": [ 10 | {"type": "string", "value": "a"}, 11 | {"type": "string", "value": "b"} 12 | ]}, 13 | {"type": "array", "value": [ 14 | {"type": "float", "value": "1.1"}, 15 | {"type": "float", "value": "2.1"} 16 | ]} 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/valid/table-array-nest.json: -------------------------------------------------------------------------------- 1 | { 2 | "albums": [ 3 | { 4 | "name": {"type": "string", "value": "Born to Run"}, 5 | "songs": [ 6 | {"name": {"type": "string", "value": "Jungleland"}}, 7 | {"name": {"type": "string", "value": "Meeting Across the River"}} 8 | ] 9 | }, 10 | { 11 | "name": {"type": "string", "value": "Born in the USA"}, 12 | "songs": [ 13 | {"name": {"type": "string", "value": "Glory Days"}}, 14 | {"name": {"type": "string", "value": "Dancing in the Dark"}} 15 | ] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tests/invalid/table-array-implicit.toml: -------------------------------------------------------------------------------- 1 | # This test is a bit tricky. It should fail because the first use of 2 | # `[[albums.songs]]` without first declaring `albums` implies that `albums` 3 | # must be a table. The alternative would be quite weird. Namely, it wouldn't 4 | # comply with the TOML spec: "Each double-bracketed sub-table will belong to 5 | # the most *recently* defined table element *above* it." 6 | # 7 | # This is in contrast to the *valid* test, table-array-implicit where 8 | # `[[albums.songs]]` works by itself, so long as `[[albums]]` isn't declared 9 | # later. (Although, `[albums]` could be.) 10 | [[albums.songs]] 11 | name = "Glory Days" 12 | 13 | [[albums]] 14 | name = "Born in the USA" 15 | -------------------------------------------------------------------------------- /tests/valid/string-escapes.toml: -------------------------------------------------------------------------------- 1 | backspace = "This string has a \b backspace character." 2 | tab = "This string has a \t tab character." 3 | newline = "This string has a \n new line character." 4 | formfeed = "This string has a \f form feed character." 5 | carriage = "This string has a \r carriage return character." 6 | quote = "This string has a \" quote character." 7 | backslash = "This string has a \\ backslash character." 8 | notunicode1 = "This string does not have a unicode \\u escape." 9 | notunicode2 = "This string does not have a unicode \u005Cu escape." 10 | notunicode3 = "This string does not have a unicode \\u0075 escape." 11 | notunicode4 = "This string does not have a unicode \\\u0075 escape." 12 | -------------------------------------------------------------------------------- /tests/valid/multiline-string.json: -------------------------------------------------------------------------------- 1 | { 2 | "multiline_empty_one": { 3 | "type": "string", 4 | "value": "" 5 | }, 6 | "multiline_empty_two": { 7 | "type": "string", 8 | "value": "" 9 | }, 10 | "multiline_empty_three": { 11 | "type": "string", 12 | "value": "" 13 | }, 14 | "multiline_empty_four": { 15 | "type": "string", 16 | "value": "" 17 | }, 18 | "equivalent_one": { 19 | "type": "string", 20 | "value": "The quick brown fox jumps over the lazy dog." 21 | }, 22 | "equivalent_two": { 23 | "type": "string", 24 | "value": "The quick brown fox jumps over the lazy dog." 25 | }, 26 | "equivalent_three": { 27 | "type": "string", 28 | "value": "The quick brown fox jumps over the lazy dog." 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/valid/raw-string.json: -------------------------------------------------------------------------------- 1 | { 2 | "backspace": { 3 | "type": "string", 4 | "value": "This string has a \\b backspace character." 5 | }, 6 | "tab": { 7 | "type": "string", 8 | "value": "This string has a \\t tab character." 9 | }, 10 | "newline": { 11 | "type": "string", 12 | "value": "This string has a \\n new line character." 13 | }, 14 | "formfeed": { 15 | "type": "string", 16 | "value": "This string has a \\f form feed character." 17 | }, 18 | "carriage": { 19 | "type": "string", 20 | "value": "This string has a \\r carriage return character." 21 | }, 22 | "slash": { 23 | "type": "string", 24 | "value": "This string has a \\/ slash character." 25 | }, 26 | "backslash": { 27 | "type": "string", 28 | "value": "This string has a \\\\ backslash character." 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/valid/arrays.json: -------------------------------------------------------------------------------- 1 | { 2 | "ints": { 3 | "type": "array", 4 | "value": [ 5 | {"type": "integer", "value": "1"}, 6 | {"type": "integer", "value": "2"}, 7 | {"type": "integer", "value": "3"} 8 | ] 9 | }, 10 | "floats": { 11 | "type": "array", 12 | "value": [ 13 | {"type": "float", "value": "1.1"}, 14 | {"type": "float", "value": "2.1"}, 15 | {"type": "float", "value": "3.1"} 16 | ] 17 | }, 18 | "strings": { 19 | "type": "array", 20 | "value": [ 21 | {"type": "string", "value": "a"}, 22 | {"type": "string", "value": "b"}, 23 | {"type": "string", "value": "c"} 24 | ] 25 | }, 26 | "dates": { 27 | "type": "array", 28 | "value": [ 29 | {"type": "datetime", "value": "1987-07-05T17:45:00Z"}, 30 | {"type": "datetime", "value": "1979-05-27T07:32:00Z"}, 31 | {"type": "datetime", "value": "2006-06-01T11:00:00Z"} 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/valid/string-escapes.json: -------------------------------------------------------------------------------- 1 | { 2 | "backspace": { 3 | "type": "string", 4 | "value": "This string has a \u0008 backspace character." 5 | }, 6 | "tab": { 7 | "type": "string", 8 | "value": "This string has a \u0009 tab character." 9 | }, 10 | "newline": { 11 | "type": "string", 12 | "value": "This string has a \u000A new line character." 13 | }, 14 | "formfeed": { 15 | "type": "string", 16 | "value": "This string has a \u000C form feed character." 17 | }, 18 | "carriage": { 19 | "type": "string", 20 | "value": "This string has a \u000D carriage return character." 21 | }, 22 | "quote": { 23 | "type": "string", 24 | "value": "This string has a \u0022 quote character." 25 | }, 26 | "backslash": { 27 | "type": "string", 28 | "value": "This string has a \u005C backslash character." 29 | }, 30 | "notunicode1": { 31 | "type": "string", 32 | "value": "This string does not have a unicode \\u escape." 33 | }, 34 | "notunicode2": { 35 | "type": "string", 36 | "value": "This string does not have a unicode \u005Cu escape." 37 | }, 38 | "notunicode3": { 39 | "type": "string", 40 | "value": "This string does not have a unicode \\u0075 escape." 41 | }, 42 | "notunicode4": { 43 | "type": "string", 44 | "value": "This string does not have a unicode \\\u0075 escape." 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /toml.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // cmpToml consumes the recursive structure of both `expected` and `test` 8 | // simultaneously. If anything is unequal, the result has failed and 9 | // comparison stops. 10 | // 11 | // N.B. `reflect.DeepEqual` could work here, but it won't tell us how the 12 | // two structures are different. (Although we do use it here on primitive 13 | // values.) 14 | func (r result) cmpToml(expected, test interface{}) result { 15 | if isTomlValue(expected) { 16 | if !isTomlValue(test) { 17 | return r.failedf("Key '%s' in expected output is a primitive "+ 18 | "TOML value (not table or array), but the encoder provided "+ 19 | "a %T.", r.key, test) 20 | } 21 | if !reflect.DeepEqual(expected, test) { 22 | return r.failedf("Values for key '%s' differ. Expected value is "+ 23 | "%v (%T), but your encoder produced %v (%T).", 24 | r.key, expected, expected, test, test) 25 | } 26 | return r 27 | } 28 | switch e := expected.(type) { 29 | case map[string]interface{}: 30 | return r.cmpTomlMaps(e, test) 31 | case []interface{}: 32 | return r.cmpTomlArrays(e, test) 33 | default: 34 | return r.failedf("Unrecognized TOML structure: %T", expected) 35 | } 36 | panic("unreachable") 37 | } 38 | 39 | func (r result) cmpTomlMaps( 40 | e map[string]interface{}, 41 | test interface{}, 42 | ) result { 43 | t, ok := test.(map[string]interface{}) 44 | if !ok { 45 | return r.mismatch("table", t) 46 | } 47 | 48 | // Check that the keys of each map are equivalent. 49 | for k, _ := range e { 50 | if _, ok := t[k]; !ok { 51 | bunk := r.kjoin(k) 52 | return bunk.failedf("Could not find key '%s' in encoder output.", 53 | bunk.key) 54 | } 55 | } 56 | for k, _ := range t { 57 | if _, ok := e[k]; !ok { 58 | bunk := r.kjoin(k) 59 | return bunk.failedf("Could not find key '%s' in expected output.", 60 | bunk.key) 61 | } 62 | } 63 | 64 | // Okay, now make sure that each value is equivalent. 65 | for k, _ := range e { 66 | if sub := r.kjoin(k).cmpToml(e[k], t[k]); sub.failed() { 67 | return sub 68 | } 69 | } 70 | return r 71 | } 72 | 73 | func (r result) cmpTomlArrays(ea []interface{}, t interface{}) result { 74 | ta, ok := t.([]interface{}) 75 | if !ok { 76 | return r.mismatch("array", t) 77 | } 78 | if len(ea) != len(ta) { 79 | return r.failedf("Array lengths differ for key '%s'. Expected a "+ 80 | "length of %d but got %d.", r.key, len(ea), len(ta)) 81 | } 82 | for i := 0; i < len(ea); i++ { 83 | if sub := r.cmpToml(ea[i], ta[i]); sub.failed() { 84 | return sub 85 | } 86 | } 87 | return r 88 | } 89 | 90 | func isTomlValue(v interface{}) bool { 91 | switch v.(type) { 92 | case map[string]interface{}, []interface{}: 93 | return false 94 | } 95 | return true 96 | } 97 | -------------------------------------------------------------------------------- /json.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | // compareJson consumes the recursive structure of both `expected` and `test` 8 | // simultaneously. If anything is unequal, the result has failed and 9 | // comparison stops. 10 | // 11 | // N.B. `reflect.DeepEqual` could work here, but it won't tell us how the 12 | // two structures are different. 13 | func (r result) cmpJson(expected, test interface{}) result { 14 | switch e := expected.(type) { 15 | case map[string]interface{}: 16 | return r.cmpJsonMaps(e, test) 17 | case []interface{}: 18 | return r.cmpJsonArrays(e, test) 19 | default: 20 | return r.failedf("Key '%s' in expected output should be a map or a "+ 21 | "list of maps, but it's a %T.", r.key, expected) 22 | } 23 | panic("unreachable") 24 | } 25 | 26 | func (r result) cmpJsonMaps( 27 | e map[string]interface{}, test interface{}) result { 28 | 29 | t, ok := test.(map[string]interface{}) 30 | if !ok { 31 | return r.mismatch("table", t) 32 | } 33 | 34 | // Check to make sure both or neither are values. 35 | if isValue(e) && !isValue(t) { 36 | return r.failedf("Key '%s' is supposed to be a value, but the "+ 37 | "parser reports it as a table.", r.key) 38 | } 39 | if !isValue(e) && isValue(t) { 40 | return r.failedf("Key '%s' is supposed to be a table, but the "+ 41 | "parser reports it as a value.", r.key) 42 | } 43 | if isValue(e) && isValue(t) { 44 | return r.cmpJsonValues(e, t) 45 | } 46 | 47 | // Check that the keys of each map are equivalent. 48 | for k, _ := range e { 49 | if _, ok := t[k]; !ok { 50 | bunk := r.kjoin(k) 51 | return bunk.failedf("Could not find key '%s' in parser output.", 52 | bunk.key) 53 | } 54 | } 55 | for k, _ := range t { 56 | if _, ok := e[k]; !ok { 57 | bunk := r.kjoin(k) 58 | return bunk.failedf("Could not find key '%s' in expected output.", 59 | bunk.key) 60 | } 61 | } 62 | 63 | // Okay, now make sure that each value is equivalent. 64 | for k, _ := range e { 65 | if sub := r.kjoin(k).cmpJson(e[k], t[k]); sub.failed() { 66 | return sub 67 | } 68 | } 69 | return r 70 | } 71 | 72 | func (r result) cmpJsonArrays(e, t interface{}) result { 73 | ea, ok := e.([]interface{}) 74 | if !ok { 75 | return r.failedf("BUG in test case. 'value' should be a JSON array "+ 76 | "when 'type' indicates 'array', but it is a %T.", e) 77 | } 78 | 79 | ta, ok := t.([]interface{}) 80 | if !ok { 81 | return r.failedf("Malformed parser output. 'value' should be a "+ 82 | "JSON array when 'type' indicates 'array', but it is a %T.", t) 83 | } 84 | if len(ea) != len(ta) { 85 | return r.failedf("Array lengths differ for key '%s'. Expected a "+ 86 | "length of %d but got %d.", r.key, len(ea), len(ta)) 87 | } 88 | for i := 0; i < len(ea); i++ { 89 | if sub := r.cmpJson(ea[i], ta[i]); sub.failed() { 90 | return sub 91 | } 92 | } 93 | return r 94 | } 95 | 96 | func (r result) cmpJsonValues(e, t map[string]interface{}) result { 97 | etype, ok := e["type"].(string) 98 | if !ok { 99 | return r.failedf("BUG in test case. 'type' should be a string, "+ 100 | "but it is a %T.", e["type"]) 101 | } 102 | 103 | ttype, ok := t["type"].(string) 104 | if !ok { 105 | return r.failedf("Malformed parser output. 'type' should be a "+ 106 | "string, but it is a %T.", t["type"]) 107 | } 108 | 109 | if etype != ttype { 110 | return r.valMismatch(etype, ttype) 111 | } 112 | 113 | // If this is an array, then we've got to do some work to check 114 | // equality. 115 | if etype == "array" { 116 | return r.cmpJsonArrays(e["value"], t["value"]) 117 | } 118 | 119 | // Floats need special attention too. Not every language can 120 | // represent the same floats, and sometimes the string version of 121 | // a float can be wonky with extra zeroes and what not. 122 | if etype == "float" { 123 | enum, ok := e["value"].(string) 124 | if !ok { 125 | return r.failedf("BUG in test case. 'value' should be a string, "+ 126 | "but it is a %T.", e["value"]) 127 | } 128 | tnum, ok := t["value"].(string) 129 | if !ok { 130 | return r.failedf("Malformed parser output. 'value' should be a "+ 131 | "string but it is a %T.", t["value"]) 132 | } 133 | return r.cmpFloats(enum, tnum) 134 | } 135 | 136 | // Otherwise, we can do simple string equality. 137 | if e["value"] != t["value"] { 138 | return r.failedf("Values for key '%s' don't match. Expected a "+ 139 | "value of '%s' but got '%s'.", r.key, e["value"], t["value"]) 140 | } 141 | return r 142 | } 143 | 144 | func (r result) cmpFloats(e, t string) result { 145 | ef, err := strconv.ParseFloat(e, 64) 146 | if err != nil { 147 | return r.failedf("BUG in test case. Could not read '%s' as a float "+ 148 | "value for key '%s'.", e, r.key) 149 | } 150 | 151 | tf, err := strconv.ParseFloat(t, 64) 152 | if err != nil { 153 | return r.failedf("Malformed parser output. Could not read '%s' as "+ 154 | "a float value for key '%s'.", t, r.key) 155 | } 156 | if ef != tf { 157 | return r.failedf("Values for key '%s' don't match. Expected a "+ 158 | "value of '%v' but got '%v'.", r.key, ef, tf) 159 | } 160 | return r 161 | } 162 | 163 | func isValue(m map[string]interface{}) bool { 164 | if len(m) != 2 { 165 | return false 166 | } 167 | if _, ok := m["type"]; !ok { 168 | return false 169 | } 170 | if _, ok := m["value"]; !ok { 171 | return false 172 | } 173 | return true 174 | } 175 | -------------------------------------------------------------------------------- /results.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/BurntSushi/toml" 9 | 10 | "os" 11 | "os/exec" 12 | ) 13 | 14 | type result struct { 15 | testName string 16 | err error 17 | valid bool 18 | failure string 19 | key string 20 | } 21 | 22 | func (r result) errorf(format string, v ...interface{}) result { 23 | r.err = fmt.Errorf(format, v...) 24 | return r 25 | } 26 | 27 | func (r result) failedf(format string, v ...interface{}) result { 28 | r.failure = fmt.Sprintf(format, v...) 29 | return r 30 | } 31 | 32 | func (r result) mismatch(expected string, got interface{}) result { 33 | return r.failedf("Type mismatch for key '%s'. Expected %s but got %T.", 34 | r.key, expected, got) 35 | } 36 | 37 | func (r result) valMismatch(expected string, got string) result { 38 | return r.failedf("Type mismatch for key '%s'. Expected %s but got %s.", 39 | r.key, expected, got) 40 | } 41 | 42 | func (r result) kjoin(key string) result { 43 | if len(r.key) == 0 { 44 | r.key = key 45 | } else { 46 | r.key += "." + key 47 | } 48 | return r 49 | } 50 | 51 | func (r result) failed() bool { 52 | return r.err != nil || len(r.failure) > 0 53 | } 54 | 55 | func (r result) pathTest() string { 56 | ext := "toml" 57 | if flagEncoder { 58 | ext = "json" 59 | } 60 | if r.valid { 61 | return vPath("%s.%s", r.testName, ext) 62 | } 63 | return invPath("%s.%s", r.testName, ext) 64 | } 65 | 66 | func (r result) pathGold() string { 67 | if !r.valid { 68 | panic("Invalid tests do not have a 'correct' version.") 69 | } 70 | if flagEncoder { 71 | return vPath("%s.toml", r.testName) 72 | } 73 | return vPath("%s.json", r.testName) 74 | } 75 | 76 | func runInvalidTest(name string) result { 77 | r := result{ 78 | testName: name, 79 | valid: false, 80 | } 81 | 82 | _, stderr, err := runParser(r.pathTest()) 83 | if err != nil { 84 | // Errors here are OK if it's just an exit error. 85 | if _, ok := err.(*exec.ExitError); ok { 86 | return r 87 | } 88 | 89 | // Otherwise, something has gone horribly wrong. 90 | return r.errorf(err.Error()) 91 | } 92 | if stderr != nil { // test has passed! 93 | return r 94 | } 95 | return r.failedf("Expected an error, but no error was reported.") 96 | } 97 | 98 | func runValidTest(name string) result { 99 | r := result{ 100 | testName: name, 101 | valid: true, 102 | } 103 | 104 | stdout, stderr, err := runParser(r.pathTest()) 105 | if err != nil { 106 | if _, ok := err.(*exec.ExitError); ok { 107 | switch { 108 | case stderr != nil && stderr.Len() > 0: 109 | return r.failedf(stderr.String()) 110 | case stdout != nil && stdout.Len() > 0: 111 | return r.failedf(stdout.String()) 112 | } 113 | } 114 | return r.errorf(err.Error()) 115 | } 116 | 117 | if stdout == nil { 118 | return r.errorf("Parser does not satisfy interface. stdout is " + 119 | "empty, but the process exited successfully.") 120 | } 121 | 122 | if flagEncoder { 123 | tomlExpected, err := loadToml(r.pathGold()) 124 | if err != nil { 125 | return r.errorf(err.Error()) 126 | } 127 | var tomlTest interface{} 128 | if _, err := toml.DecodeReader(stdout, &tomlTest); err != nil { 129 | return r.errorf( 130 | "Could not decode TOML output from encoder: %s", err) 131 | } 132 | return r.cmpToml(tomlExpected, tomlTest) 133 | } else { 134 | jsonExpected, err := loadJson(r.pathGold()) 135 | if err != nil { 136 | return r.errorf(err.Error()) 137 | } 138 | var jsonTest interface{} 139 | if err := json.NewDecoder(stdout).Decode(&jsonTest); err != nil { 140 | return r.errorf( 141 | "Could not decode JSON output from parser: %s", err) 142 | } 143 | return r.cmpJson(jsonExpected, jsonTest) 144 | } 145 | } 146 | 147 | func runParser(testFile string) (*bytes.Buffer, *bytes.Buffer, error) { 148 | f, err := os.Open(testFile) 149 | if err != nil { 150 | return nil, nil, err 151 | } 152 | 153 | stdout := new(bytes.Buffer) 154 | stderr := new(bytes.Buffer) 155 | 156 | c := exec.Command(parserCmd) 157 | c.Stdin = f 158 | c.Stdout = stdout 159 | c.Stderr = stderr 160 | 161 | if err := c.Run(); err != nil { 162 | return stdout, stderr, err 163 | } 164 | return stdout, nil, nil 165 | } 166 | 167 | func loadJson(fp string) (interface{}, error) { 168 | fjson, err := os.Open(fp) 169 | if err != nil { 170 | return nil, fmt.Errorf( 171 | "Could not find expected JSON output at %s.", fp) 172 | } 173 | 174 | var vjson interface{} 175 | if err := json.NewDecoder(fjson).Decode(&vjson); err != nil { 176 | return nil, fmt.Errorf( 177 | "Could not decode expected JSON output at %s: %s", fp, err) 178 | } 179 | return vjson, nil 180 | } 181 | 182 | func loadToml(fp string) (interface{}, error) { 183 | var vtoml interface{} 184 | if _, err := toml.DecodeFile(fp, &vtoml); err != nil { 185 | return nil, fmt.Errorf( 186 | "Could not decode expected TOML output at %s: %s", fp, err) 187 | } 188 | return vtoml, nil 189 | } 190 | 191 | func (r result) String() string { 192 | buf := new(bytes.Buffer) 193 | p := func(s string, v ...interface{}) { fmt.Fprintf(buf, s, v...) } 194 | 195 | validStr := "invalid" 196 | if r.valid { 197 | validStr = "valid" 198 | } 199 | p("Test: %s (%s)\n\n", r.testName, validStr) 200 | 201 | if r.err != nil { 202 | p("Error running test: %s", r.err) 203 | return buf.String() 204 | } 205 | if len(r.failure) > 0 { 206 | p(r.failure) 207 | return buf.String() 208 | } 209 | 210 | p("PASSED.") 211 | return buf.String() 212 | } 213 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "go/build" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | "path" 11 | "strings" 12 | ) 13 | 14 | var ( 15 | flagTestdir = "" 16 | flagShowAll = false 17 | flagEncoder = false 18 | ) 19 | 20 | var ( 21 | parserCmd string 22 | dirInvalid string 23 | dirValid string 24 | invalidExt = "toml" // set to "json" when testing encoders 25 | ) 26 | 27 | func init() { 28 | log.SetFlags(0) 29 | 30 | // If no test directory was specified, let's look for it automatically. 31 | // Assumes `toml-test` was installed with the Go tool. 32 | if len(flagTestdir) == 0 { 33 | imp := path.Join("github.com", "BurntSushi", "toml-test", "tests") 34 | for _, dir := range build.Default.SrcDirs() { 35 | if readable(path.Join(dir, imp)) { 36 | flagTestdir = path.Join(dir, imp) 37 | break 38 | } 39 | } 40 | } 41 | 42 | // Nada, just use 'tests'. 43 | if len(flagTestdir) == 0 { 44 | flagTestdir = "tests" 45 | } 46 | 47 | flag.StringVar(&flagTestdir, "testdir", flagTestdir, 48 | "The path to the test directory.") 49 | flag.BoolVar(&flagShowAll, "all", flagShowAll, 50 | "When set, all tests will be shown.") 51 | flag.BoolVar(&flagEncoder, "encoder", flagEncoder, 52 | "When set, the given executable will be tested as a TOML encoder.") 53 | 54 | flag.Usage = usage 55 | flag.Parse() 56 | 57 | dirValid = path.Join(flagTestdir, "valid") 58 | if flagEncoder { 59 | dirInvalid = path.Join(flagTestdir, "invalid-encoder") 60 | invalidExt = "json" 61 | } else { 62 | dirInvalid = path.Join(flagTestdir, "invalid") 63 | } 64 | } 65 | 66 | func usage() { 67 | log.Printf("Usage: %s parser-cmd [ test-name ... ]\n", 68 | path.Base(os.Args[0])) 69 | log.Println(` 70 | parser-cmd should be a program that accepts TOML data on stdin until EOF, 71 | and outputs the corresponding JSON encoding on stdout. Please see 'README.md' 72 | for details on how to satisfy the interface expected by 'toml-test' with your 73 | own parser. 74 | 75 | The 'testdir' directory should have two sub-directories: 'invalid' and 'valid'. 76 | 77 | The 'invalid' directory should contain 'toml' files, 78 | where test names are the file names not including the '.toml' suffix. 79 | 80 | The 'valid' directory should contain 'toml' files and a 'json' file for each 81 | 'toml' file, that contains the expected output of 'parser-cmd'. Test names 82 | are the file names not including the '.toml' or '.json' suffix. 83 | 84 | Test names must be globally unique. Behavior is undefined if there is a 85 | failure test with the same name as a valid test. 86 | 87 | Note that toml-test can also test TOML encoders with the "encoder" flag set. 88 | In particular, the binary will be given JSON on stdin and expect TOML on 89 | stdout. The JSON will be in the same format as specified in the toml-test 90 | README. Note that this depends on the correctness of my TOML parser! 91 | (For encoders, the same directory scheme above is used, except the 92 | 'invalid-encoder' directory is used instead of the 'invalid' directory.) 93 | 94 | Flags:`) 95 | 96 | flag.PrintDefaults() 97 | 98 | os.Exit(1) 99 | } 100 | 101 | func main() { 102 | if flag.NArg() < 1 { 103 | flag.Usage() 104 | } 105 | parserCmd = flag.Arg(0) 106 | 107 | var results []result 108 | 109 | // Run all tests. 110 | if flag.NArg() == 1 { 111 | results = runAllTests() 112 | } else { // just a few 113 | results = make([]result, 0, flag.NArg()-1) 114 | for _, testName := range flag.Args()[1:] { 115 | results = append(results, runTestByName(testName)) 116 | } 117 | } 118 | 119 | out := make([]string, 0, len(results)) 120 | passed, failed := 0, 0 121 | for _, r := range results { 122 | if flagShowAll || r.failed() { 123 | out = append(out, r.String()) 124 | } 125 | if r.failed() { 126 | failed++ 127 | } else { 128 | passed++ 129 | } 130 | } 131 | if len(out) > 0 { 132 | fmt.Println(strings.Join(out, "\n"+strings.Repeat("-", 79)+"\n")) 133 | fmt.Println("") 134 | } 135 | fmt.Printf("%d passed, %d failed\n", passed, failed) 136 | if failed > 0 { 137 | os.Exit(1) 138 | } 139 | } 140 | 141 | func runAllTests() []result { 142 | invalidTests, err := ioutil.ReadDir(dirInvalid) 143 | if err != nil { 144 | log.Fatalf("Cannot read invalid directory (%s): %s", dirInvalid, err) 145 | } 146 | 147 | validTests, err := ioutil.ReadDir(dirValid) 148 | if err != nil { 149 | log.Fatalf("Cannot read valid directory (%s): %s", dirValid, err) 150 | } 151 | 152 | results := make([]result, 0, len(invalidTests)+len(validTests)) 153 | for _, f := range invalidTests { 154 | if !strings.HasSuffix(f.Name(), fmt.Sprintf(".%s", invalidExt)) { 155 | continue 156 | } 157 | tname := stripSuffix(f.Name()) 158 | results = append(results, runInvalidTest(tname)) 159 | } 160 | for _, f := range validTests { 161 | if !strings.HasSuffix(f.Name(), ".toml") { 162 | continue 163 | } 164 | tname := stripSuffix(f.Name()) 165 | results = append(results, runValidTest(tname)) 166 | } 167 | return results 168 | } 169 | 170 | func runTestByName(name string) result { 171 | if readable(invPath("%s.%s", name, invalidExt)) { 172 | return runInvalidTest(name) 173 | } 174 | if readable(vPath("%s.toml", name)) && readable(vPath("%s.json", name)) { 175 | 176 | return runValidTest(name) 177 | } 178 | return result{testName: name}.errorf( 179 | "Could not find test in '%s' or '%s'.", dirInvalid, dirValid) 180 | } 181 | 182 | func readable(fp string) bool { 183 | _, err := os.Stat(fp) 184 | return err == nil 185 | } 186 | 187 | func vPath(fname string, v ...interface{}) string { 188 | return path.Join(dirValid, fmt.Sprintf(fname, v...)) 189 | } 190 | 191 | func invPath(fname string, v ...interface{}) string { 192 | return path.Join(dirInvalid, fmt.Sprintf(fname, v...)) 193 | } 194 | 195 | func stripSuffix(fname string) string { 196 | for _, suf := range []string{".toml", ".json"} { 197 | if ind := strings.LastIndex(fname, suf); ind > -1 { 198 | return fname[0:ind] 199 | } 200 | } 201 | return fname 202 | } 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### A language agnostic test suite for TOML encoders and decoders 2 | 3 | `toml-test` is a higher-order program that tests other 4 | [TOML](https://github.com/mojombo/toml) 5 | decoders or encoders. The goal is to make it comprehensive. 6 | Tests are divided into two groups: invalid TOML data and valid TOML 7 | data. Decoders that reject invalid TOML data pass invalid TOML tests. Deocoders 8 | that accept valid TOML data and output precisely what is expected pass valid 9 | tests. The output format is JSON, described below. 10 | 11 | Both decoders and encoders share valid tests, except an encoder accepts JSON 12 | and outputs TOML. The TOML representations are read with a blessed decoder and 13 | compared. Note though that encoders have their own set of invalid tests in the 14 | invalid-encoder directory. The JSON given to a TOML encoder is in the same 15 | format as the JSON that a TOML decoder should output. 16 | 17 | Version: v0.2.0 (in sync with TOML) 18 | 19 | Compatible with TOML version 20 | [v0.2.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.2.0.md) 21 | 22 | Dependencies: [Go](http://golang.org). 23 | 24 | 25 | ### Try it out 26 | 27 | All you need is to have [Go](http://golang.org) installed. Then simply 28 | use: 29 | 30 | ```bash 31 | cd 32 | export GOPATH=$HOME/go # if it isn't already set 33 | go get github.com/BurntSushi/toml-test # install test suite 34 | go get github.com/BurntSushi/toml/cmd/toml-test-decoder # e.g., install my parser 35 | $HOME/go/bin/toml-test $HOME/go/bin/toml-test-decoder # e.g., run tests on my parser 36 | # Outputs: 64 passed, 0 failed 37 | ``` 38 | 39 | The `go get` commands install Go packages and binaries into your `GOPATH`. 40 | 41 | To test your decoder, you will have to satisfy the interface expected by 42 | `toml-test` described below. Then just execute `toml-test your-decoder` in the 43 | `toml-test` directory to run your decoder against all tests. 44 | 45 | To test your encoder, the instructions are the same, except the input/output 46 | is reversed, and you'll need to run `toml-test -encoder your-encoder`. 47 | (You can install my TOML encoder with `go get 48 | github.com/BurntSushi/toml/cmd/toml-test-encoder`.) 49 | 50 | 51 | ### Interface of a decoder 52 | 53 | For your decoder to be compatible with `toml-test`, it **must** satisfy the 54 | interface expected. 55 | 56 | Your decoder **must** accept TOML data on `stdin` until EOF. 57 | 58 | If the TOML data is invalid, your decoder **must** return with a non-zero 59 | exit code indicating an error. 60 | 61 | If the TOML data is valid, your decoder **must** output a JSON encoding of that 62 | data on `stdout` and return with a zero exit code indicating success. 63 | 64 | 65 | ### Interface of an encoder 66 | 67 | For your encoder to be compatible with `toml-test`, it **must** satisfy the 68 | interface expected. 69 | 70 | Your encoder **must** accept JSON data on `stdin` until EOF. 71 | 72 | If the JSON data cannot be converted to a valid TOML representation, your 73 | encoder **must** return with a non-zero exit code indicating an error. 74 | 75 | If the JSON data can be converted to a valid TOML representation, your encoder 76 | **must** output a TOML encoding of that data on `stdout` and return with a zero 77 | exit code indicating success. 78 | 79 | 80 | ### JSON encoding 81 | 82 | The following JSON encoding applies equally to both encoders and decoders. 83 | 84 | * TOML tables correspond to JSON objects. 85 | * TOML table arrays correspond to JSON arrays. 86 | * TOML values correspond to a special JSON object of the form 87 | `{"type": "{TTYPE}", "value": {TVALUE}}` 88 | 89 | In the above, `TTYPE` may be one of: 90 | 91 | * string 92 | * integer 93 | * float 94 | * datetime 95 | * bool 96 | * array 97 | 98 | and `TVALUE` is always a JSON string, except when `TTYPE` is `array` in which 99 | `TVALUE` is a JSON array containing TOML values. 100 | 101 | Empty hashes correspond to empty JSON objects (i.e., `{}`) and empty arrays 102 | correspond to empty JSON arrays (i.e., `[]`). 103 | 104 | 105 | ### Example JSON encoding 106 | 107 | Here is the TOML data: 108 | 109 | ```toml 110 | best-day-ever = 1987-07-05T17:45:00Z 111 | 112 | [numtheory] 113 | boring = false 114 | perfection = [6, 28, 496] 115 | ``` 116 | 117 | And the JSON encoding expected by `toml-test` is: 118 | 119 | ```json 120 | { 121 | "best-day-ever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"}, 122 | "numtheory": { 123 | "boring": {"type": "bool", "value": "false"}, 124 | "perfection": { 125 | "type": "array", 126 | "value": [ 127 | {"type": "integer", "value": "6"}, 128 | {"type": "integer", "value": "28"}, 129 | {"type": "integer", "value": "496"} 130 | ] 131 | } 132 | } 133 | } 134 | ``` 135 | 136 | Note that the only JSON values ever used are objects, arrays and strings. 137 | 138 | 139 | ### Assumptions of Truth 140 | 141 | The following are taken as ground truths by `toml-test`: 142 | 143 | * All tests classified as `invalid` **are** invalid. 144 | * All tests classified as `valid` **are** valid. 145 | * All expected outputs in `valid/test-name.json` are exactly correct. 146 | * The Go standard library package `encoding/json` decodes JSON correctly. 147 | * When testing encoders, the TOML decoder at 148 | [BurntSushi/toml](https://github.com/BurntSushi/toml) is assumed to be 149 | correct. (Note that this assumption is not made when testing decoders!) 150 | 151 | Of particular note is that **no TOML decoder** is taken as ground truth when 152 | testing decoders. This means that most changes to the spec will only require an 153 | update of the tests in `toml-test`. (Bigger changes may require an adjustment 154 | of how two things are considered equal. Particularly if a new type of data is 155 | added.) Obviously, this advantage does not apply to testing TOML encoders since 156 | there must exist a TOML decoder that conforms to the specification in order to 157 | read the output of a TOML encoder. 158 | 159 | 160 | ### Adding tests 161 | 162 | `toml-test` was designed so that tests can be easily added and removed. As 163 | mentioned above, tests are split into two groups: invalid and valid tests. 164 | 165 | Invalid tests **only check if a decoder rejects invalid TOML data**. Or, in the 166 | case of testing encoders, invalid tests **only check if an encoder rejects an 167 | invalid representation of TOML** (e.g., a hetergeneous array). 168 | Therefore, all invalid tests should try to **test one thing and one thing 169 | only**. Invalid tests should be named after the fault it is trying to expose. 170 | Invalid tests for decoders are in the `tests/invalid` directory while invalid 171 | tests for encoders are in the `tests/invalid-encoder` directory. 172 | 173 | Valid tests check that a decoder accepts valid TOML data **and** that 174 | the parser has the correct representation of the TOML data. Therefore, valid 175 | tests need a JSON encoding in addition to the TOML data. The tests should be 176 | small enough that writing the JSON encoding by hand will not give you brain 177 | damage. The exact reverse is true when testing encoders. 178 | 179 | A valid test without either a `.json` or `.toml` file will automatically fail. 180 | 181 | If you have tests that you'd like to add, please submit a pull request. 182 | 183 | 184 | ### Why JSON? 185 | 186 | In order for a language agnostic test suite to work, we need some kind of data 187 | exchange format. TOML cannot be used, as it would imply that a particular 188 | parser has a blessing of correctness. 189 | 190 | My decision to use JSON was not a careful one. It was based on expediency. The 191 | Go standard library has an excellent `encoding/json` package built in, which 192 | made it easy to compare JSON data. 193 | 194 | The problem with JSON is that the types in TOML are not in one-to-one 195 | correspondence with JSON. This is why every TOML value represented in JSON is 196 | tagged with a type annotation, as described above. 197 | 198 | YAML may be closer in correspondence with TOML, but I don't believe we should 199 | rely on that correspondence. Making things explicit with JSON means that 200 | writing tests is a little more cumbersome, but it also reduces the number of 201 | assumptions we need to make. 202 | 203 | 204 | ### Decoders or encoders that satisfy the `toml-test` interface 205 | 206 | If you have an implementation, send a pull request adding to this list. Please 207 | note the commit SHA1 or version tag that your parser supports in your `README`. 208 | 209 | * C (@ajwans) - https://github.com/ajwans/libtoml 210 | * C++ (@skystrife) - https://github.com/skystrife/cpptoml 211 | * Go (@thompelletier) - https://github.com/pelletier/go-toml 212 | * Go w/ Reflection (@BurntSushi) - https://github.com/BurntSushi/toml/tree/master/cmd/toml-test-decoder 213 | * Python (@uiri) - https://github.com/uiri/toml 214 | * Python (@marksteve) - https://github.com/marksteve/toml-ply 215 | * Ruby (@jm, @cespare) - https://gist.github.com/cespare/5052442 216 | * Rust (@mneumann) - https://github.com/mneumann/rust-toml 217 | 218 | N.B. Your decoder/encoder doesn't need to pass all tests to be on this list. 219 | 220 | 221 | ### TOML projects using the test suite 222 | 223 | I'm not sure why, but some projects seem to build their own testing harness 224 | while using the tests in this repository. That's OK, but it's probably more 225 | work than necessary. Plus, I claim that `toml-test` outputs nice error 226 | messages. 227 | 228 | * Haskell (@cies) - https://github.com/cies/htoml 229 | * Julia (@pygy) - https://github.com/pygy/TOML.jl 230 | * PHP (@yosymfony) - https://github.com/yosymfony/toml 231 | * Python (@f03lipe) - https://github.com/f03lipe/toml-python 232 | 233 | --------------------------------------------------------------------------------