From 167cc9547131ebbffe9f999f5e0ed6262631aaca Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Tue, 14 Oct 2025 19:28:03 -0700 Subject: [PATCH 1/5] pull in libtomlc99 Problem: libflux-conf will require libtomlc99. Pull in a copy from flux-core, with unit test and upstream inputs. --- Makefile.am | 1 + configure.ac | 1 + vendored/libtomlc99/BurntSushi_input/COPYING | 14 + .../array-mixed-types-arrays-and-ints.toml | 1 + .../array-mixed-types-ints-and-floats.toml | 1 + .../array-mixed-types-strings-and-ints.toml | 1 + .../invalid/datetime-malformed-no-leads.toml | 1 + .../invalid/datetime-malformed-no-secs.toml | 1 + .../invalid/datetime-malformed-no-t.toml | 1 + .../datetime-malformed-with-milli.toml | 1 + .../invalid/duplicate-key-table.toml | 5 + .../invalid/duplicate-keys.toml | 2 + .../invalid/duplicate-tables.toml | 2 + .../invalid/empty-implicit-table.toml | 1 + .../BurntSushi_input/invalid/empty-table.toml | 1 + .../invalid/float-no-leading-zero.toml | 2 + .../invalid/float-no-trailing-digits.toml | 2 + .../BurntSushi_input/invalid/key-empty.toml | 1 + .../BurntSushi_input/invalid/key-hash.toml | 1 + .../BurntSushi_input/invalid/key-newline.toml | 2 + .../invalid/key-open-bracket.toml | 1 + .../invalid/key-single-open-bracket.toml | 1 + .../BurntSushi_input/invalid/key-space.toml | 1 + .../invalid/key-start-bracket.toml | 3 + .../invalid/key-two-equals.toml | 1 + .../invalid/string-bad-byte-escape.toml | 1 + .../invalid/string-bad-escape.toml | 1 + .../invalid/string-byte-escapes.toml | 1 + .../invalid/string-no-close.toml | 1 + .../invalid/table-array-implicit.toml | 14 + .../table-array-malformed-bracket.toml | 2 + .../invalid/table-array-malformed-empty.toml | 2 + .../BurntSushi_input/invalid/table-empty.toml | 1 + .../invalid/table-nested-brackets-close.toml | 2 + .../invalid/table-nested-brackets-open.toml | 2 + .../invalid/table-whitespace.toml | 1 + .../invalid/table-with-pound.toml | 2 + .../invalid/text-after-array-entries.toml | 4 + .../invalid/text-after-integer.toml | 1 + .../invalid/text-after-string.toml | 1 + .../invalid/text-after-table.toml | 1 + .../invalid/text-before-array-separator.toml | 4 + .../invalid/text-in-array.toml | 5 + .../BurntSushi_input/valid/array-empty.json | 11 + .../BurntSushi_input/valid/array-empty.toml | 1 + .../valid/array-nospaces.json | 10 + .../valid/array-nospaces.toml | 1 + .../valid/arrays-hetergeneous.json | 19 + .../valid/arrays-hetergeneous.toml | 1 + .../BurntSushi_input/valid/arrays-nested.json | 13 + .../BurntSushi_input/valid/arrays-nested.toml | 1 + .../BurntSushi_input/valid/arrays.json | 34 + .../BurntSushi_input/valid/arrays.toml | 8 + .../BurntSushi_input/valid/bool.json | 4 + .../BurntSushi_input/valid/bool.toml | 2 + .../valid/comments-everywhere.json | 12 + .../valid/comments-everywhere.toml | 24 + .../BurntSushi_input/valid/datetime.json | 3 + .../BurntSushi_input/valid/datetime.toml | 1 + .../BurntSushi_input/valid/empty.json | 1 + .../BurntSushi_input/valid/empty.toml | 0 .../BurntSushi_input/valid/example.json | 14 + .../BurntSushi_input/valid/example.toml | 5 + .../BurntSushi_input/valid/float.json | 4 + .../BurntSushi_input/valid/float.toml | 2 + .../valid/implicit-and-explicit-after.json | 10 + .../valid/implicit-and-explicit-after.toml | 5 + .../valid/implicit-and-explicit-before.json | 10 + .../valid/implicit-and-explicit-before.toml | 5 + .../valid/implicit-groups.json | 9 + .../valid/implicit-groups.toml | 2 + .../BurntSushi_input/valid/integer.json | 4 + .../BurntSushi_input/valid/integer.toml | 2 + .../valid/key-equals-nospace.json | 3 + .../valid/key-equals-nospace.toml | 1 + .../BurntSushi_input/valid/key-space.json | 3 + .../BurntSushi_input/valid/key-space.toml | 1 + .../valid/key-special-chars.json | 5 + .../valid/key-special-chars.toml | 1 + .../BurntSushi_input/valid/long-float.json | 4 + .../BurntSushi_input/valid/long-float.toml | 2 + .../BurntSushi_input/valid/long-integer.json | 4 + .../BurntSushi_input/valid/long-integer.toml | 2 + .../valid/multiline-string.json | 30 + .../valid/multiline-string.toml | 23 + .../valid/raw-multiline-string.json | 14 + .../valid/raw-multiline-string.toml | 9 + .../BurntSushi_input/valid/raw-string.json | 30 + .../BurntSushi_input/valid/raw-string.toml | 7 + .../BurntSushi_input/valid/string-empty.json | 6 + .../BurntSushi_input/valid/string-empty.toml | 1 + .../valid/string-escapes.json | 46 + .../valid/string-escapes.toml | 11 + .../BurntSushi_input/valid/string-simple.json | 6 + .../BurntSushi_input/valid/string-simple.toml | 1 + .../valid/string-with-pound.json | 7 + .../valid/string-with-pound.toml | 2 + .../valid/table-array-implicit.json | 7 + .../valid/table-array-implicit.toml | 2 + .../valid/table-array-many.json | 16 + .../valid/table-array-many.toml | 11 + .../valid/table-array-nest.json | 18 + .../valid/table-array-nest.toml | 17 + .../valid/table-array-one.json | 8 + .../valid/table-array-one.toml | 3 + .../BurntSushi_input/valid/table-empty.json | 3 + .../BurntSushi_input/valid/table-empty.toml | 1 + .../valid/table-sub-empty.json | 3 + .../valid/table-sub-empty.toml | 2 + .../valid/table-whitespace.json | 3 + .../valid/table-whitespace.toml | 1 + .../valid/table-with-pound.json | 5 + .../valid/table-with-pound.toml | 2 + .../valid/unicode-escape.json | 4 + .../valid/unicode-escape.toml | 2 + .../valid/unicode-literal.json | 3 + .../valid/unicode-literal.toml | 1 + vendored/libtomlc99/LICENSE | 22 + vendored/libtomlc99/Makefile.am | 132 + vendored/libtomlc99/README.md | 95 + vendored/libtomlc99/test/toml.c | 454 ++++ vendored/libtomlc99/toml.c | 2142 +++++++++++++++++ vendored/libtomlc99/toml.h | 145 ++ vendored/libtomlc99/toml_cat.c | 193 ++ vendored/libtomlc99/toml_json.c | 214 ++ 125 files changed, 4041 insertions(+) create mode 100644 vendored/libtomlc99/BurntSushi_input/COPYING create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/array-mixed-types-arrays-and-ints.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/array-mixed-types-ints-and-floats.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/array-mixed-types-strings-and-ints.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/datetime-malformed-no-leads.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/datetime-malformed-no-secs.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/datetime-malformed-no-t.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/datetime-malformed-with-milli.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/duplicate-key-table.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/duplicate-keys.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/duplicate-tables.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/empty-implicit-table.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/empty-table.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/float-no-leading-zero.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/float-no-trailing-digits.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/key-empty.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/key-hash.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/key-newline.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/key-open-bracket.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/key-single-open-bracket.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/key-space.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/key-start-bracket.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/key-two-equals.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/string-bad-byte-escape.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/string-bad-escape.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/string-byte-escapes.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/string-no-close.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/table-array-implicit.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/table-array-malformed-bracket.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/table-array-malformed-empty.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/table-empty.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/table-nested-brackets-close.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/table-nested-brackets-open.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/table-whitespace.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/table-with-pound.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/text-after-array-entries.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/text-after-integer.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/text-after-string.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/text-after-table.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/text-before-array-separator.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/invalid/text-in-array.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/array-empty.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/array-empty.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/array-nospaces.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/array-nospaces.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/arrays-hetergeneous.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/arrays-hetergeneous.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/arrays-nested.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/arrays-nested.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/arrays.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/arrays.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/bool.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/bool.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/comments-everywhere.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/comments-everywhere.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/datetime.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/datetime.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/empty.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/empty.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/example.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/example.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/float.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/float.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/implicit-and-explicit-after.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/implicit-and-explicit-after.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/implicit-and-explicit-before.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/implicit-and-explicit-before.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/implicit-groups.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/implicit-groups.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/integer.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/integer.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/key-equals-nospace.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/key-equals-nospace.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/key-space.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/key-space.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/key-special-chars.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/key-special-chars.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/long-float.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/long-float.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/long-integer.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/long-integer.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/multiline-string.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/multiline-string.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/raw-multiline-string.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/raw-multiline-string.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/raw-string.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/raw-string.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/string-empty.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/string-empty.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/string-escapes.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/string-escapes.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/string-simple.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/string-simple.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/string-with-pound.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/string-with-pound.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/table-array-implicit.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/table-array-implicit.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/table-array-many.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/table-array-many.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/table-array-nest.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/table-array-nest.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/table-array-one.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/table-array-one.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/table-empty.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/table-empty.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/table-sub-empty.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/table-sub-empty.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/table-whitespace.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/table-whitespace.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/table-with-pound.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/table-with-pound.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/unicode-escape.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/unicode-escape.toml create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/unicode-literal.json create mode 100644 vendored/libtomlc99/BurntSushi_input/valid/unicode-literal.toml create mode 100644 vendored/libtomlc99/LICENSE create mode 100644 vendored/libtomlc99/Makefile.am create mode 100644 vendored/libtomlc99/README.md create mode 100644 vendored/libtomlc99/test/toml.c create mode 100644 vendored/libtomlc99/toml.c create mode 100644 vendored/libtomlc99/toml.h create mode 100644 vendored/libtomlc99/toml_cat.c create mode 100644 vendored/libtomlc99/toml_json.c diff --git a/Makefile.am b/Makefile.am index 768071a..3943ec0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4,6 +4,7 @@ SUBDIRS = \ vendored/libveb \ vendored/libglibc \ vendored/libczmq \ + vendored/libtomlc99 \ src/libmissing \ src/libutil \ src/libhostlist \ diff --git a/configure.ac b/configure.ac index f6f2484..633451d 100644 --- a/configure.ac +++ b/configure.ac @@ -99,6 +99,7 @@ AC_CONFIG_FILES( \ vendored/libveb/Makefile \ vendored/libglibc/Makefile \ vendored/libczmq/Makefile \ + vendored/libtomlc99/Makefile \ src/libmissing/Makefile \ src/libutil/Makefile \ src/libhostlist/Makefile \ diff --git a/vendored/libtomlc99/BurntSushi_input/COPYING b/vendored/libtomlc99/BurntSushi_input/COPYING new file mode 100644 index 0000000..5a8e332 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/COPYING @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/array-mixed-types-arrays-and-ints.toml b/vendored/libtomlc99/BurntSushi_input/invalid/array-mixed-types-arrays-and-ints.toml new file mode 100644 index 0000000..051ec73 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/array-mixed-types-arrays-and-ints.toml @@ -0,0 +1 @@ +arrays-and-ints = [1, ["Arrays are not integers."]] diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/array-mixed-types-ints-and-floats.toml b/vendored/libtomlc99/BurntSushi_input/invalid/array-mixed-types-ints-and-floats.toml new file mode 100644 index 0000000..a5aa9b7 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/array-mixed-types-ints-and-floats.toml @@ -0,0 +1 @@ +ints-and-floats = [1, 1.1] diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/array-mixed-types-strings-and-ints.toml b/vendored/libtomlc99/BurntSushi_input/invalid/array-mixed-types-strings-and-ints.toml new file mode 100644 index 0000000..f348308 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/array-mixed-types-strings-and-ints.toml @@ -0,0 +1 @@ +strings-and-ints = ["hi", 42] diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/datetime-malformed-no-leads.toml b/vendored/libtomlc99/BurntSushi_input/invalid/datetime-malformed-no-leads.toml new file mode 100644 index 0000000..123f173 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/datetime-malformed-no-leads.toml @@ -0,0 +1 @@ +no-leads = 1987-7-05T17:45:00Z diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/datetime-malformed-no-secs.toml b/vendored/libtomlc99/BurntSushi_input/invalid/datetime-malformed-no-secs.toml new file mode 100644 index 0000000..ba93900 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/datetime-malformed-no-secs.toml @@ -0,0 +1 @@ +no-secs = 1987-07-05T17:45Z diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/datetime-malformed-no-t.toml b/vendored/libtomlc99/BurntSushi_input/invalid/datetime-malformed-no-t.toml new file mode 100644 index 0000000..617e3c5 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/datetime-malformed-no-t.toml @@ -0,0 +1 @@ +no-t = 1987-07-0517:45:00Z diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/datetime-malformed-with-milli.toml b/vendored/libtomlc99/BurntSushi_input/invalid/datetime-malformed-with-milli.toml new file mode 100644 index 0000000..eef792f --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/datetime-malformed-with-milli.toml @@ -0,0 +1 @@ +with-milli = 1987-07-5T17:45:00.12Z diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/duplicate-key-table.toml b/vendored/libtomlc99/BurntSushi_input/invalid/duplicate-key-table.toml new file mode 100644 index 0000000..cedf05f --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/duplicate-key-table.toml @@ -0,0 +1,5 @@ +[fruit] +type = "apple" + +[fruit.type] +apple = "yes" diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/duplicate-keys.toml b/vendored/libtomlc99/BurntSushi_input/invalid/duplicate-keys.toml new file mode 100644 index 0000000..9b5aee0 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/duplicate-keys.toml @@ -0,0 +1,2 @@ +dupe = false +dupe = true diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/duplicate-tables.toml b/vendored/libtomlc99/BurntSushi_input/invalid/duplicate-tables.toml new file mode 100644 index 0000000..8ddf49b --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/duplicate-tables.toml @@ -0,0 +1,2 @@ +[a] +[a] diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/empty-implicit-table.toml b/vendored/libtomlc99/BurntSushi_input/invalid/empty-implicit-table.toml new file mode 100644 index 0000000..0cc36d0 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/empty-implicit-table.toml @@ -0,0 +1 @@ +[naughty..naughty] diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/empty-table.toml b/vendored/libtomlc99/BurntSushi_input/invalid/empty-table.toml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/empty-table.toml @@ -0,0 +1 @@ +[] diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/float-no-leading-zero.toml b/vendored/libtomlc99/BurntSushi_input/invalid/float-no-leading-zero.toml new file mode 100644 index 0000000..cab76bf --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/float-no-leading-zero.toml @@ -0,0 +1,2 @@ +answer = .12345 +neganswer = -.12345 diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/float-no-trailing-digits.toml b/vendored/libtomlc99/BurntSushi_input/invalid/float-no-trailing-digits.toml new file mode 100644 index 0000000..cbff2d0 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/float-no-trailing-digits.toml @@ -0,0 +1,2 @@ +answer = 1. +neganswer = -1. diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/key-empty.toml b/vendored/libtomlc99/BurntSushi_input/invalid/key-empty.toml new file mode 100644 index 0000000..09f998f --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/key-empty.toml @@ -0,0 +1 @@ + = 1 diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/key-hash.toml b/vendored/libtomlc99/BurntSushi_input/invalid/key-hash.toml new file mode 100644 index 0000000..e321b1f --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/key-hash.toml @@ -0,0 +1 @@ +a# = 1 diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/key-newline.toml b/vendored/libtomlc99/BurntSushi_input/invalid/key-newline.toml new file mode 100644 index 0000000..707aad5 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/key-newline.toml @@ -0,0 +1,2 @@ +a += 1 diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/key-open-bracket.toml b/vendored/libtomlc99/BurntSushi_input/invalid/key-open-bracket.toml new file mode 100644 index 0000000..f0aeb16 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/key-open-bracket.toml @@ -0,0 +1 @@ +[abc = 1 diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/key-single-open-bracket.toml b/vendored/libtomlc99/BurntSushi_input/invalid/key-single-open-bracket.toml new file mode 100644 index 0000000..8e2f0be --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/key-single-open-bracket.toml @@ -0,0 +1 @@ +[ \ No newline at end of file diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/key-space.toml b/vendored/libtomlc99/BurntSushi_input/invalid/key-space.toml new file mode 100644 index 0000000..201806d --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/key-space.toml @@ -0,0 +1 @@ +a b = 1 \ No newline at end of file diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/key-start-bracket.toml b/vendored/libtomlc99/BurntSushi_input/invalid/key-start-bracket.toml new file mode 100644 index 0000000..e0597ae --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/key-start-bracket.toml @@ -0,0 +1,3 @@ +[a] +[xyz = 5 +[b] diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/key-two-equals.toml b/vendored/libtomlc99/BurntSushi_input/invalid/key-two-equals.toml new file mode 100644 index 0000000..25a0378 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/key-two-equals.toml @@ -0,0 +1 @@ +key= = 1 diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/string-bad-byte-escape.toml b/vendored/libtomlc99/BurntSushi_input/invalid/string-bad-byte-escape.toml new file mode 100644 index 0000000..4c7be59 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/string-bad-byte-escape.toml @@ -0,0 +1 @@ +naughty = "\xAg" diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/string-bad-escape.toml b/vendored/libtomlc99/BurntSushi_input/invalid/string-bad-escape.toml new file mode 100644 index 0000000..60acb0c --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/string-bad-escape.toml @@ -0,0 +1 @@ +invalid-escape = "This string has a bad \a escape character." diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/string-byte-escapes.toml b/vendored/libtomlc99/BurntSushi_input/invalid/string-byte-escapes.toml new file mode 100644 index 0000000..e94452a --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/string-byte-escapes.toml @@ -0,0 +1 @@ +answer = "\x33" diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/string-no-close.toml b/vendored/libtomlc99/BurntSushi_input/invalid/string-no-close.toml new file mode 100644 index 0000000..0c292fc --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/string-no-close.toml @@ -0,0 +1 @@ +no-ending-quote = "One time, at band camp diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/table-array-implicit.toml b/vendored/libtomlc99/BurntSushi_input/invalid/table-array-implicit.toml new file mode 100644 index 0000000..05f2507 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/table-array-implicit.toml @@ -0,0 +1,14 @@ +# This test is a bit tricky. It should fail because the first use of +# `[[albums.songs]]` without first declaring `albums` implies that `albums` +# must be a table. The alternative would be quite weird. Namely, it wouldn't +# comply with the TOML spec: "Each double-bracketed sub-table will belong to +# the most *recently* defined table element *above* it." +# +# This is in contrast to the *valid* test, table-array-implicit where +# `[[albums.songs]]` works by itself, so long as `[[albums]]` isn't declared +# later. (Although, `[albums]` could be.) +[[albums.songs]] +name = "Glory Days" + +[[albums]] +name = "Born in the USA" diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/table-array-malformed-bracket.toml b/vendored/libtomlc99/BurntSushi_input/invalid/table-array-malformed-bracket.toml new file mode 100644 index 0000000..39c73b0 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/table-array-malformed-bracket.toml @@ -0,0 +1,2 @@ +[[albums] +name = "Born to Run" diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/table-array-malformed-empty.toml b/vendored/libtomlc99/BurntSushi_input/invalid/table-array-malformed-empty.toml new file mode 100644 index 0000000..a470ca3 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/table-array-malformed-empty.toml @@ -0,0 +1,2 @@ +[[]] +name = "Born to Run" diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/table-empty.toml b/vendored/libtomlc99/BurntSushi_input/invalid/table-empty.toml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/table-empty.toml @@ -0,0 +1 @@ +[] diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/table-nested-brackets-close.toml b/vendored/libtomlc99/BurntSushi_input/invalid/table-nested-brackets-close.toml new file mode 100644 index 0000000..c8b5a67 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/table-nested-brackets-close.toml @@ -0,0 +1,2 @@ +[a]b] +zyx = 42 diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/table-nested-brackets-open.toml b/vendored/libtomlc99/BurntSushi_input/invalid/table-nested-brackets-open.toml new file mode 100644 index 0000000..246d7e9 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/table-nested-brackets-open.toml @@ -0,0 +1,2 @@ +[a[b] +zyx = 42 diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/table-whitespace.toml b/vendored/libtomlc99/BurntSushi_input/invalid/table-whitespace.toml new file mode 100644 index 0000000..79bbcb1 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/table-whitespace.toml @@ -0,0 +1 @@ +[invalid key] \ No newline at end of file diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/table-with-pound.toml b/vendored/libtomlc99/BurntSushi_input/invalid/table-with-pound.toml new file mode 100644 index 0000000..0d8edb5 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/table-with-pound.toml @@ -0,0 +1,2 @@ +[key#group] +answer = 42 \ No newline at end of file diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/text-after-array-entries.toml b/vendored/libtomlc99/BurntSushi_input/invalid/text-after-array-entries.toml new file mode 100644 index 0000000..1a72890 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/text-after-array-entries.toml @@ -0,0 +1,4 @@ +array = [ + "Is there life after an array separator?", No + "Entry" +] diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/text-after-integer.toml b/vendored/libtomlc99/BurntSushi_input/invalid/text-after-integer.toml new file mode 100644 index 0000000..42de7af --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/text-after-integer.toml @@ -0,0 +1 @@ +answer = 42 the ultimate answer? diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/text-after-string.toml b/vendored/libtomlc99/BurntSushi_input/invalid/text-after-string.toml new file mode 100644 index 0000000..c92a6f1 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/text-after-string.toml @@ -0,0 +1 @@ +string = "Is there life after strings?" No. diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/text-after-table.toml b/vendored/libtomlc99/BurntSushi_input/invalid/text-after-table.toml new file mode 100644 index 0000000..87da9db --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/text-after-table.toml @@ -0,0 +1 @@ +[error] this shouldn't be here diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/text-before-array-separator.toml b/vendored/libtomlc99/BurntSushi_input/invalid/text-before-array-separator.toml new file mode 100644 index 0000000..9b06a39 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/text-before-array-separator.toml @@ -0,0 +1,4 @@ +array = [ + "Is there life before an array separator?" No, + "Entry" +] diff --git a/vendored/libtomlc99/BurntSushi_input/invalid/text-in-array.toml b/vendored/libtomlc99/BurntSushi_input/invalid/text-in-array.toml new file mode 100644 index 0000000..a6a6c42 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/invalid/text-in-array.toml @@ -0,0 +1,5 @@ +array = [ + "Entry 1", + I don't belong, + "Entry 2", +] diff --git a/vendored/libtomlc99/BurntSushi_input/valid/array-empty.json b/vendored/libtomlc99/BurntSushi_input/valid/array-empty.json new file mode 100644 index 0000000..2fbf256 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/array-empty.json @@ -0,0 +1,11 @@ +{ + "thevoid": { "type": "array", "value": [ + {"type": "array", "value": [ + {"type": "array", "value": [ + {"type": "array", "value": [ + {"type": "array", "value": []} + ]} + ]} + ]} + ]} +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/array-empty.toml b/vendored/libtomlc99/BurntSushi_input/valid/array-empty.toml new file mode 100644 index 0000000..fa58dc6 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/array-empty.toml @@ -0,0 +1 @@ +thevoid = [[[[[]]]]] diff --git a/vendored/libtomlc99/BurntSushi_input/valid/array-nospaces.json b/vendored/libtomlc99/BurntSushi_input/valid/array-nospaces.json new file mode 100644 index 0000000..1833d61 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/array-nospaces.json @@ -0,0 +1,10 @@ +{ + "ints": { + "type": "array", + "value": [ + {"type": "integer", "value": "1"}, + {"type": "integer", "value": "2"}, + {"type": "integer", "value": "3"} + ] + } +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/array-nospaces.toml b/vendored/libtomlc99/BurntSushi_input/valid/array-nospaces.toml new file mode 100644 index 0000000..6618936 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/array-nospaces.toml @@ -0,0 +1 @@ +ints = [1,2,3] diff --git a/vendored/libtomlc99/BurntSushi_input/valid/arrays-hetergeneous.json b/vendored/libtomlc99/BurntSushi_input/valid/arrays-hetergeneous.json new file mode 100644 index 0000000..478fa5c --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/arrays-hetergeneous.json @@ -0,0 +1,19 @@ +{ + "mixed": { + "type": "array", + "value": [ + {"type": "array", "value": [ + {"type": "integer", "value": "1"}, + {"type": "integer", "value": "2"} + ]}, + {"type": "array", "value": [ + {"type": "string", "value": "a"}, + {"type": "string", "value": "b"} + ]}, + {"type": "array", "value": [ + {"type": "float", "value": "1.1"}, + {"type": "float", "value": "2.1"} + ]} + ] + } +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/arrays-hetergeneous.toml b/vendored/libtomlc99/BurntSushi_input/valid/arrays-hetergeneous.toml new file mode 100644 index 0000000..a246fcf --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/arrays-hetergeneous.toml @@ -0,0 +1 @@ +mixed = [[1, 2], ["a", "b"], [1.1, 2.1]] diff --git a/vendored/libtomlc99/BurntSushi_input/valid/arrays-nested.json b/vendored/libtomlc99/BurntSushi_input/valid/arrays-nested.json new file mode 100644 index 0000000..d21920c --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/arrays-nested.json @@ -0,0 +1,13 @@ +{ + "nest": { + "type": "array", + "value": [ + {"type": "array", "value": [ + {"type": "string", "value": "a"} + ]}, + {"type": "array", "value": [ + {"type": "string", "value": "b"} + ]} + ] + } +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/arrays-nested.toml b/vendored/libtomlc99/BurntSushi_input/valid/arrays-nested.toml new file mode 100644 index 0000000..ce33022 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/arrays-nested.toml @@ -0,0 +1 @@ +nest = [["a"], ["b"]] diff --git a/vendored/libtomlc99/BurntSushi_input/valid/arrays.json b/vendored/libtomlc99/BurntSushi_input/valid/arrays.json new file mode 100644 index 0000000..58aedbc --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/arrays.json @@ -0,0 +1,34 @@ +{ + "ints": { + "type": "array", + "value": [ + {"type": "integer", "value": "1"}, + {"type": "integer", "value": "2"}, + {"type": "integer", "value": "3"} + ] + }, + "floats": { + "type": "array", + "value": [ + {"type": "float", "value": "1.1"}, + {"type": "float", "value": "2.1"}, + {"type": "float", "value": "3.1"} + ] + }, + "strings": { + "type": "array", + "value": [ + {"type": "string", "value": "a"}, + {"type": "string", "value": "b"}, + {"type": "string", "value": "c"} + ] + }, + "dates": { + "type": "array", + "value": [ + {"type": "datetime", "value": "1987-07-05T17:45:00Z"}, + {"type": "datetime", "value": "1979-05-27T07:32:00Z"}, + {"type": "datetime", "value": "2006-06-01T11:00:00Z"} + ] + } +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/arrays.toml b/vendored/libtomlc99/BurntSushi_input/valid/arrays.toml new file mode 100644 index 0000000..c435f57 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/arrays.toml @@ -0,0 +1,8 @@ +ints = [1, 2, 3] +floats = [1.1, 2.1, 3.1] +strings = ["a", "b", "c"] +dates = [ + 1987-07-05T17:45:00Z, + 1979-05-27T07:32:00Z, + 2006-06-01T11:00:00Z, +] diff --git a/vendored/libtomlc99/BurntSushi_input/valid/bool.json b/vendored/libtomlc99/BurntSushi_input/valid/bool.json new file mode 100644 index 0000000..ae368e9 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/bool.json @@ -0,0 +1,4 @@ +{ + "f": {"type": "bool", "value": "false"}, + "t": {"type": "bool", "value": "true"} +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/bool.toml b/vendored/libtomlc99/BurntSushi_input/valid/bool.toml new file mode 100644 index 0000000..a8a829b --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/bool.toml @@ -0,0 +1,2 @@ +t = true +f = false diff --git a/vendored/libtomlc99/BurntSushi_input/valid/comments-everywhere.json b/vendored/libtomlc99/BurntSushi_input/valid/comments-everywhere.json new file mode 100644 index 0000000..e69a2e9 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/comments-everywhere.json @@ -0,0 +1,12 @@ +{ + "group": { + "answer": {"type": "integer", "value": "42"}, + "more": { + "type": "array", + "value": [ + {"type": "integer", "value": "42"}, + {"type": "integer", "value": "42"} + ] + } + } +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/comments-everywhere.toml b/vendored/libtomlc99/BurntSushi_input/valid/comments-everywhere.toml new file mode 100644 index 0000000..3dca74c --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/comments-everywhere.toml @@ -0,0 +1,24 @@ +# Top comment. + # Top comment. +# Top comment. + +# [no-extraneous-groups-please] + +[group] # Comment +answer = 42 # Comment +# no-extraneous-keys-please = 999 +# Inbetween comment. +more = [ # Comment + # What about multiple # comments? + # Can you handle it? + # + # Evil. +# Evil. + 42, 42, # Comments within arrays are fun. + # What about multiple # comments? + # Can you handle it? + # + # Evil. +# Evil. +# ] Did I fool you? +] # Hopefully not. diff --git a/vendored/libtomlc99/BurntSushi_input/valid/datetime.json b/vendored/libtomlc99/BurntSushi_input/valid/datetime.json new file mode 100644 index 0000000..2ca93ce --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/datetime.json @@ -0,0 +1,3 @@ +{ + "bestdayever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"} +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/datetime.toml b/vendored/libtomlc99/BurntSushi_input/valid/datetime.toml new file mode 100644 index 0000000..2e99340 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/datetime.toml @@ -0,0 +1 @@ +bestdayever = 1987-07-05T17:45:00Z diff --git a/vendored/libtomlc99/BurntSushi_input/valid/empty.json b/vendored/libtomlc99/BurntSushi_input/valid/empty.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/empty.json @@ -0,0 +1 @@ +{} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/empty.toml b/vendored/libtomlc99/BurntSushi_input/valid/empty.toml new file mode 100644 index 0000000..e69de29 diff --git a/vendored/libtomlc99/BurntSushi_input/valid/example.json b/vendored/libtomlc99/BurntSushi_input/valid/example.json new file mode 100644 index 0000000..48aa907 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/example.json @@ -0,0 +1,14 @@ +{ + "best-day-ever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"}, + "numtheory": { + "boring": {"type": "bool", "value": "false"}, + "perfection": { + "type": "array", + "value": [ + {"type": "integer", "value": "6"}, + {"type": "integer", "value": "28"}, + {"type": "integer", "value": "496"} + ] + } + } +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/example.toml b/vendored/libtomlc99/BurntSushi_input/valid/example.toml new file mode 100644 index 0000000..8cb02e0 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/example.toml @@ -0,0 +1,5 @@ +best-day-ever = 1987-07-05T17:45:00Z + +[numtheory] +boring = false +perfection = [6, 28, 496] diff --git a/vendored/libtomlc99/BurntSushi_input/valid/float.json b/vendored/libtomlc99/BurntSushi_input/valid/float.json new file mode 100644 index 0000000..b8a2e97 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/float.json @@ -0,0 +1,4 @@ +{ + "pi": {"type": "float", "value": "3.14"}, + "negpi": {"type": "float", "value": "-3.14"} +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/float.toml b/vendored/libtomlc99/BurntSushi_input/valid/float.toml new file mode 100644 index 0000000..7c528d2 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/float.toml @@ -0,0 +1,2 @@ +pi = 3.14 +negpi = -3.14 diff --git a/vendored/libtomlc99/BurntSushi_input/valid/implicit-and-explicit-after.json b/vendored/libtomlc99/BurntSushi_input/valid/implicit-and-explicit-after.json new file mode 100644 index 0000000..374bd09 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/implicit-and-explicit-after.json @@ -0,0 +1,10 @@ +{ + "a": { + "better": {"type": "integer", "value": "43"}, + "b": { + "c": { + "answer": {"type": "integer", "value": "42"} + } + } + } +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/implicit-and-explicit-after.toml b/vendored/libtomlc99/BurntSushi_input/valid/implicit-and-explicit-after.toml new file mode 100644 index 0000000..c0e8865 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/implicit-and-explicit-after.toml @@ -0,0 +1,5 @@ +[a.b.c] +answer = 42 + +[a] +better = 43 diff --git a/vendored/libtomlc99/BurntSushi_input/valid/implicit-and-explicit-before.json b/vendored/libtomlc99/BurntSushi_input/valid/implicit-and-explicit-before.json new file mode 100644 index 0000000..374bd09 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/implicit-and-explicit-before.json @@ -0,0 +1,10 @@ +{ + "a": { + "better": {"type": "integer", "value": "43"}, + "b": { + "c": { + "answer": {"type": "integer", "value": "42"} + } + } + } +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/implicit-and-explicit-before.toml b/vendored/libtomlc99/BurntSushi_input/valid/implicit-and-explicit-before.toml new file mode 100644 index 0000000..eee68ff --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/implicit-and-explicit-before.toml @@ -0,0 +1,5 @@ +[a] +better = 43 + +[a.b.c] +answer = 42 diff --git a/vendored/libtomlc99/BurntSushi_input/valid/implicit-groups.json b/vendored/libtomlc99/BurntSushi_input/valid/implicit-groups.json new file mode 100644 index 0000000..fbae7fc --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/implicit-groups.json @@ -0,0 +1,9 @@ +{ + "a": { + "b": { + "c": { + "answer": {"type": "integer", "value": "42"} + } + } + } +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/implicit-groups.toml b/vendored/libtomlc99/BurntSushi_input/valid/implicit-groups.toml new file mode 100644 index 0000000..b6333e4 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/implicit-groups.toml @@ -0,0 +1,2 @@ +[a.b.c] +answer = 42 diff --git a/vendored/libtomlc99/BurntSushi_input/valid/integer.json b/vendored/libtomlc99/BurntSushi_input/valid/integer.json new file mode 100644 index 0000000..61985a1 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/integer.json @@ -0,0 +1,4 @@ +{ + "answer": {"type": "integer", "value": "42"}, + "neganswer": {"type": "integer", "value": "-42"} +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/integer.toml b/vendored/libtomlc99/BurntSushi_input/valid/integer.toml new file mode 100644 index 0000000..c4f6297 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/integer.toml @@ -0,0 +1,2 @@ +answer = 42 +neganswer = -42 diff --git a/vendored/libtomlc99/BurntSushi_input/valid/key-equals-nospace.json b/vendored/libtomlc99/BurntSushi_input/valid/key-equals-nospace.json new file mode 100644 index 0000000..1f8709a --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/key-equals-nospace.json @@ -0,0 +1,3 @@ +{ + "answer": {"type": "integer", "value": "42"} +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/key-equals-nospace.toml b/vendored/libtomlc99/BurntSushi_input/valid/key-equals-nospace.toml new file mode 100644 index 0000000..560901c --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/key-equals-nospace.toml @@ -0,0 +1 @@ +answer=42 diff --git a/vendored/libtomlc99/BurntSushi_input/valid/key-space.json b/vendored/libtomlc99/BurntSushi_input/valid/key-space.json new file mode 100644 index 0000000..9d1f769 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/key-space.json @@ -0,0 +1,3 @@ +{ + "a b": {"type": "integer", "value": "1"} +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/key-space.toml b/vendored/libtomlc99/BurntSushi_input/valid/key-space.toml new file mode 100644 index 0000000..f4f36c4 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/key-space.toml @@ -0,0 +1 @@ +"a b" = 1 diff --git a/vendored/libtomlc99/BurntSushi_input/valid/key-special-chars.json b/vendored/libtomlc99/BurntSushi_input/valid/key-special-chars.json new file mode 100644 index 0000000..3585b2c --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/key-special-chars.json @@ -0,0 +1,5 @@ +{ + "~!@$^&*()_+-`1234567890[]|/?><.,;:'": { + "type": "integer", "value": "1" + } +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/key-special-chars.toml b/vendored/libtomlc99/BurntSushi_input/valid/key-special-chars.toml new file mode 100644 index 0000000..cc572be --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/key-special-chars.toml @@ -0,0 +1 @@ +"~!@$^&*()_+-`1234567890[]|/?><.,;:'" = 1 diff --git a/vendored/libtomlc99/BurntSushi_input/valid/long-float.json b/vendored/libtomlc99/BurntSushi_input/valid/long-float.json new file mode 100644 index 0000000..8ceed47 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/long-float.json @@ -0,0 +1,4 @@ +{ + "longpi": {"type": "float", "value": "3.141592653589793"}, + "neglongpi": {"type": "float", "value": "-3.141592653589793"} +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/long-float.toml b/vendored/libtomlc99/BurntSushi_input/valid/long-float.toml new file mode 100644 index 0000000..9558ae4 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/long-float.toml @@ -0,0 +1,2 @@ +longpi = 3.141592653589793 +neglongpi = -3.141592653589793 diff --git a/vendored/libtomlc99/BurntSushi_input/valid/long-integer.json b/vendored/libtomlc99/BurntSushi_input/valid/long-integer.json new file mode 100644 index 0000000..16c331e --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/long-integer.json @@ -0,0 +1,4 @@ +{ + "answer": {"type": "integer", "value": "9223372036854775807"}, + "neganswer": {"type": "integer", "value": "-9223372036854775808"} +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/long-integer.toml b/vendored/libtomlc99/BurntSushi_input/valid/long-integer.toml new file mode 100644 index 0000000..424a13a --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/long-integer.toml @@ -0,0 +1,2 @@ +answer = 9223372036854775807 +neganswer = -9223372036854775808 diff --git a/vendored/libtomlc99/BurntSushi_input/valid/multiline-string.json b/vendored/libtomlc99/BurntSushi_input/valid/multiline-string.json new file mode 100644 index 0000000..075bf50 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/multiline-string.json @@ -0,0 +1,30 @@ +{ + "multiline_empty_one": { + "type": "string", + "value": "" + }, + "multiline_empty_two": { + "type": "string", + "value": "" + }, + "multiline_empty_three": { + "type": "string", + "value": "" + }, + "multiline_empty_four": { + "type": "string", + "value": "" + }, + "equivalent_one": { + "type": "string", + "value": "The quick brown fox jumps over the lazy dog." + }, + "equivalent_two": { + "type": "string", + "value": "The quick brown fox jumps over the lazy dog." + }, + "equivalent_three": { + "type": "string", + "value": "The quick brown fox jumps over the lazy dog." + } +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/multiline-string.toml b/vendored/libtomlc99/BurntSushi_input/valid/multiline-string.toml new file mode 100644 index 0000000..15b1143 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/multiline-string.toml @@ -0,0 +1,23 @@ +multiline_empty_one = """""" +multiline_empty_two = """ +""" +multiline_empty_three = """\ + """ +multiline_empty_four = """\ + \ + \ + """ + +equivalent_one = "The quick brown fox jumps over the lazy dog." +equivalent_two = """ +The quick brown \ + + + fox jumps over \ + the lazy dog.""" + +equivalent_three = """\ + The quick brown \ + fox jumps over \ + the lazy dog.\ + """ diff --git a/vendored/libtomlc99/BurntSushi_input/valid/raw-multiline-string.json b/vendored/libtomlc99/BurntSushi_input/valid/raw-multiline-string.json new file mode 100644 index 0000000..b43cce5 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/raw-multiline-string.json @@ -0,0 +1,14 @@ +{ + "oneline": { + "type": "string", + "value": "This string has a ' quote character." + }, + "firstnl": { + "type": "string", + "value": "This string has a ' quote character." + }, + "multiline": { + "type": "string", + "value": "This string\nhas ' a quote character\nand more than\none newline\nin it." + } +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/raw-multiline-string.toml b/vendored/libtomlc99/BurntSushi_input/valid/raw-multiline-string.toml new file mode 100644 index 0000000..8094c03 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/raw-multiline-string.toml @@ -0,0 +1,9 @@ +oneline = '''This string has a ' quote character.''' +firstnl = ''' +This string has a ' quote character.''' +multiline = ''' +This string +has ' a quote character +and more than +one newline +in it.''' diff --git a/vendored/libtomlc99/BurntSushi_input/valid/raw-string.json b/vendored/libtomlc99/BurntSushi_input/valid/raw-string.json new file mode 100644 index 0000000..693ab9b --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/raw-string.json @@ -0,0 +1,30 @@ +{ + "backspace": { + "type": "string", + "value": "This string has a \\b backspace character." + }, + "tab": { + "type": "string", + "value": "This string has a \\t tab character." + }, + "newline": { + "type": "string", + "value": "This string has a \\n new line character." + }, + "formfeed": { + "type": "string", + "value": "This string has a \\f form feed character." + }, + "carriage": { + "type": "string", + "value": "This string has a \\r carriage return character." + }, + "slash": { + "type": "string", + "value": "This string has a \\/ slash character." + }, + "backslash": { + "type": "string", + "value": "This string has a \\\\ backslash character." + } +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/raw-string.toml b/vendored/libtomlc99/BurntSushi_input/valid/raw-string.toml new file mode 100644 index 0000000..92acd25 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/raw-string.toml @@ -0,0 +1,7 @@ +backspace = 'This string has a \b backspace character.' +tab = 'This string has a \t tab character.' +newline = 'This string has a \n new line character.' +formfeed = 'This string has a \f form feed character.' +carriage = 'This string has a \r carriage return character.' +slash = 'This string has a \/ slash character.' +backslash = 'This string has a \\ backslash character.' diff --git a/vendored/libtomlc99/BurntSushi_input/valid/string-empty.json b/vendored/libtomlc99/BurntSushi_input/valid/string-empty.json new file mode 100644 index 0000000..6c26d69 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/string-empty.json @@ -0,0 +1,6 @@ +{ + "answer": { + "type": "string", + "value": "" + } +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/string-empty.toml b/vendored/libtomlc99/BurntSushi_input/valid/string-empty.toml new file mode 100644 index 0000000..e37e681 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/string-empty.toml @@ -0,0 +1 @@ +answer = "" diff --git a/vendored/libtomlc99/BurntSushi_input/valid/string-escapes.json b/vendored/libtomlc99/BurntSushi_input/valid/string-escapes.json new file mode 100644 index 0000000..98e2c82 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/string-escapes.json @@ -0,0 +1,46 @@ +{ + "backspace": { + "type": "string", + "value": "This string has a \u0008 backspace character." + }, + "tab": { + "type": "string", + "value": "This string has a \u0009 tab character." + }, + "newline": { + "type": "string", + "value": "This string has a \u000A new line character." + }, + "formfeed": { + "type": "string", + "value": "This string has a \u000C form feed character." + }, + "carriage": { + "type": "string", + "value": "This string has a \u000D carriage return character." + }, + "quote": { + "type": "string", + "value": "This string has a \u0022 quote character." + }, + "backslash": { + "type": "string", + "value": "This string has a \u005C backslash character." + }, + "notunicode1": { + "type": "string", + "value": "This string does not have a unicode \\u escape." + }, + "notunicode2": { + "type": "string", + "value": "This string does not have a unicode \u005Cu escape." + }, + "notunicode3": { + "type": "string", + "value": "This string does not have a unicode \\u0075 escape." + }, + "notunicode4": { + "type": "string", + "value": "This string does not have a unicode \\\u0075 escape." + } +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/string-escapes.toml b/vendored/libtomlc99/BurntSushi_input/valid/string-escapes.toml new file mode 100644 index 0000000..6d554e4 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/string-escapes.toml @@ -0,0 +1,11 @@ +backspace = "This string has a \b backspace character." +tab = "This string has a \t tab character." +newline = "This string has a \n new line character." +formfeed = "This string has a \f form feed character." +carriage = "This string has a \r carriage return character." +quote = "This string has a \" quote character." +backslash = "This string has a \\ backslash character." +notunicode1 = "This string does not have a unicode \\u escape." +notunicode2 = "This string does not have a unicode \u005Cu escape." +notunicode3 = "This string does not have a unicode \\u0075 escape." +notunicode4 = "This string does not have a unicode \\\u0075 escape." diff --git a/vendored/libtomlc99/BurntSushi_input/valid/string-simple.json b/vendored/libtomlc99/BurntSushi_input/valid/string-simple.json new file mode 100644 index 0000000..2e05f99 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/string-simple.json @@ -0,0 +1,6 @@ +{ + "answer": { + "type": "string", + "value": "You are not drinking enough whisky." + } +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/string-simple.toml b/vendored/libtomlc99/BurntSushi_input/valid/string-simple.toml new file mode 100644 index 0000000..e17ade6 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/string-simple.toml @@ -0,0 +1 @@ +answer = "You are not drinking enough whisky." diff --git a/vendored/libtomlc99/BurntSushi_input/valid/string-with-pound.json b/vendored/libtomlc99/BurntSushi_input/valid/string-with-pound.json new file mode 100644 index 0000000..33cdc9c --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/string-with-pound.json @@ -0,0 +1,7 @@ +{ + "pound": {"type": "string", "value": "We see no # comments here."}, + "poundcomment": { + "type": "string", + "value": "But there are # some comments here." + } +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/string-with-pound.toml b/vendored/libtomlc99/BurntSushi_input/valid/string-with-pound.toml new file mode 100644 index 0000000..5fd8746 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/string-with-pound.toml @@ -0,0 +1,2 @@ +pound = "We see no # comments here." +poundcomment = "But there are # some comments here." # Did I # mess you up? diff --git a/vendored/libtomlc99/BurntSushi_input/valid/table-array-implicit.json b/vendored/libtomlc99/BurntSushi_input/valid/table-array-implicit.json new file mode 100644 index 0000000..32e4640 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/table-array-implicit.json @@ -0,0 +1,7 @@ +{ + "albums": { + "songs": [ + {"name": {"type": "string", "value": "Glory Days"}} + ] + } +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/table-array-implicit.toml b/vendored/libtomlc99/BurntSushi_input/valid/table-array-implicit.toml new file mode 100644 index 0000000..3157ac9 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/table-array-implicit.toml @@ -0,0 +1,2 @@ +[[albums.songs]] +name = "Glory Days" diff --git a/vendored/libtomlc99/BurntSushi_input/valid/table-array-many.json b/vendored/libtomlc99/BurntSushi_input/valid/table-array-many.json new file mode 100644 index 0000000..84df2da --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/table-array-many.json @@ -0,0 +1,16 @@ +{ + "people": [ + { + "first_name": {"type": "string", "value": "Bruce"}, + "last_name": {"type": "string", "value": "Springsteen"} + }, + { + "first_name": {"type": "string", "value": "Eric"}, + "last_name": {"type": "string", "value": "Clapton"} + }, + { + "first_name": {"type": "string", "value": "Bob"}, + "last_name": {"type": "string", "value": "Seger"} + } + ] +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/table-array-many.toml b/vendored/libtomlc99/BurntSushi_input/valid/table-array-many.toml new file mode 100644 index 0000000..46062be --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/table-array-many.toml @@ -0,0 +1,11 @@ +[[people]] +first_name = "Bruce" +last_name = "Springsteen" + +[[people]] +first_name = "Eric" +last_name = "Clapton" + +[[people]] +first_name = "Bob" +last_name = "Seger" diff --git a/vendored/libtomlc99/BurntSushi_input/valid/table-array-nest.json b/vendored/libtomlc99/BurntSushi_input/valid/table-array-nest.json new file mode 100644 index 0000000..c117afa --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/table-array-nest.json @@ -0,0 +1,18 @@ +{ + "albums": [ + { + "name": {"type": "string", "value": "Born to Run"}, + "songs": [ + {"name": {"type": "string", "value": "Jungleland"}}, + {"name": {"type": "string", "value": "Meeting Across the River"}} + ] + }, + { + "name": {"type": "string", "value": "Born in the USA"}, + "songs": [ + {"name": {"type": "string", "value": "Glory Days"}}, + {"name": {"type": "string", "value": "Dancing in the Dark"}} + ] + } + ] +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/table-array-nest.toml b/vendored/libtomlc99/BurntSushi_input/valid/table-array-nest.toml new file mode 100644 index 0000000..d659a3d --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/table-array-nest.toml @@ -0,0 +1,17 @@ +[[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" diff --git a/vendored/libtomlc99/BurntSushi_input/valid/table-array-one.json b/vendored/libtomlc99/BurntSushi_input/valid/table-array-one.json new file mode 100644 index 0000000..d75faae --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/table-array-one.json @@ -0,0 +1,8 @@ +{ + "people": [ + { + "first_name": {"type": "string", "value": "Bruce"}, + "last_name": {"type": "string", "value": "Springsteen"} + } + ] +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/table-array-one.toml b/vendored/libtomlc99/BurntSushi_input/valid/table-array-one.toml new file mode 100644 index 0000000..cd7e1b6 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/table-array-one.toml @@ -0,0 +1,3 @@ +[[people]] +first_name = "Bruce" +last_name = "Springsteen" diff --git a/vendored/libtomlc99/BurntSushi_input/valid/table-empty.json b/vendored/libtomlc99/BurntSushi_input/valid/table-empty.json new file mode 100644 index 0000000..6f3873a --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/table-empty.json @@ -0,0 +1,3 @@ +{ + "a": {} +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/table-empty.toml b/vendored/libtomlc99/BurntSushi_input/valid/table-empty.toml new file mode 100644 index 0000000..8bb6a0a --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/table-empty.toml @@ -0,0 +1 @@ +[a] diff --git a/vendored/libtomlc99/BurntSushi_input/valid/table-sub-empty.json b/vendored/libtomlc99/BurntSushi_input/valid/table-sub-empty.json new file mode 100644 index 0000000..9787770 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/table-sub-empty.json @@ -0,0 +1,3 @@ +{ + "a": { "b": {} } +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/table-sub-empty.toml b/vendored/libtomlc99/BurntSushi_input/valid/table-sub-empty.toml new file mode 100644 index 0000000..70b7fe1 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/table-sub-empty.toml @@ -0,0 +1,2 @@ +[a] +[a.b] diff --git a/vendored/libtomlc99/BurntSushi_input/valid/table-whitespace.json b/vendored/libtomlc99/BurntSushi_input/valid/table-whitespace.json new file mode 100644 index 0000000..3a73ec8 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/table-whitespace.json @@ -0,0 +1,3 @@ +{ + "valid key": {} +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/table-whitespace.toml b/vendored/libtomlc99/BurntSushi_input/valid/table-whitespace.toml new file mode 100644 index 0000000..daf881d --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/table-whitespace.toml @@ -0,0 +1 @@ +["valid key"] diff --git a/vendored/libtomlc99/BurntSushi_input/valid/table-with-pound.json b/vendored/libtomlc99/BurntSushi_input/valid/table-with-pound.json new file mode 100644 index 0000000..5e594e4 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/table-with-pound.json @@ -0,0 +1,5 @@ +{ + "key#group": { + "answer": {"type": "integer", "value": "42"} + } +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/table-with-pound.toml b/vendored/libtomlc99/BurntSushi_input/valid/table-with-pound.toml new file mode 100644 index 0000000..33f2c4f --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/table-with-pound.toml @@ -0,0 +1,2 @@ +["key#group"] +answer = 42 diff --git a/vendored/libtomlc99/BurntSushi_input/valid/unicode-escape.json b/vendored/libtomlc99/BurntSushi_input/valid/unicode-escape.json new file mode 100644 index 0000000..216f8f7 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/unicode-escape.json @@ -0,0 +1,4 @@ +{ + "answer4": {"type": "string", "value": "\u03B4"}, + "answer8": {"type": "string", "value": "\u03B4"} +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/unicode-escape.toml b/vendored/libtomlc99/BurntSushi_input/valid/unicode-escape.toml new file mode 100644 index 0000000..82faecb --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/unicode-escape.toml @@ -0,0 +1,2 @@ +answer4 = "\u03B4" +answer8 = "\U000003B4" diff --git a/vendored/libtomlc99/BurntSushi_input/valid/unicode-literal.json b/vendored/libtomlc99/BurntSushi_input/valid/unicode-literal.json new file mode 100644 index 0000000..00aa2f8 --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/unicode-literal.json @@ -0,0 +1,3 @@ +{ + "answer": {"type": "string", "value": "δ"} +} diff --git a/vendored/libtomlc99/BurntSushi_input/valid/unicode-literal.toml b/vendored/libtomlc99/BurntSushi_input/valid/unicode-literal.toml new file mode 100644 index 0000000..c65723c --- /dev/null +++ b/vendored/libtomlc99/BurntSushi_input/valid/unicode-literal.toml @@ -0,0 +1 @@ +answer = "δ" diff --git a/vendored/libtomlc99/LICENSE b/vendored/libtomlc99/LICENSE new file mode 100644 index 0000000..a3292b1 --- /dev/null +++ b/vendored/libtomlc99/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2017 CK Tan +https://github.com/cktan/tomlc99 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendored/libtomlc99/Makefile.am b/vendored/libtomlc99/Makefile.am new file mode 100644 index 0000000..7edcca0 --- /dev/null +++ b/vendored/libtomlc99/Makefile.am @@ -0,0 +1,132 @@ +AM_CFLAGS = \ + -I$(top_srcdir) \ + $(WARNING_CFLAGS) \ + -Wno-unused-parameter \ + $(CODE_COVERAGE_CFLAGS) + +AM_LDFLAGS = \ + $(CODE_COVERAGE_LIBS) + +AM_CPPFLAGS = \ + $(CODE_COVERAGE_CPPFLAGS) + +EXTRA_DIST = \ + LICENSE \ + README.md + +noinst_LTLIBRARIES = libtomlc99.la + +libtomlc99_la_SOURCES = \ + toml.c \ + toml.h + +TESTS = test_toml.t + +check_PROGRAMS = \ + toml_cat \ + toml_json \ + $(TESTS) + +TEST_EXTENSIONS = .t +T_LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) \ + $(top_srcdir)/config/tap-driver.sh + +test_cppflags = \ + -DTEST_GOOD_INPUT=\"$(srcdir)/BurntSushi_input/valid\" \ + -DTEST_BAD_INPUT=\"$(srcdir)/BurntSushi_input/invalid\" \ + $(AM_CPPFLAGS) + +test_ldadd = \ + $(builddir)/libtomlc99.la \ + $(top_builddir)/vendored/libtap/libtap.la + +toml_cat_SOURCES = toml_cat.c +toml_cat_LDADD = $(test_ldadd) +toml_cat_CPPFLAGS = $(test_cppflags) + +toml_json_SOURCES = toml_json.c +toml_json_LDADD = $(test_ldadd) +toml_json_CPPFLAGS = $(test_cppflags) + +test_toml_t_SOURCES = test/toml.c +test_toml_t_LDADD = $(test_ldadd) +test_toml_t_CPPFLAGS = $(test_cppflags) + +EXTRA_DIST += \ + BurntSushi_input/invalid/array-mixed-types-arrays-and-ints.toml \ + BurntSushi_input/invalid/array-mixed-types-ints-and-floats.toml \ + BurntSushi_input/invalid/array-mixed-types-strings-and-ints.toml \ + BurntSushi_input/invalid/datetime-malformed-no-leads.toml \ + BurntSushi_input/invalid/datetime-malformed-no-secs.toml \ + BurntSushi_input/invalid/datetime-malformed-no-t.toml \ + BurntSushi_input/invalid/datetime-malformed-with-milli.toml \ + BurntSushi_input/invalid/duplicate-keys.toml \ + BurntSushi_input/invalid/duplicate-key-table.toml \ + BurntSushi_input/invalid/duplicate-tables.toml \ + BurntSushi_input/invalid/empty-implicit-table.toml \ + BurntSushi_input/invalid/empty-table.toml \ + BurntSushi_input/invalid/float-no-leading-zero.toml \ + BurntSushi_input/invalid/float-no-trailing-digits.toml \ + BurntSushi_input/invalid/key-empty.toml \ + BurntSushi_input/invalid/key-hash.toml \ + BurntSushi_input/invalid/key-newline.toml \ + BurntSushi_input/invalid/key-open-bracket.toml \ + BurntSushi_input/invalid/key-single-open-bracket.toml \ + BurntSushi_input/invalid/key-space.toml \ + BurntSushi_input/invalid/key-start-bracket.toml \ + BurntSushi_input/invalid/key-two-equals.toml \ + BurntSushi_input/invalid/string-bad-byte-escape.toml \ + BurntSushi_input/invalid/string-bad-escape.toml \ + BurntSushi_input/invalid/string-byte-escapes.toml \ + BurntSushi_input/invalid/string-no-close.toml \ + BurntSushi_input/invalid/table-array-implicit.toml \ + BurntSushi_input/invalid/table-array-malformed-bracket.toml \ + BurntSushi_input/invalid/table-array-malformed-empty.toml \ + BurntSushi_input/invalid/table-empty.toml \ + BurntSushi_input/invalid/table-nested-brackets-close.toml \ + BurntSushi_input/invalid/table-nested-brackets-open.toml \ + BurntSushi_input/invalid/table-whitespace.toml \ + BurntSushi_input/invalid/table-with-pound.toml \ + BurntSushi_input/invalid/text-after-array-entries.toml \ + BurntSushi_input/invalid/text-after-integer.toml \ + BurntSushi_input/invalid/text-after-string.toml \ + BurntSushi_input/invalid/text-after-table.toml \ + BurntSushi_input/invalid/text-before-array-separator.toml \ + BurntSushi_input/invalid/text-in-array.toml \ + BurntSushi_input/valid/array-empty.toml \ + BurntSushi_input/valid/array-nospaces.toml \ + BurntSushi_input/valid/arrays-hetergeneous.toml \ + BurntSushi_input/valid/arrays-nested.toml \ + BurntSushi_input/valid/arrays.toml \ + BurntSushi_input/valid/bool.toml \ + BurntSushi_input/valid/comments-everywhere.toml \ + BurntSushi_input/valid/datetime.toml \ + BurntSushi_input/valid/empty.toml \ + BurntSushi_input/valid/example.toml \ + BurntSushi_input/valid/float.toml \ + BurntSushi_input/valid/implicit-and-explicit-after.toml \ + BurntSushi_input/valid/implicit-and-explicit-before.toml \ + BurntSushi_input/valid/implicit-groups.toml \ + BurntSushi_input/valid/integer.toml \ + BurntSushi_input/valid/key-equals-nospace.toml \ + BurntSushi_input/valid/key-space.toml \ + BurntSushi_input/valid/key-special-chars.toml \ + BurntSushi_input/valid/long-float.toml \ + BurntSushi_input/valid/long-integer.toml \ + BurntSushi_input/valid/multiline-string.toml \ + BurntSushi_input/valid/raw-multiline-string.toml \ + BurntSushi_input/valid/raw-string.toml \ + BurntSushi_input/valid/string-empty.toml \ + BurntSushi_input/valid/string-escapes.toml \ + BurntSushi_input/valid/string-simple.toml \ + BurntSushi_input/valid/string-with-pound.toml \ + BurntSushi_input/valid/table-array-implicit.toml \ + BurntSushi_input/valid/table-array-many.toml \ + BurntSushi_input/valid/table-array-nest.toml \ + BurntSushi_input/valid/table-array-one.toml \ + BurntSushi_input/valid/table-empty.toml \ + BurntSushi_input/valid/table-sub-empty.toml \ + BurntSushi_input/valid/table-whitespace.toml \ + BurntSushi_input/valid/table-with-pound.toml \ + BurntSushi_input/valid/unicode-escape.toml \ + BurntSushi_input/valid/unicode-literal.toml diff --git a/vendored/libtomlc99/README.md b/vendored/libtomlc99/README.md new file mode 100644 index 0000000..2ef0526 --- /dev/null +++ b/vendored/libtomlc99/README.md @@ -0,0 +1,95 @@ +# tomlc99 +TOML in c99; v0.5.0 compliant. + + +# Usage + +Please see the `toml.h` file for details. What follows is a simple example that +parses this config file: + +``` +[server] +    host = "www.example.com" + port = 80 +``` + +For each config param, the code first extracts a raw value and then +convert it to a string or integer depending on context. + +``` + + FILE* fp; + toml_table_t* conf; + toml_table_t* server; + const char* raw; + char* host; + int64_t port; + char errbuf[200]; + + /* open file and parse */ + if (0 == (fp = fopen(FNAME, "r"))) { + return handle_error(); + } + conf = toml_parse_file(fp, errbuf, sizeof(errbuf)); + fclose(fp); + if (0 == conf) { + return handle_error(); + } + + /* locate the [server] table */ + if (0 == (server = toml_table_in(conf, "server"))) { + return handle_error(); + } + + /* extract host config value */ + if (0 == (raw = toml_raw_in(server, "host"))) { + return handle_error(); + } + if (toml_rtos(raw, &host)) { + return handle_error(); + } + + /* extract port config value */ + if (0 == (raw = toml_raw_in(server, "port"))) { + return handle_error(); + } + if (toml_rtoi(raw, &port)) { + return handle_error(); + } + + /* done with conf */ + toml_free(conf); + + /* use host and port */ + do_work(host, port); + + /* clean up */ + free(host); +``` + + +# Building + +A normal *make* suffices. Alternately, you can also simply include the +`toml.c` and `toml.h` files in your project. + +# Testing + +To test against the standard test set provided by BurntSushi/toml-test: + +``` + % make + % cd test1 + % bash build.sh # do this once + % bash run.sh # this will run the test suite +``` + + +To test against the standard test set provided by iarna/toml: + +``` + % make + % cd test2 + % bash build.sh # do this once + % bash run.sh # this will run the test suite +``` diff --git a/vendored/libtomlc99/test/toml.c b/vendored/libtomlc99/test/toml.c new file mode 100644 index 0000000..b2a126c --- /dev/null +++ b/vendored/libtomlc99/test/toml.c @@ -0,0 +1,454 @@ +/************************************************************\ + * Copyright 2017 Lawrence Livermore National Security, LLC + * (c.f. AUTHORS, NOTICE.LLNS, COPYING) + * + * This file is part of the Flux resource manager framework. + * For details, see https://github.com/flux-framework. + * + * SPDX-License-Identifier: LGPL-3.0 +\************************************************************/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vendored/libtap/tap.h" +#include "toml.h" + +#define EX1 "\ +[server]\n\ + host = \"www.example.com\"\n\ + port = 80\n\ + verbose = false\n\ + timeout = 1.5E3\n\ +" + +bool validate_toml_table (toml_table_t *conf, char *errbuf, int errsize); +bool validate_toml_array (toml_array_t *array, char *errbuf, int errsize); +bool validate_toml_value (const char *raw, char *errbuf, int errsize); + +void parse_ex1 (void) +{ + char errbuf[255]; + toml_table_t *conf; + toml_table_t *server; + const char *raw; + char *host; + int64_t port; + int verbose; + double timeout; + int rc; + + conf = toml_parse (EX1, errbuf, sizeof (errbuf)); + ok (conf != NULL, + "ex1: parsed simple example"); + + server = toml_table_in (conf, "server"); + ok (server != NULL, + "ex1: located server table"); + + raw = toml_raw_in (server, "host"); + ok (raw != NULL, + "ex1: located host in server table"); + host = NULL; + rc = toml_rtos (raw, &host); + ok (rc == 0, + "ex1: extracted host string"); + is (host, "www.example.com", + "ex1: host string has expected value"); + + raw = toml_raw_in (server, "port"); + ok (raw != NULL, + "ex1: located port in server table"); + port = 0; + rc = toml_rtoi (raw, &port); + ok (rc == 0, + "ex1: extracted port int"); + ok (port == 80, + "ex1: port int has expected value"); + + raw = toml_raw_in (server, "verbose"); + ok (raw != NULL, + "ex1: located verbose in server table"); + verbose = 2; + rc = toml_rtob (raw, &verbose); + ok (rc == 0, + "ex1: extracted verbose boolean"); + ok (verbose == 0, + "ex1: verbose boolean has expected value"); + + raw = toml_raw_in (server, "timeout"); + ok (raw != NULL, + "ex1: located timeout in server table"); + timeout = 0; + rc = toml_rtod (raw, &timeout); + ok (rc == 0, + "ex1: extracted timeout double"); + ok (timeout == 1.5E3, + "ex1: timeout double has expected value"); + + toml_free (conf); + free (host); +} + +bool validate_toml_value (const char *raw, char *errbuf, int errsize) +{ + char *str; + int i; + int64_t i64; + double d; + struct toml_timestamp_t ts; + + if (toml_rtos (raw, &str) == 0) { + free (str); + return true; + } + else if (toml_rtob (raw, &i) == 0) + return true; + else if (toml_rtoi (raw, &i64) == 0) + return true; + else if (toml_rtod (raw, &d) == 0) + return true; + else if (toml_rtots (raw, &ts) == 0) + return true; + + snprintf (errbuf, errsize, "%s is an invalid value", raw); + return false; +} + +bool validate_toml_array (toml_array_t *array, char *errbuf, int errsize) +{ + int i; + const char *raw; + toml_array_t *arr; + toml_table_t *tab; + + switch (toml_array_kind (array)) { + case 'v': + for (i = 0; (raw = toml_raw_at (array, i)); i++) { + if (!validate_toml_value (raw, errbuf, errsize)) + return false; + } + break; + case 'a': + for (i = 0; (arr = toml_array_at (array, i)); i++) { + if (!validate_toml_array (arr, errbuf, errsize)) + return false; + } + break; + case 't': + for (i = 0; (tab = toml_table_at (array, i)); i++) { + if (!validate_toml_table (tab, errbuf, errsize)) + return false; + } + break; + } + return true; +} + +bool validate_toml_table (toml_table_t *conf, char *errbuf, int errsize) +{ + int i; + const char *key; + const char *raw; + toml_array_t *arr; + toml_table_t *tab; + + for (i = 0; (key = toml_key_in (conf, i)); i++) { + if ((raw = toml_raw_in (conf, key))) { // value + if (!validate_toml_value (raw, errbuf, errsize)) + return false; + } + else if ((arr = toml_array_in (conf, key))) { // array + if (!validate_toml_array (arr, errbuf, errsize)) + return false; + } + else if ((tab = toml_table_in (conf, key))) { // table + if (!validate_toml_table (tab, errbuf, errsize)) + return false; + } + else { + snprintf (errbuf, errsize, "key=%s is invalid", key); + return false; + } + } + return true; +} + +/* return true if file can be opened and parsing fails + */ +bool parse_bad_file (const char *path, char *errbuf, int errsize) +{ + FILE *fp; + toml_table_t *conf = NULL; + + if (!(fp = fopen (path, "r"))) { + snprintf (errbuf, errsize, "%s", strerror (errno)); + return false; + } + conf = toml_parse_file (fp, errbuf, errsize); + if (conf != NULL) { + if (validate_toml_table (conf, errbuf, errsize)) { + toml_free (conf); + fclose (fp); + snprintf (errbuf, errsize, "success"); + return false; + } + toml_free (conf); + } + fclose (fp); + return true; +} + +struct entry { + char *name; + char *reason; +}; + +const struct entry bad_input_blocklist[] = { + { NULL, NULL }, +}; + +bool matchtab (const char *name, const struct entry tab[], const char **reason) +{ + int i; + for (i = 0; tab[i].name != NULL; i++) + if (!strcmp (tab[i].name, name)) { + *reason = tab[i].reason; + return true; + } + return false; +} + +void parse_bad_input (void) +{ + char pattern[PATH_MAX]; + int flags = 0; + glob_t results; + unsigned i; + + snprintf (pattern, sizeof (pattern), "%s/*.toml", TEST_BAD_INPUT); + if (glob (pattern, flags, NULL, &results) != 0) + BAIL_OUT ("glob %s failed - test input not found", pattern); + diag ("%d files in %s", results.gl_pathc, TEST_BAD_INPUT); + + for (i = 0; i < results.gl_pathc; i++) { + char errbuf[255]; + char *name = basename (results.gl_pathv[i]); + const char *reason; + bool blocklist = matchtab (name, bad_input_blocklist, &reason); + + skip (blocklist, 1, "%s: %s", name, reason); + ok (parse_bad_file (results.gl_pathv[i], errbuf, 255) == true, + "%s: %s", name, errbuf); + end_skip; + } + + globfree (&results); +} + +/* return true if file can be opened and parsed + */ +bool parse_good_file (const char *path, char *errbuf, int errsize) +{ + FILE *fp; + toml_table_t *conf = NULL; + + + if (!(fp = fopen (path, "r"))) { + snprintf (errbuf, errsize, "%s", strerror (errno)); + return false; + } + conf = toml_parse_file (fp, errbuf, errsize); + if (conf == NULL) { + fclose (fp); + return false; + } + if (!validate_toml_table (conf, errbuf, errsize)) { + fclose (fp); + toml_free (conf); + return false; + } + toml_free (conf); + fclose (fp); + snprintf (errbuf, errsize, "success"); + return true; +} + +void parse_good_input (void) +{ + char pattern[PATH_MAX]; + int flags = 0; + glob_t results; + unsigned i; + + snprintf (pattern, sizeof (pattern), "%s/*.toml", TEST_GOOD_INPUT); + if (glob (pattern, flags, NULL, &results) != 0) + BAIL_OUT ("glob %s failed - test input not found", pattern); + diag ("%d files in %s", results.gl_pathc, TEST_GOOD_INPUT); + + for (i = 0; i < results.gl_pathc; i++) { + char errbuf[255]; + ok (parse_good_file (results.gl_pathv[i], errbuf, 255) == true, + "%s: %s", basename (results.gl_pathv[i]), errbuf); + } + globfree (&results); +} + +/* Recreate the TOML input from the tomlc99/test/extra directory. + */ +void parse_extra (void) +{ + const char *good[] = { + "x = [ {'a'= 1}, {'a'= 2} ]", // array_of_tables.toml + "x = [1,2,3]", // inline_array.toml + "x = {'a'= 1, 'b'= 2 }", // inline_table.toml + NULL, + }; + int i; + + for (i = 0; good[i] != NULL; i++) { + char e[200]; + toml_table_t *conf = toml_parse ((char *)good[i], e, sizeof (e)); + ok (conf != NULL, + "parsed extra %d: \"%s\"", i, good[i]); + if (!conf) + diag ("%s", e); + toml_free (conf); + } + +} + +void check_ucs_to_utf8 (void) +{ + char buf[6]; + int64_t code; + int errors; + + errors = 0; + for (code = 0xd800; code <= 0xdfff; code++) + if (toml_ucs_to_utf8 (code, buf) != -1) + errors++; + ok (errors == 0, + "ucs_to_utf8: UTF-16 surrogates are rejected"); + + errors = 0; + for (code = 0xfffe; code <= 0xffff; code++) + if (toml_ucs_to_utf8 (code, buf) != -1) + errors++; + ok (errors == 0, + "ucs_to_utf8: UCS non-characters are rejected"); + + ok (toml_ucs_to_utf8 (-42, buf) < 0, + "ucs_to_utf8: UCS negative code is rejected"); + + errors = 0; + for (code = 0; code <= 0x7f; code++) + if (toml_ucs_to_utf8 (code, buf) != 1 || buf[0] != code) + errors++; + ok (errors == 0, + "ucs_to_utf8: 1 byte codes convert directly to UTF8"); + + /* Check boundary values against results from: + * http://www.ltg.ed.ac.uk/~richard/utf-8.cgi + */ + + ok (toml_ucs_to_utf8 (0x80, buf) == 2 && !memcmp (buf, "\xc2\x80", 2), + "ucs_to_utf8: 0x80 converted to 2-char UTF8"); + ok (toml_ucs_to_utf8 (0x7ff, buf) == 2 && !memcmp (buf, "\xdf\xbf", 2), + "ucs_to_utf8: 0x7ff converted to 2-char UTF8"); + + ok (toml_ucs_to_utf8 (0x800, buf) == 3 && !memcmp (buf, "\xe0\xa0\x80", 3), + "ucs_to_utf8: 0x800 converted to 3-char UTF8"); + ok (toml_ucs_to_utf8 (0xfffd, buf) == 3 && !memcmp (buf, "\xef\xbf\xbd", 3), + "ucs_to_utf8: 0xfffd converted to 3-char UTF8"); + + ok (toml_ucs_to_utf8 (0x10000, buf) == 4 + && !memcmp (buf, "\xf0\x90\x80\x80", 4), + "ucs_to_utf8: 0x10000 converted to 4-char UTF8"); + ok (toml_ucs_to_utf8 (0x1fffff, buf) == 4 + && !memcmp (buf, "\xf7\xbf\xbf\xbf", 4), + "ucs_to_utf8: 0x1fffff converted to 4-char UTF8"); + + ok (toml_ucs_to_utf8 (0x200000, buf) == 5 + && !memcmp (buf, "\xf8\x88\x80\x80\x80", 5), + "ucs_to_utf8: 0x200000 converted to 5-char UTF8"); + ok (toml_ucs_to_utf8 (0x3ffffff, buf) == 5 + && !memcmp (buf, "\xfb\xbf\xbf\xbf\xbf", 5), + "ucs_to_utf8: 0x3ffffff converted to 5-char UTF8"); + + ok (toml_ucs_to_utf8 (0x4000000, buf) == 6 + && !memcmp (buf, "\xfc\x84\x80\x80\x80\x80", 6), + "ucs_to_utf8: 0x4000000 converted to 6-char UTF8"); + ok (toml_ucs_to_utf8 (0x7fffffff, buf) == 6 + && !memcmp (buf, "\xfd\xbf\xbf\xbf\xbf\xbf", 6), + "ucs_to_utf8: 0x7fffffff converted to 6-char UTF8"); +} + +void check_utf8_to_ucs (void) +{ + int64_t code; + + /* Reverse above values + */ + + ok (toml_utf8_to_ucs ("\x0", 1, &code) == 1 && code == 0, + "utf8_to_ucs: 0 converted from 1-char UTF8"); + ok (toml_utf8_to_ucs ("\x7f", 1, &code) == 1 && code == 0x7f, + "utf8_to_ucs: 0x7f converted from 1-char UTF8"); + + ok (toml_utf8_to_ucs ("\xc2\x80", 2, &code) == 2 && code == 0x80, + "utf8_to_ucs: 0x80 converted from 2-char UTF8"); + ok (toml_utf8_to_ucs ("\xdf\xbf", 2, &code) == 2 && code == 0x7ff, + "utf8_to_ucs: 0x7ff converted from 2-char UTF8"); + + ok (toml_utf8_to_ucs ("\xe0\xa0\x80", 3, &code) == 3 && code == 0x800, + "utf8_to_ucs: 0x800 converted from 3-char UTF8"); + ok (toml_utf8_to_ucs ("\xef\xbf\xbd", 3, &code) == 3 && code == 0xfffd, + "utf8_to_ucs: 0xfffd converted from 3-char UTF8"); + + ok (toml_utf8_to_ucs ("\xf0\x90\x80\x80", 4, &code) == 4 && code == 0x10000, + "utf8_to_ucs: 0x10000 converted from 4-char UTF8"); + ok (toml_utf8_to_ucs ("\xf7\xbf\xbf\xbf", 4, &code) == 4 && code == 0x1fffff, + "utf8_to_ucs: 0x1fffff converted from 4-char UTF8"); + + ok (toml_utf8_to_ucs ("\xf8\x88\x80\x80\x80", 5, &code) == 5 && code == 0x200000, + "utf8_to_ucs: 0x200000 converted from 5-char UTF8"); + ok (toml_utf8_to_ucs ("\xfb\xbf\xbf\xbf\xbf", 5, &code) == 5 && code == 0x3ffffff, + "utf8_to_ucs: 0x3ffffff converted from 5-char UTF8"); + + ok (toml_utf8_to_ucs ("\xfc\x84\x80\x80\x80\x80", 6, &code) == 6 && code == 0x4000000, + "utf8_to_ucs: 0x4000000 converted from 6-char UTF8"); + ok (toml_utf8_to_ucs ("\xfd\xbf\xbf\xbf\xbf\xbf", 6, &code) == 6 && code == 0x7fffffff, + "utf8_to_ucs: 0x7fffffff converted from 6-char UTF8"); +} + +int main (int argc, char *argv[]) +{ + plan (NO_PLAN); + + parse_ex1 (); + + parse_good_input (); + parse_bad_input (); + + parse_extra (); + + check_ucs_to_utf8 (); + check_utf8_to_ucs (); + + done_testing (); +} + +/* + * vi: ts=4 sw=4 expandtab + */ diff --git a/vendored/libtomlc99/toml.c b/vendored/libtomlc99/toml.c new file mode 100644 index 0000000..7a47bfc --- /dev/null +++ b/vendored/libtomlc99/toml.c @@ -0,0 +1,2142 @@ +/* + + MIT License + + Copyright (c) 2017 - 2019 CK Tan + https://github.com/cktan/tomlc99 + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +*/ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include "toml.h" + + +static void* (*ppmalloc)(size_t) = malloc; +static void (*ppfree)(void*) = free; +static void* (*ppcalloc)(size_t, size_t) = calloc; +static void* (*pprealloc)(void*, size_t) = realloc; + +void toml_set_memutil(void* (*xxmalloc)(size_t), + void (*xxfree)(void*), + void* (*xxcalloc)(size_t, size_t), + void* (*xxrealloc)(void*, size_t)) +{ + ppmalloc = xxmalloc; + ppfree = xxfree; + ppcalloc = xxcalloc; + pprealloc = xxrealloc; +} + + +#define MALLOC(a) ppmalloc(a) +#define FREE(a) ppfree(a) +#define CALLOC(a,b) ppcalloc(a,b) +#define REALLOC(a,b) pprealloc(a,b) + +char* STRDUP(const char* s) +{ + int len = strlen(s); + char* p = MALLOC(len+1); + if (p) { + memcpy(p, s, len); + p[len] = 0; + } + return p; +} + +char* STRNDUP(const char* s, size_t n) +{ + size_t len = strnlen(s, n); + char* p = MALLOC(len+1); + if (p) { + memcpy(p, s, len); + p[len] = 0; + } + return p; +} + + + +/** + * Convert a char in utf8 into UCS, and store it in *ret. + * Return #bytes consumed or -1 on failure. + */ +int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret) +{ + const unsigned char* buf = (const unsigned char*) orig; + unsigned i = *buf++; + int64_t v; + + /* 0x00000000 - 0x0000007F: + 0xxxxxxx + */ + if (0 == (i >> 7)) { + if (len < 1) return -1; + v = i; + return *ret = v, 1; + } + /* 0x00000080 - 0x000007FF: + 110xxxxx 10xxxxxx + */ + if (0x6 == (i >> 5)) { + if (len < 2) return -1; + v = i & 0x1f; + for (int j = 0; j < 1; j++) { + i = *buf++; + if (0x2 != (i >> 6)) return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char*) buf - orig; + } + + /* 0x00000800 - 0x0000FFFF: + 1110xxxx 10xxxxxx 10xxxxxx + */ + if (0xE == (i >> 4)) { + if (len < 3) return -1; + v = i & 0x0F; + for (int j = 0; j < 2; j++) { + i = *buf++; + if (0x2 != (i >> 6)) return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char*) buf - orig; + } + + /* 0x00010000 - 0x001FFFFF: + 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (0x1E == (i >> 3)) { + if (len < 4) return -1; + v = i & 0x07; + for (int j = 0; j < 3; j++) { + i = *buf++; + if (0x2 != (i >> 6)) return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char*) buf - orig; + } + + /* 0x00200000 - 0x03FFFFFF: + 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (0x3E == (i >> 2)) { + if (len < 5) return -1; + v = i & 0x03; + for (int j = 0; j < 4; j++) { + i = *buf++; + if (0x2 != (i >> 6)) return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char*) buf - orig; + } + + /* 0x04000000 - 0x7FFFFFFF: + 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (0x7e == (i >> 1)) { + if (len < 6) return -1; + v = i & 0x01; + for (int j = 0; j < 5; j++) { + i = *buf++; + if (0x2 != (i >> 6)) return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char*) buf - orig; + } + return -1; +} + + +/** + * Convert a UCS char to utf8 code, and return it in buf. + * Return #bytes used in buf to encode the char, or + * -1 on error. + */ +int toml_ucs_to_utf8(int64_t code, char buf[6]) +{ + /* http://stackoverflow.com/questions/6240055/manually-converting-unicode-codepoints-into-utf-8-and-utf-16 */ + /* The UCS code values 0xd800–0xdfff (UTF-16 surrogates) as well + * as 0xfffe and 0xffff (UCS noncharacters) should not appear in + * conforming UTF-8 streams. + */ + if (0xd800 <= code && code <= 0xdfff) return -1; + if (0xfffe <= code && code <= 0xffff) return -1; + + /* 0x00000000 - 0x0000007F: + 0xxxxxxx + */ + if (code < 0) return -1; + if (code <= 0x7F) { + buf[0] = (unsigned char) code; + return 1; + } + + /* 0x00000080 - 0x000007FF: + 110xxxxx 10xxxxxx + */ + if (code <= 0x000007FF) { + buf[0] = 0xc0 | (code >> 6); + buf[1] = 0x80 | (code & 0x3f); + return 2; + } + + /* 0x00000800 - 0x0000FFFF: + 1110xxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x0000FFFF) { + buf[0] = 0xe0 | (code >> 12); + buf[1] = 0x80 | ((code >> 6) & 0x3f); + buf[2] = 0x80 | (code & 0x3f); + return 3; + } + + /* 0x00010000 - 0x001FFFFF: + 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x001FFFFF) { + buf[0] = 0xf0 | (code >> 18); + buf[1] = 0x80 | ((code >> 12) & 0x3f); + buf[2] = 0x80 | ((code >> 6) & 0x3f); + buf[3] = 0x80 | (code & 0x3f); + return 4; + } + + /* 0x00200000 - 0x03FFFFFF: + 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x03FFFFFF) { + buf[0] = 0xf8 | (code >> 24); + buf[1] = 0x80 | ((code >> 18) & 0x3f); + buf[2] = 0x80 | ((code >> 12) & 0x3f); + buf[3] = 0x80 | ((code >> 6) & 0x3f); + buf[4] = 0x80 | (code & 0x3f); + return 5; + } + + /* 0x04000000 - 0x7FFFFFFF: + 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x7FFFFFFF) { + buf[0] = 0xfc | (code >> 30); + buf[1] = 0x80 | ((code >> 24) & 0x3f); + buf[2] = 0x80 | ((code >> 18) & 0x3f); + buf[3] = 0x80 | ((code >> 12) & 0x3f); + buf[4] = 0x80 | ((code >> 6) & 0x3f); + buf[5] = 0x80 | (code & 0x3f); + return 6; + } + + return -1; +} + +/* + * TOML has 3 data structures: value, array, table. + * Each of them can have identification key. + */ +typedef struct toml_keyval_t toml_keyval_t; +struct toml_keyval_t { + const char* key; /* key to this value */ + const char* val; /* the raw value */ +}; + + +struct toml_array_t { + const char* key; /* key to this array */ + int kind; /* element kind: 'v'alue, 'a'rray, or 't'able */ + int type; /* for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime, 'D'ate, 'T'imestamp */ + + int nelem; /* number of elements */ + union { + char** val; + toml_array_t** arr; + toml_table_t** tab; + } u; +}; + + +struct toml_table_t { + const char* key; /* key to this table */ + int implicit; /* table was created implicitly */ + + /* key-values in the table */ + int nkval; + toml_keyval_t** kval; + + /* arrays in the table */ + int narr; + toml_array_t** arr; + + /* tables in the table */ + int ntab; + toml_table_t** tab; +}; + + +static inline void xfree(const void* x) { if (x) FREE((void*)x); } + + +enum tokentype_t { + INVALID, + DOT, + COMMA, + EQUAL, + LBRACE, + RBRACE, + NEWLINE, + LBRACKET, + RBRACKET, + STRING, +}; +typedef enum tokentype_t tokentype_t; + +typedef struct token_t token_t; +struct token_t { + tokentype_t tok; + int lineno; + char* ptr; /* points into context->start */ + int len; + int eof; +}; + + +typedef struct context_t context_t; +struct context_t { + char* start; + char* stop; + char* errbuf; + int errbufsz; + jmp_buf jmp; + + token_t tok; + toml_table_t* root; + toml_table_t* curtab; + + struct { + int top; + char* key[10]; + token_t tok[10]; + } tpath; + +}; + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) +#define FLINE __FILE__ ":" TOSTRING(__LINE__) + +static tokentype_t next_token(context_t* ctx, int dotisspecial); + +/* error routines. All these functions longjmp to ctx->jmp */ +static int e_outofmemory(context_t* ctx, const char* fline) +{ + snprintf(ctx->errbuf, ctx->errbufsz, "ERROR: out of memory (%s)", fline); + longjmp(ctx->jmp, 1); + return -1; +} + + +static int e_internal_error(context_t* ctx, const char* fline) +{ + snprintf(ctx->errbuf, ctx->errbufsz, "internal error (%s)", fline); + longjmp(ctx->jmp, 1); + return -1; +} + +static int e_syntax_error(context_t* ctx, int lineno, const char* msg) +{ + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, msg); + longjmp(ctx->jmp, 1); + return -1; +} + +static int e_bad_key_error(context_t* ctx, int lineno) +{ + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: bad key", lineno); + longjmp(ctx->jmp, 1); + return -1; +} + +/* + static int e_noimpl(context_t* ctx, const char* feature) + { + snprintf(ctx->errbuf, ctx->errbufsz, "not implemented: %s", feature); + longjmp(ctx->jmp, 1); + return -1; + } +*/ + +static int e_key_exists_error(context_t* ctx, int lineno, const char* key) +{ + snprintf(ctx->errbuf, ctx->errbufsz, + "line %d: key %s exists", lineno, key); + return -1; +} + +static char* norm_lit_str(const char* src, int srclen, + int multiline, + char* errbuf, int errbufsz) +{ + char* dst = 0; /* will write to dst[] and return it */ + int max = 0; /* max size of dst[] */ + int off = 0; /* cur offset in dst[] */ + const char* sp = src; + const char* sq = src + srclen; + int ch; + + /* scan forward on src */ + for (;;) { + if (off >= max - 10) { /* have some slack for misc stuff */ + char* x = REALLOC(dst, max += 50); + if (!x) { + xfree(dst); + snprintf(errbuf, errbufsz, "out of memory"); + return 0; + } + dst = x; + } + + /* finished? */ + if (sp >= sq) break; + + ch = *sp++; + /* control characters other than tab is not allowed */ + if ((0 <= ch && ch <= 0x08) + || (0x0a <= ch && ch <= 0x1f) + || (ch == 0x7f)) { + if (! (multiline && (ch == '\r' || ch == '\n'))) { + xfree(dst); + snprintf(errbuf, errbufsz, "invalid char U+%04x", ch); + return 0; + } + } + + // a plain copy suffice + dst[off++] = ch; + } + + dst[off++] = 0; + return dst; +} + + + + +/* + * Convert src to raw unescaped utf-8 string. + * Returns NULL if error with errmsg in errbuf. + */ +static char* norm_basic_str(const char* src, int srclen, + int multiline, + char* errbuf, int errbufsz) +{ + char* dst = 0; /* will write to dst[] and return it */ + int max = 0; /* max size of dst[] */ + int off = 0; /* cur offset in dst[] */ + const char* sp = src; + const char* sq = src + srclen; + int ch; + + /* scan forward on src */ + for (;;) { + if (off >= max - 10) { /* have some slack for misc stuff */ + char* x = REALLOC(dst, max += 50); + if (!x) { + xfree(dst); + snprintf(errbuf, errbufsz, "out of memory"); + return 0; + } + dst = x; + } + + /* finished? */ + if (sp >= sq) break; + + ch = *sp++; + if (ch != '\\') { + /* these chars must be escaped: U+0000 to U+0008, U+000A to U+001F, U+007F */ + if ((0 <= ch && ch <= 0x08) + || (0x0a <= ch && ch <= 0x1f) + || (ch == 0x7f)) { + if (! (multiline && (ch == '\r' || ch == '\n'))) { + xfree(dst); + snprintf(errbuf, errbufsz, "invalid char U+%04x", ch); + return 0; + } + } + + // a plain copy suffice + dst[off++] = ch; + continue; + } + + /* ch was backslash. we expect the escape char. */ + if (sp >= sq) { + snprintf(errbuf, errbufsz, "last backslash is invalid"); + xfree(dst); + return 0; + } + + /* for multi-line, we want to kill line-ending-backslash ... */ + if (multiline) { + + // if there is only whitespace after the backslash ... + if (sp[strspn(sp, " \t\r")] == '\n') { + /* skip all the following whitespaces */ + sp += strspn(sp, " \t\r\n"); + continue; + } + } + + /* get the escaped char */ + ch = *sp++; + switch (ch) { + case 'u': case 'U': + { + int64_t ucs = 0; + int nhex = (ch == 'u' ? 4 : 8); + for (int i = 0; i < nhex; i++) { + if (sp >= sq) { + snprintf(errbuf, errbufsz, "\\%c expects %d hex chars", ch, nhex); + xfree(dst); + return 0; + } + ch = *sp++; + int v = ('0' <= ch && ch <= '9') + ? ch - '0' + : (('A' <= ch && ch <= 'F') ? ch - 'A' + 10 : -1); + if (-1 == v) { + snprintf(errbuf, errbufsz, "invalid hex chars for \\u or \\U"); + xfree(dst); + return 0; + } + ucs = ucs * 16 + v; + } + int n = toml_ucs_to_utf8(ucs, &dst[off]); + if (-1 == n) { + snprintf(errbuf, errbufsz, "illegal ucs code in \\u or \\U"); + xfree(dst); + return 0; + } + off += n; + } + continue; + + case 'b': ch = '\b'; break; + case 't': ch = '\t'; break; + case 'n': ch = '\n'; break; + case 'f': ch = '\f'; break; + case 'r': ch = '\r'; break; + case '"': ch = '"'; break; + case '\\': ch = '\\'; break; + default: + snprintf(errbuf, errbufsz, "illegal escape char \\%c", ch); + xfree(dst); + return 0; + } + + dst[off++] = ch; + } + + // Cap with NUL and return it. + dst[off++] = 0; + return dst; +} + + +/* Normalize a key. Convert all special chars to raw unescaped utf-8 chars. */ +static char* normalize_key(context_t* ctx, token_t strtok) +{ + const char* sp = strtok.ptr; + const char* sq = strtok.ptr + strtok.len; + int lineno = strtok.lineno; + char* ret; + int ch = *sp; + char ebuf[80]; + + /* handle quoted string */ + if (ch == '\'' || ch == '\"') { + /* if ''' or """, take 3 chars off front and back. Else, take 1 char off. */ + int multiline = 0; + if (sp[1] == ch && sp[2] == ch) { + sp += 3, sq -= 3; + multiline = 1; + } + else + sp++, sq--; + + if (ch == '\'') { + /* for single quote, take it verbatim. */ + if (! (ret = STRNDUP(sp, sq - sp))) { + e_outofmemory(ctx, FLINE); + return 0; /* not reached */ + } + } else { + /* for double quote, we need to normalize */ + ret = norm_basic_str(sp, sq - sp, multiline, ebuf, sizeof(ebuf)); + if (!ret) { + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, ebuf); + longjmp(ctx->jmp, 1); + } + } + + /* newlines are not allowed in keys */ + if (strchr(ret, '\n')) { + xfree(ret); + e_bad_key_error(ctx, lineno); + return 0; /* not reached */ + } + return ret; + } + + /* for bare-key allow only this regex: [A-Za-z0-9_-]+ */ + const char* xp; + for (xp = sp; xp != sq; xp++) { + int k = *xp; + if (isalnum(k)) continue; + if (k == '_' || k == '-') continue; + e_bad_key_error(ctx, lineno); + return 0; /* not reached */ + } + + /* dup and return it */ + if (! (ret = STRNDUP(sp, sq - sp))) { + e_outofmemory(ctx, FLINE); + return 0; /* not reached */ + } + return ret; +} + + +/* + * Look up key in tab. Return 0 if not found, or + * 'v'alue, 'a'rray or 't'able depending on the element. + */ +static int check_key(toml_table_t* tab, const char* key, + toml_keyval_t** ret_val, + toml_array_t** ret_arr, + toml_table_t** ret_tab) +{ + int i; + void* dummy; + + if (!ret_tab) ret_tab = (toml_table_t**) &dummy; + if (!ret_arr) ret_arr = (toml_array_t**) &dummy; + if (!ret_val) ret_val = (toml_keyval_t**) &dummy; + + *ret_tab = 0; *ret_arr = 0; *ret_val = 0; + + for (i = 0; i < tab->nkval; i++) { + if (0 == strcmp(key, tab->kval[i]->key)) { + *ret_val = tab->kval[i]; + return 'v'; + } + } + for (i = 0; i < tab->narr; i++) { + if (0 == strcmp(key, tab->arr[i]->key)) { + *ret_arr = tab->arr[i]; + return 'a'; + } + } + for (i = 0; i < tab->ntab; i++) { + if (0 == strcmp(key, tab->tab[i]->key)) { + *ret_tab = tab->tab[i]; + return 't'; + } + } + return 0; +} + + +static int key_kind(toml_table_t* tab, const char* key) +{ + return check_key(tab, key, 0, 0, 0); +} + +/* Create a keyval in the table. + */ +static toml_keyval_t* create_keyval_in_table(context_t* ctx, toml_table_t* tab, token_t keytok) +{ + /* first, normalize the key to be used for lookup. + * remember to free it if we error out. + */ + char* newkey = normalize_key(ctx, keytok); + + /* if key exists: error out. */ + toml_keyval_t* dest = 0; + if (key_kind(tab, newkey)) { + e_key_exists_error(ctx, keytok.lineno, newkey); + xfree(newkey); + longjmp(ctx->jmp, 1); + return 0; /* not reached */ + } + + /* make a new entry */ + int n = tab->nkval; + toml_keyval_t** base; + if (0 == (base = (toml_keyval_t**) REALLOC(tab->kval, (n+1) * sizeof(*base)))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; /* not reached */ + } + tab->kval = base; + + if (0 == (base[n] = (toml_keyval_t*) CALLOC(1, sizeof(*base[n])))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; /* not reached */ + } + dest = tab->kval[tab->nkval++]; + + /* save the key in the new value struct */ + dest->key = newkey; + return dest; +} + + +/* Create a table in the table. + */ +static toml_table_t* create_keytable_in_table(context_t* ctx, toml_table_t* tab, token_t keytok) +{ + /* first, normalize the key to be used for lookup. + * remember to free it if we error out. + */ + char* newkey = normalize_key(ctx, keytok); + + /* if key exists: error out */ + toml_table_t* dest = 0; + if (check_key(tab, newkey, 0, 0, &dest)) { + /* special case: if table exists, but was created implicitly ... */ + if (dest && dest->implicit) { + /* we make it explicit now, and simply return it. */ + dest->implicit = 0; + xfree(newkey); /* don't need this anymore */ + return dest; + } + e_key_exists_error(ctx, keytok.lineno, newkey); + xfree(newkey); /* don't need this anymore */ + longjmp(ctx->jmp, 1); + return 0; /* not reached */ + } + + /* create a new table entry */ + int n = tab->ntab; + toml_table_t** base; + if (0 == (base = (toml_table_t**) REALLOC(tab->tab, (n+1) * sizeof(*base)))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; /* not reached */ + } + tab->tab = base; + + if (0 == (base[n] = (toml_table_t*) CALLOC(1, sizeof(*base[n])))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; /* not reached */ + } + dest = tab->tab[tab->ntab++]; + + /* save the key in the new table struct */ + dest->key = newkey; + return dest; +} + + +/* Create an array in the table. + */ +static toml_array_t* create_keyarray_in_table(context_t* ctx, + toml_table_t* tab, + token_t keytok, + char kind) +{ + /* first, normalize the key to be used for lookup. + * remember to free it if we error out. + */ + char* newkey = normalize_key(ctx, keytok); + + /* if key exists: error out */ + if (key_kind(tab, newkey)) { + e_key_exists_error(ctx, keytok.lineno, newkey); + xfree(newkey); /* don't need this anymore */ + longjmp(ctx->jmp, 1); + return 0; /* not reached */ + } + + /* make a new array entry */ + int n = tab->narr; + toml_array_t** base; + if (0 == (base = (toml_array_t**) REALLOC(tab->arr, (n+1) * sizeof(*base)))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; /* not reached */ + } + tab->arr = base; + + if (0 == (base[n] = (toml_array_t*) CALLOC(1, sizeof(*base[n])))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; /* not reached */ + } + toml_array_t* dest = tab->arr[tab->narr++]; + + /* save the key in the new array struct */ + dest->key = newkey; + dest->kind = kind; + return dest; +} + +/* Create an array in an array + */ +static toml_array_t* create_array_in_array(context_t* ctx, + toml_array_t* parent) +{ + int n = parent->nelem; + toml_array_t** base; + if (0 == (base = (toml_array_t**) REALLOC(parent->u.arr, (n+1) * sizeof(*base)))) { + e_outofmemory(ctx, FLINE); + return 0; /* not reached */ + } + parent->u.arr = base; + + if (0 == (base[n] = (toml_array_t*) CALLOC(1, sizeof(*base[n])))) { + e_outofmemory(ctx, FLINE); + return 0; /* not reached */ + } + + return parent->u.arr[parent->nelem++]; +} + +/* Create a table in an array + */ +static toml_table_t* create_table_in_array(context_t* ctx, + toml_array_t* parent) +{ + int n = parent->nelem; + toml_table_t** base; + if (0 == (base = (toml_table_t**) REALLOC(parent->u.tab, (n+1) * sizeof(*base)))) { + e_outofmemory(ctx, FLINE); + return 0; /* not reached */ + } + parent->u.tab = base; + + if (0 == (base[n] = (toml_table_t*) CALLOC(1, sizeof(*base[n])))) { + e_outofmemory(ctx, FLINE); + return 0; /* not reached */ + } + + return parent->u.tab[parent->nelem++]; +} + + +#define SKIP_NEWLINES(ctx, isdotspecial) while (ctx->tok.tok == NEWLINE) next_token(ctx, isdotspecial) +#define EAT_TOKEN(ctx, typ, isdotspecial) \ + if ((ctx)->tok.tok != typ) e_internal_error(ctx, FLINE); else next_token(ctx, isdotspecial) + + +static void parse_keyval(context_t* ctx, toml_table_t* tab); + + +/* We are at '{ ... }'. + * Parse the table. + */ +static void parse_table(context_t* ctx, toml_table_t* tab) +{ + EAT_TOKEN(ctx, LBRACE, 1); + + for (;;) { + if (ctx->tok.tok == NEWLINE) { + e_syntax_error(ctx, ctx->tok.lineno, "newline not allowed in inline table"); + return; /* not reached */ + } + + /* until } */ + if (ctx->tok.tok == RBRACE) break; + + if (ctx->tok.tok != STRING) { + e_syntax_error(ctx, ctx->tok.lineno, "syntax error"); + return; /* not reached */ + } + parse_keyval(ctx, tab); + + if (ctx->tok.tok == NEWLINE) { + e_syntax_error(ctx, ctx->tok.lineno, "newline not allowed in inline table"); + return; /* not reached */ + } + + /* on comma, continue to scan for next keyval */ + if (ctx->tok.tok == COMMA) { + EAT_TOKEN(ctx, COMMA, 1); + continue; + } + break; + } + + if (ctx->tok.tok != RBRACE) { + e_syntax_error(ctx, ctx->tok.lineno, "syntax error"); + return; /* not reached */ + } + + EAT_TOKEN(ctx, RBRACE, 1); +} + +static int valtype(const char* val) +{ + toml_timestamp_t ts; + if (*val == '\'' || *val == '"') return 's'; + if (0 == toml_rtob(val, 0)) return 'b'; + if (0 == toml_rtoi(val, 0)) return 'i'; + if (0 == toml_rtod(val, 0)) return 'd'; + if (0 == toml_rtots(val, &ts)) { + if (ts.year && ts.hour) return 'T'; /* timestamp */ + if (ts.year) return 'D'; /* date */ + return 't'; /* time */ + } + return 'u'; /* unknown */ +} + + +/* We are at '[...]' */ +static void parse_array(context_t* ctx, toml_array_t* arr) +{ + EAT_TOKEN(ctx, LBRACKET, 0); + + for (;;) { + SKIP_NEWLINES(ctx, 0); + + /* until ] */ + if (ctx->tok.tok == RBRACKET) break; + + switch (ctx->tok.tok) { + case STRING: + { + char* val = ctx->tok.ptr; + int vlen = ctx->tok.len; + + /* set array kind if this will be the first entry */ + if (arr->kind == 0) arr->kind = 'v'; + /* check array kind */ + if (arr->kind != 'v') { + e_syntax_error(ctx, ctx->tok.lineno, + "a string array can only contain strings"); + return; /* not reached */ + } + + /* make a new value in array */ + char** tmp = (char**) REALLOC(arr->u.val, (arr->nelem+1) * sizeof(*tmp)); + if (!tmp) { + e_outofmemory(ctx, FLINE); + return; /* not reached */ + } + arr->u.val = tmp; + if (! (val = STRNDUP(val, vlen))) { + e_outofmemory(ctx, FLINE); + return; /* not reached */ + } + arr->u.val[arr->nelem++] = val; + + /* set array type if this is the first entry, or check that the types matched. */ + if (arr->nelem == 1) + arr->type = valtype(arr->u.val[0]); + else if (arr->type != valtype(val)) { + e_syntax_error(ctx, ctx->tok.lineno, + "array type mismatch while processing array of values"); + return; /* not reached */ + } + + EAT_TOKEN(ctx, STRING, 0); + break; + } + + case LBRACKET: + { /* [ [array], [array] ... ] */ + /* set the array kind if this will be the first entry */ + if (arr->kind == 0) arr->kind = 'a'; + /* check array kind */ + if (arr->kind != 'a') { + e_syntax_error(ctx, ctx->tok.lineno, + "array type mismatch while processing array of arrays"); + return; /* not reached */ + } + parse_array(ctx, create_array_in_array(ctx, arr)); + break; + } + + case LBRACE: + { /* [ {table}, {table} ... ] */ + /* set the array kind if this will be the first entry */ + if (arr->kind == 0) arr->kind = 't'; + /* check array kind */ + if (arr->kind != 't') { + e_syntax_error(ctx, ctx->tok.lineno, + "array type mismatch while processing array of tables"); + return; /* not reached */ + } + parse_table(ctx, create_table_in_array(ctx, arr)); + break; + } + + default: + e_syntax_error(ctx, ctx->tok.lineno, "syntax error"); + return; /* not reached */ + } + + SKIP_NEWLINES(ctx, 0); + + /* on comma, continue to scan for next element */ + if (ctx->tok.tok == COMMA) { + EAT_TOKEN(ctx, COMMA, 0); + continue; + } + break; + } + + if (ctx->tok.tok != RBRACKET) { + e_syntax_error(ctx, ctx->tok.lineno, "syntax error"); + return; /* not reached */ + } + + EAT_TOKEN(ctx, RBRACKET, 1); +} + + +/* handle lines like these: + key = "value" + key = [ array ] + key = { table } +*/ +static void parse_keyval(context_t* ctx, toml_table_t* tab) +{ + token_t key = ctx->tok; + EAT_TOKEN(ctx, STRING, 1); + + if (ctx->tok.tok == DOT) { + /* handle inline dotted key. + e.g. + physical.color = "orange" + physical.shape = "round" + */ + toml_table_t* subtab = 0; + { + char* subtabstr = normalize_key(ctx, key); + subtab = toml_table_in(tab, subtabstr); + xfree(subtabstr); + } + if (!subtab) { + subtab = create_keytable_in_table(ctx, tab, key); + } + next_token(ctx, 1); + parse_keyval(ctx, subtab); + return; + } + + if (ctx->tok.tok != EQUAL) { + e_syntax_error(ctx, ctx->tok.lineno, "missing ="); + return; /* not reached */ + } + + next_token(ctx, 0); + + switch (ctx->tok.tok) { + case STRING: + { /* key = "value" */ + toml_keyval_t* keyval = create_keyval_in_table(ctx, tab, key); + token_t val = ctx->tok; + assert(keyval->val == 0); + keyval->val = STRNDUP(val.ptr, val.len); + if (! keyval->val) { + e_outofmemory(ctx, FLINE); + return; /* not reached */ + } + + next_token(ctx, 1); + + return; + } + + case LBRACKET: + { /* key = [ array ] */ + toml_array_t* arr = create_keyarray_in_table(ctx, tab, key, 0); + parse_array(ctx, arr); + return; + } + + case LBRACE: + { /* key = { table } */ + toml_table_t* nxttab = create_keytable_in_table(ctx, tab, key); + parse_table(ctx, nxttab); + return; + } + + default: + e_syntax_error(ctx, ctx->tok.lineno, "syntax error"); + return; /* not reached */ + } +} + + +typedef struct tabpath_t tabpath_t; +struct tabpath_t { + int cnt; + token_t key[10]; +}; + +/* at [x.y.z] or [[x.y.z]] + * Scan forward and fill tabpath until it enters ] or ]] + * There will be at least one entry on return. + */ +static void fill_tabpath(context_t* ctx) +{ + int lineno = ctx->tok.lineno; + int i; + + /* clear tpath */ + for (i = 0; i < ctx->tpath.top; i++) { + char** p = &ctx->tpath.key[i]; + xfree(*p); + *p = 0; + } + ctx->tpath.top = 0; + + for (;;) { + if (ctx->tpath.top >= 10) { + e_syntax_error(ctx, lineno, "table path is too deep; max allowed is 10."); + return; /* not reached */ + } + + if (ctx->tok.tok != STRING) { + e_syntax_error(ctx, lineno, "invalid or missing key"); + return; /* not reached */ + } + + ctx->tpath.tok[ctx->tpath.top] = ctx->tok; + ctx->tpath.key[ctx->tpath.top] = normalize_key(ctx, ctx->tok); + ctx->tpath.top++; + + next_token(ctx, 1); + + if (ctx->tok.tok == RBRACKET) break; + + if (ctx->tok.tok != DOT) { + e_syntax_error(ctx, lineno, "invalid key"); + return; /* not reached */ + } + + next_token(ctx, 1); + } + + if (ctx->tpath.top <= 0) { + e_syntax_error(ctx, lineno, "empty table selector"); + return; /* not reached */ + } +} + + +/* Walk tabpath from the root, and create new tables on the way. + * Sets ctx->curtab to the final table. + */ +static void walk_tabpath(context_t* ctx) +{ + /* start from root */ + toml_table_t* curtab = ctx->root; + + for (int i = 0; i < ctx->tpath.top; i++) { + const char* key = ctx->tpath.key[i]; + + toml_keyval_t* nextval = 0; + toml_array_t* nextarr = 0; + toml_table_t* nexttab = 0; + switch (check_key(curtab, key, &nextval, &nextarr, &nexttab)) { + case 't': + /* found a table. nexttab is where we will go next. */ + break; + + case 'a': + /* found an array. nexttab is the last table in the array. */ + if (nextarr->kind != 't') { + e_internal_error(ctx, FLINE); + return; /* not reached */ + } + if (nextarr->nelem == 0) { + e_internal_error(ctx, FLINE); + return; /* not reached */ + } + nexttab = nextarr->u.tab[nextarr->nelem-1]; + break; + + case 'v': + e_key_exists_error(ctx, ctx->tpath.tok[i].lineno, key); + longjmp(ctx->jmp, 1); + return; /* not reached */ + + default: + { /* Not found. Let's create an implicit table. */ + int n = curtab->ntab; + toml_table_t** base = (toml_table_t**) REALLOC(curtab->tab, (n+1) * sizeof(*base)); + if (0 == base) { + e_outofmemory(ctx, FLINE); + return; /* not reached */ + } + curtab->tab = base; + + if (0 == (base[n] = (toml_table_t*) CALLOC(1, sizeof(*base[n])))) { + e_outofmemory(ctx, FLINE); + return; /* not reached */ + } + + if (0 == (base[n]->key = STRDUP(key))) { + e_outofmemory(ctx, FLINE); + return; /* not reached */ + } + + nexttab = curtab->tab[curtab->ntab++]; + + /* tabs created by walk_tabpath are considered implicit */ + nexttab->implicit = 1; + } + break; + } + + /* switch to next tab */ + curtab = nexttab; + } + + /* save it */ + ctx->curtab = curtab; +} + + +/* handle lines like [x.y.z] or [[x.y.z]] */ +static void parse_select(context_t* ctx) +{ + assert(ctx->tok.tok == LBRACKET); + + /* true if [[ */ + int llb = (ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == '['); + /* need to detect '[[' on our own because next_token() will skip whitespace, + and '[ [' would be taken as '[[', which is wrong. */ + + /* eat [ or [[ */ + EAT_TOKEN(ctx, LBRACKET, 1); + if (llb) { + assert(ctx->tok.tok == LBRACKET); + EAT_TOKEN(ctx, LBRACKET, 1); + } + + fill_tabpath(ctx); + + /* For [x.y.z] or [[x.y.z]], remove z from tpath. + */ + token_t z = ctx->tpath.tok[ctx->tpath.top-1]; + xfree(ctx->tpath.key[ctx->tpath.top-1]); + ctx->tpath.top--; + + /* set up ctx->curtab */ + walk_tabpath(ctx); + + if (! llb) { + /* [x.y.z] -> create z = {} in x.y */ + ctx->curtab = create_keytable_in_table(ctx, ctx->curtab, z); + } else { + /* [[x.y.z]] -> create z = [] in x.y */ + toml_array_t* arr = 0; + { + char* zstr = normalize_key(ctx, z); + arr = toml_array_in(ctx->curtab, zstr); + xfree(zstr); + } + if (!arr) { + arr = create_keyarray_in_table(ctx, ctx->curtab, z, 't'); + if (!arr) { + e_internal_error(ctx, FLINE); + return; + } + } + if (arr->kind != 't') { + e_syntax_error(ctx, z.lineno, "array mismatch"); + return; /* not reached */ + } + + /* add to z[] */ + toml_table_t* dest; + { + int n = arr->nelem; + toml_table_t** base = REALLOC(arr->u.tab, (n+1) * sizeof(*base)); + if (0 == base) { + e_outofmemory(ctx, FLINE); + return; /* not reached */ + } + arr->u.tab = base; + + if (0 == (base[n] = CALLOC(1, sizeof(*base[n])))) { + e_outofmemory(ctx, FLINE); + return; /* not reached */ + } + + if (0 == (base[n]->key = STRDUP("__anon__"))) { + e_outofmemory(ctx, FLINE); + return; /* not reached */ + } + + dest = arr->u.tab[arr->nelem++]; + } + + ctx->curtab = dest; + } + + if (ctx->tok.tok != RBRACKET) { + e_syntax_error(ctx, ctx->tok.lineno, "expects ]"); + return; /* not reached */ + } + if (llb) { + if (! (ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == ']')) { + e_syntax_error(ctx, ctx->tok.lineno, "expects ]]"); + return; /* not reached */ + } + EAT_TOKEN(ctx, RBRACKET, 1); + } + EAT_TOKEN(ctx, RBRACKET, 1); + + if (ctx->tok.tok != NEWLINE) { + e_syntax_error(ctx, ctx->tok.lineno, "extra chars after ] or ]]"); + return; /* not reached */ + } +} + + + + +toml_table_t* toml_parse(char* conf, + char* errbuf, + int errbufsz) +{ + context_t ctx; + + // clear errbuf + if (errbufsz <= 0) errbufsz = 0; + if (errbufsz > 0) errbuf[0] = 0; + + // init context + memset(&ctx, 0, sizeof(ctx)); + ctx.start = conf; + ctx.stop = ctx.start + strlen(conf); + ctx.errbuf = errbuf; + ctx.errbufsz = errbufsz; + + // start with an artificial newline of length 0 + ctx.tok.tok = NEWLINE; + ctx.tok.lineno = 1; + ctx.tok.ptr = conf; + ctx.tok.len = 0; + + // make a root table + if (0 == (ctx.root = CALLOC(1, sizeof(*ctx.root)))) { + /* do not call outofmemory() here... setjmp not done yet */ + snprintf(ctx.errbuf, ctx.errbufsz, "ERROR: out of memory (%s)", FLINE); + return 0; + } + + // set root as default table + ctx.curtab = ctx.root; + + if (0 != setjmp(ctx.jmp)) { + // Got here from a long_jmp. Something bad has happened. + // Free resources and return error. + for (int i = 0; i < ctx.tpath.top; i++) xfree(ctx.tpath.key[i]); + toml_free(ctx.root); + return 0; + } + + /* Scan forward until EOF */ + for (token_t tok = ctx.tok; ! tok.eof ; tok = ctx.tok) { + switch (tok.tok) { + + case NEWLINE: + next_token(&ctx, 1); + break; + + case STRING: + parse_keyval(&ctx, ctx.curtab); + if (ctx.tok.tok != NEWLINE) { + e_syntax_error(&ctx, ctx.tok.lineno, "extra chars after value"); + return 0; /* not reached */ + } + + EAT_TOKEN(&ctx, NEWLINE, 1); + break; + + case LBRACKET: /* [ x.y.z ] or [[ x.y.z ]] */ + parse_select(&ctx); + break; + + default: + snprintf(ctx.errbuf, ctx.errbufsz, "line %d: syntax error", tok.lineno); + longjmp(ctx.jmp, 1); + } + } + + /* success */ + for (int i = 0; i < ctx.tpath.top; i++) xfree(ctx.tpath.key[i]); + return ctx.root; +} + + +toml_table_t* toml_parse_file(FILE* fp, + char* errbuf, + int errbufsz) +{ + int bufsz = 0; + char* buf = 0; + int off = 0; + + /* prime the buf[] */ + bufsz = 1000; + if (! (buf = MALLOC(bufsz + 1))) { + snprintf(errbuf, errbufsz, "out of memory"); + return 0; + } + + /* read from fp into buf */ + while (! feof(fp)) { + bufsz += 1000; + + /* Allocate 1 extra byte because we will tag on a NUL */ + char* x = REALLOC(buf, bufsz + 1); + if (!x) { + snprintf(errbuf, errbufsz, "out of memory"); + xfree(buf); + return 0; + } + buf = x; + + errno = 0; + int n = fread(buf + off, 1, bufsz - off, fp); + if (ferror(fp)) { + snprintf(errbuf, errbufsz, "%s", + errno ? strerror(errno) : "Error reading file"); + xfree(buf); + return 0; + } + off += n; + } + + /* tag on a NUL to cap the string */ + buf[off] = 0; /* we accounted for this byte in the REALLOC() above. */ + + /* parse it, cleanup and finish */ + toml_table_t* ret = toml_parse(buf, errbuf, errbufsz); + xfree(buf); + return ret; +} + + +static void xfree_kval(toml_keyval_t* p) +{ + if (!p) return; + xfree(p->key); + xfree(p->val); + xfree(p); +} + +static void xfree_tab(toml_table_t* p); + +static void xfree_arr(toml_array_t* p) +{ + if (!p) return; + + xfree(p->key); + switch (p->kind) { + case 'v': + for (int i = 0; i < p->nelem; i++) xfree(p->u.val[i]); + xfree(p->u.val); + break; + + case 'a': + for (int i = 0; i < p->nelem; i++) xfree_arr(p->u.arr[i]); + xfree(p->u.arr); + break; + + case 't': + for (int i = 0; i < p->nelem; i++) xfree_tab(p->u.tab[i]); + xfree(p->u.tab); + break; + } + + xfree(p); +} + + +static void xfree_tab(toml_table_t* p) +{ + int i; + + if (!p) return; + + xfree(p->key); + + for (i = 0; i < p->nkval; i++) xfree_kval(p->kval[i]); + xfree(p->kval); + + for (i = 0; i < p->narr; i++) xfree_arr(p->arr[i]); + xfree(p->arr); + + for (i = 0; i < p->ntab; i++) xfree_tab(p->tab[i]); + xfree(p->tab); + + xfree(p); +} + + +void toml_free(toml_table_t* tab) +{ + xfree_tab(tab); +} + + +static tokentype_t ret_token(context_t* ctx, tokentype_t tok, int lineno, char* ptr, int len) +{ + token_t t; + t.tok = tok; + t.lineno = lineno; + t.ptr = ptr; + t.len = len; + t.eof = 0; + ctx->tok = t; + return tok; +} + +static tokentype_t ret_eof(context_t* ctx, int lineno) +{ + ret_token(ctx, NEWLINE, lineno, ctx->stop, 0); + ctx->tok.eof = 1; + return ctx->tok.tok; +} + + +/* Scan p for n digits compositing entirely of [0-9] */ +static int scan_digits(const char* p, int n) +{ + int ret = 0; + for ( ; n > 0 && isdigit(*p); n--, p++) { + ret = 10 * ret + (*p - '0'); + } + return n ? -1 : ret; +} + +static int scan_date(const char* p, int* YY, int* MM, int* DD) +{ + int year, month, day; + year = scan_digits(p, 4); + month = (year >= 0 && p[4] == '-') ? scan_digits(p+5, 2) : -1; + day = (month >= 0 && p[7] == '-') ? scan_digits(p+8, 2) : -1; + if (YY) *YY = year; + if (MM) *MM = month; + if (DD) *DD = day; + return (year >= 0 && month >= 0 && day >= 0) ? 0 : -1; +} + +static int scan_time(const char* p, int* hh, int* mm, int* ss) +{ + int hour, minute, second; + hour = scan_digits(p, 2); + minute = (hour >= 0 && p[2] == ':') ? scan_digits(p+3, 2) : -1; + second = (minute >= 0 && p[5] == ':') ? scan_digits(p+6, 2) : -1; + if (hh) *hh = hour; + if (mm) *mm = minute; + if (ss) *ss = second; + return (hour >= 0 && minute >= 0 && second >= 0) ? 0 : -1; +} + + +static tokentype_t scan_string(context_t* ctx, char* p, int lineno, int dotisspecial) +{ + char* orig = p; + if (0 == strncmp(p, "'''", 3)) { + p = strstr(p + 3, "'''"); + if (0 == p) { + e_syntax_error(ctx, lineno, "unterminated triple-s-quote"); + return 0; /* not reached */ + } + + return ret_token(ctx, STRING, lineno, orig, p + 3 - orig); + } + + if (0 == strncmp(p, "\"\"\"", 3)) { + int hexreq = 0; /* #hex required */ + int escape = 0; + int qcnt = 0; /* count quote */ + for (p += 3; *p && qcnt < 3; p++) { + if (escape) { + escape = 0; + if (strchr("btnfr\"\\", *p)) continue; + if (*p == 'u') { hexreq = 4; continue; } + if (*p == 'U') { hexreq = 8; continue; } + if (p[strspn(p, " \t\r")] == '\n') continue; /* allow for line ending backslash */ + e_syntax_error(ctx, lineno, "bad escape char"); + return 0; /* not reached */ + } + if (hexreq) { + hexreq--; + if (strchr("0123456789ABCDEF", *p)) continue; + e_syntax_error(ctx, lineno, "expect hex char"); + return 0; /* not reached */ + } + if (*p == '\\') { escape = 1; continue; } + qcnt = (*p == '"') ? qcnt + 1 : 0; + } + if (qcnt != 3) { + e_syntax_error(ctx, lineno, "unterminated triple-quote"); + return 0; /* not reached */ + } + + return ret_token(ctx, STRING, lineno, orig, p - orig); + } + + if ('\'' == *p) { + for (p++; *p && *p != '\n' && *p != '\''; p++); + if (*p != '\'') { + e_syntax_error(ctx, lineno, "unterminated s-quote"); + return 0; /* not reached */ + } + + return ret_token(ctx, STRING, lineno, orig, p + 1 - orig); + } + + if ('\"' == *p) { + int hexreq = 0; /* #hex required */ + int escape = 0; + for (p++; *p; p++) { + if (escape) { + escape = 0; + if (strchr("btnfr\"\\", *p)) continue; + if (*p == 'u') { hexreq = 4; continue; } + if (*p == 'U') { hexreq = 8; continue; } + e_syntax_error(ctx, lineno, "bad escape char"); + return 0; /* not reached */ + } + if (hexreq) { + hexreq--; + if (strchr("0123456789ABCDEF", *p)) continue; + e_syntax_error(ctx, lineno, "expect hex char"); + return 0; /* not reached */ + } + if (*p == '\\') { escape = 1; continue; } + if (*p == '\n') break; + if (*p == '"') break; + } + if (*p != '"') { + e_syntax_error(ctx, lineno, "unterminated quote"); + return 0; /* not reached */ + } + + return ret_token(ctx, STRING, lineno, orig, p + 1 - orig); + } + + /* check for timestamp without quotes */ + if (0 == scan_date(p, 0, 0, 0) || 0 == scan_time(p, 0, 0, 0)) { + // forward thru the timestamp + for ( ; strchr("0123456789.:+-T Z", toupper(*p)); p++); + // squeeze out any spaces at end of string + for ( ; p[-1] == ' '; p--); + // tokenize + return ret_token(ctx, STRING, lineno, orig, p - orig); + } + + /* literals */ + for ( ; *p && *p != '\n'; p++) { + int ch = *p; + if (ch == '.' && dotisspecial) break; + if ('A' <= ch && ch <= 'Z') continue; + if ('a' <= ch && ch <= 'z') continue; + if (strchr("0123456789+-_.", ch)) continue; + break; + } + + return ret_token(ctx, STRING, lineno, orig, p - orig); +} + + +static tokentype_t next_token(context_t* ctx, int dotisspecial) +{ + int lineno = ctx->tok.lineno; + char* p = ctx->tok.ptr; + int i; + + /* eat this tok */ + for (i = 0; i < ctx->tok.len; i++) { + if (*p++ == '\n') + lineno++; + } + + /* make next tok */ + while (p < ctx->stop) { + /* skip comment. stop just before the \n. */ + if (*p == '#') { + for (p++; p < ctx->stop && *p != '\n'; p++); + continue; + } + + if (dotisspecial && *p == '.') + return ret_token(ctx, DOT, lineno, p, 1); + + switch (*p) { + case ',': return ret_token(ctx, COMMA, lineno, p, 1); + case '=': return ret_token(ctx, EQUAL, lineno, p, 1); + case '{': return ret_token(ctx, LBRACE, lineno, p, 1); + case '}': return ret_token(ctx, RBRACE, lineno, p, 1); + case '[': return ret_token(ctx, LBRACKET, lineno, p, 1); + case ']': return ret_token(ctx, RBRACKET, lineno, p, 1); + case '\n': return ret_token(ctx, NEWLINE, lineno, p, 1); + case '\r': case ' ': case '\t': + /* ignore white spaces */ + p++; + continue; + } + + return scan_string(ctx, p, lineno, dotisspecial); + } + + return ret_eof(ctx, lineno); +} + + +const char* toml_key_in(toml_table_t* tab, int keyidx) +{ + if (keyidx < tab->nkval) return tab->kval[keyidx]->key; + + keyidx -= tab->nkval; + if (keyidx < tab->narr) return tab->arr[keyidx]->key; + + keyidx -= tab->narr; + if (keyidx < tab->ntab) return tab->tab[keyidx]->key; + + return 0; +} + + +const char* toml_raw_in(toml_table_t* tab, const char* key) +{ + int i; + for (i = 0; i < tab->nkval; i++) { + if (0 == strcmp(key, tab->kval[i]->key)) + return tab->kval[i]->val; + } + return 0; +} + +toml_array_t* toml_array_in(toml_table_t* tab, const char* key) +{ + int i; + for (i = 0; i < tab->narr; i++) { + if (0 == strcmp(key, tab->arr[i]->key)) + return tab->arr[i]; + } + return 0; +} + + +toml_table_t* toml_table_in(toml_table_t* tab, const char* key) +{ + int i; + for (i = 0; i < tab->ntab; i++) { + if (0 == strcmp(key, tab->tab[i]->key)) + return tab->tab[i]; + } + return 0; +} + +const char* toml_raw_at(toml_array_t* arr, int idx) +{ + if (arr->kind != 'v') + return 0; + if (! (0 <= idx && idx < arr->nelem)) + return 0; + return arr->u.val[idx]; +} + +char toml_array_kind(toml_array_t* arr) +{ + return arr->kind; +} + +char toml_array_type(toml_array_t* arr) +{ + if (arr->kind != 'v') + return 0; + + if (arr->nelem == 0) + return 0; + + return arr->type; +} + + +int toml_array_nelem(toml_array_t* arr) +{ + return arr->nelem; +} + +const char* toml_array_key(toml_array_t* arr) +{ + return arr ? arr->key : (const char*) NULL; +} + +int toml_table_nkval(toml_table_t* tab) +{ + return tab->nkval; +} + +int toml_table_narr(toml_table_t* tab) +{ + return tab->narr; +} + +int toml_table_ntab(toml_table_t* tab) +{ + return tab->ntab; +} + +const char* toml_table_key(toml_table_t* tab) +{ + return tab ? tab->key : (const char*) NULL; +} + +toml_array_t* toml_array_at(toml_array_t* arr, int idx) +{ + if (arr->kind != 'a') + return 0; + if (! (0 <= idx && idx < arr->nelem)) + return 0; + return arr->u.arr[idx]; +} + +toml_table_t* toml_table_at(toml_array_t* arr, int idx) +{ + if (arr->kind != 't') + return 0; + if (! (0 <= idx && idx < arr->nelem)) + return 0; + return arr->u.tab[idx]; +} + + +int toml_rtots(const char* src_, toml_timestamp_t* ret) +{ + if (! src_) return -1; + + const char* p = src_; + int must_parse_time = 0; + + memset(ret, 0, sizeof(*ret)); + + int* year = &ret->__buffer.year; + int* month = &ret->__buffer.month; + int* day = &ret->__buffer.day; + int* hour = &ret->__buffer.hour; + int* minute = &ret->__buffer.minute; + int* second = &ret->__buffer.second; + int* millisec = &ret->__buffer.millisec; + + /* parse date YYYY-MM-DD */ + if (0 == scan_date(p, year, month, day)) { + ret->year = year; + ret->month = month; + ret->day = day; + + p += 10; + if (*p) { + // parse the T or space separator + if (*p != 'T' && *p != ' ') return -1; + must_parse_time = 1; + p++; + } + } + + /* parse time HH:MM:SS */ + if (0 == scan_time(p, hour, minute, second)) { + ret->hour = hour; + ret->minute = minute; + ret->second = second; + + /* optionally, parse millisec */ + p += 8; + if (*p == '.') { + char* qq; + p++; + errno = 0; + *millisec = strtol(p, &qq, 0); + if (errno) { + return -1; + } + while (*millisec > 999) { + *millisec /= 10; + } + + ret->millisec = millisec; + p = qq; + } + + if (*p) { + /* parse and copy Z */ + char* z = ret->__buffer.z; + ret->z = z; + if (*p == 'Z' || *p == 'z') { + *z++ = 'Z'; p++; + *z = 0; + + } else if (*p == '+' || *p == '-') { + *z++ = *p++; + + if (! (isdigit(p[0]) && isdigit(p[1]))) return -1; + *z++ = *p++; + *z++ = *p++; + + if (*p == ':') { + *z++ = *p++; + + if (! (isdigit(p[0]) && isdigit(p[1]))) return -1; + *z++ = *p++; + *z++ = *p++; + } + + *z = 0; + } + } + } + if (*p != 0) + return -1; + + if (must_parse_time && !ret->hour) + return -1; + + return 0; +} + + +/* Raw to boolean */ +int toml_rtob(const char* src, int* ret_) +{ + if (!src) return -1; + int dummy; + int* ret = ret_ ? ret_ : &dummy; + + if (0 == strcmp(src, "true")) { + *ret = 1; + return 0; + } + if (0 == strcmp(src, "false")) { + *ret = 0; + return 0; + } + return -1; +} + + +/* Raw to integer */ +int toml_rtoi(const char* src, int64_t* ret_) +{ + if (!src) return -1; + + char buf[100]; + char* p = buf; + char* q = p + sizeof(buf); + const char* s = src; + int base = 0; + int64_t dummy; + int64_t* ret = ret_ ? ret_ : &dummy; + + + /* allow +/- */ + if (s[0] == '+' || s[0] == '-') + *p++ = *s++; + + /* disallow +_100 */ + if (s[0] == '_') + return -1; + + /* if 0 ... */ + if ('0' == s[0]) { + switch (s[1]) { + case 'x': base = 16; s += 2; break; + case 'o': base = 8; s += 2; break; + case 'b': base = 2; s += 2; break; + case '\0': return *ret = 0, 0; + default: + /* ensure no other digits after it */ + if (s[1]) return -1; + } + } + + /* just strip underscores and pass to strtoll */ + while (*s && p < q) { + int ch = *s++; + switch (ch) { + case '_': + // disallow '__' + if (s[0] == '_') return -1; + continue; /* skip _ */ + default: + break; + } + *p++ = ch; + } + if (*s || p == q) return -1; + + /* last char cannot be '_' */ + if (s[-1] == '_') return -1; + + /* cap with NUL */ + *p = 0; + + /* Run strtoll on buf to get the integer */ + char* endp; + errno = 0; + *ret = strtoll(buf, &endp, base); + return (errno || *endp) ? -1 : 0; +} + + +int toml_rtod_ex(const char* src, double* ret_, char* buf, int buflen) +{ + if (!src) return -1; + + char* p = buf; + char* q = p + buflen; + const char* s = src; + double dummy; + double* ret = ret_ ? ret_ : &dummy; + + + /* allow +/- */ + if (s[0] == '+' || s[0] == '-') + *p++ = *s++; + + /* disallow +_1.00 */ + if (s[0] == '_') + return -1; + + /* disallow +.99 */ + if (s[0] == '.') + return -1; + + /* zero must be followed by . or 'e', or NUL */ + if (s[0] == '0' && s[1] && !strchr("eE.", s[1])) + return -1; + + /* just strip underscores and pass to strtod */ + while (*s && p < q) { + int ch = *s++; + switch (ch) { + case '.': + if (s[-2] == '_') return -1; + if (s[0] == '_') return -1; + break; + case '_': + // disallow '__' + if (s[0] == '_') return -1; + continue; /* skip _ */ + default: + break; + } + *p++ = ch; + } + if (*s || p == q) return -1; /* reached end of string or buffer is full? */ + + /* last char cannot be '_' */ + if (s[-1] == '_') return -1; + + if (p != buf && p[-1] == '.') + return -1; /* no trailing zero */ + + /* cap with NUL */ + *p = 0; + + /* Run strtod on buf to get the value */ + char* endp; + errno = 0; + *ret = strtod(buf, &endp); + return (errno || *endp) ? -1 : 0; +} + +int toml_rtod(const char* src, double* ret_) +{ + char buf[100]; + return toml_rtod_ex(src, ret_, buf, sizeof(buf)); +} + + + + +int toml_rtos(const char* src, char** ret) +{ + int multiline = 0; + const char* sp; + const char* sq; + + *ret = 0; + if (!src) return -1; + + int qchar = src[0]; + int srclen = strlen(src); + if (! (qchar == '\'' || qchar == '"')) { + return -1; + } + + // triple quotes? + if (qchar == src[1] && qchar == src[2]) { + multiline = 1; + sp = src + 3; + sq = src + srclen - 3; + /* last 3 chars in src must be qchar */ + if (! (sp <= sq && sq[0] == qchar && sq[1] == qchar && sq[2] == qchar)) + return -1; + + /* skip new line immediate after qchar */ + if (sp[0] == '\n') + sp++; + else if (sp[0] == '\r' && sp[1] == '\n') + sp += 2; + + } else { + sp = src + 1; + sq = src + srclen - 1; + /* last char in src must be qchar */ + if (! (sp <= sq && *sq == qchar)) + return -1; + } + + if (qchar == '\'') { + *ret = norm_lit_str(sp, sq - sp, + multiline, + 0, 0); + } else { + *ret = norm_basic_str(sp, sq - sp, + multiline, + 0, 0); + } + + return *ret ? 0 : -1; +} diff --git a/vendored/libtomlc99/toml.h b/vendored/libtomlc99/toml.h new file mode 100644 index 0000000..34872c9 --- /dev/null +++ b/vendored/libtomlc99/toml.h @@ -0,0 +1,145 @@ +/* + MIT License + + Copyright (c) 2017 - 2019 CK Tan + https://github.com/cktan/tomlc99 + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef TOML_H +#define TOML_H + + +#include +#include + + +#ifdef __cplusplus +#define TOML_EXTERN extern "C" +#else +#define TOML_EXTERN extern +#endif + +typedef struct toml_table_t toml_table_t; +typedef struct toml_array_t toml_array_t; + +/* Parse a file. Return a table on success, or 0 otherwise. + * Caller must toml_free(the-return-value) after use. + */ +TOML_EXTERN toml_table_t* toml_parse_file(FILE* fp, + char* errbuf, + int errbufsz); + +/* Parse a string containing the full config. + * Return a table on success, or 0 otherwise. + * Caller must toml_free(the-return-value) after use. + */ +TOML_EXTERN toml_table_t* toml_parse(char* conf, /* NUL terminated, please. */ + char* errbuf, + int errbufsz); + +/* Free the table returned by toml_parse() or toml_parse_file(). */ +TOML_EXTERN void toml_free(toml_table_t* tab); + +/* Retrieve the key in table at keyidx. Return 0 if out of range. */ +TOML_EXTERN const char* toml_key_in(toml_table_t* tab, int keyidx); + +/* Lookup table by key. Return the element or 0 if not found. */ +TOML_EXTERN const char* toml_raw_in(toml_table_t* tab, const char* key); +TOML_EXTERN toml_array_t* toml_array_in(toml_table_t* tab, const char* key); +TOML_EXTERN toml_table_t* toml_table_in(toml_table_t* tab, const char* key); + +/* Return the array kind: 't'able, 'a'rray, 'v'alue */ +TOML_EXTERN char toml_array_kind(toml_array_t* arr); + +/* For array kind 'v'alue, return the type of values + i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp + 0 if unknown +*/ +TOML_EXTERN char toml_array_type(toml_array_t* arr); + + +/* Return the number of elements in the array */ +TOML_EXTERN int toml_array_nelem(toml_array_t* arr); + +/* Return the key of an array */ +TOML_EXTERN const char* toml_array_key(toml_array_t* arr); + +/* Return the number of key-values in a table */ +TOML_EXTERN int toml_table_nkval(toml_table_t* tab); + +/* Return the number of arrays in a table */ +TOML_EXTERN int toml_table_narr(toml_table_t* tab); + +/* Return the number of sub-tables in a table */ +TOML_EXTERN int toml_table_ntab(toml_table_t* tab); + +/* Return the key of a table*/ +TOML_EXTERN const char* toml_table_key(toml_table_t* tab); + +/* Deref array by index. Return the element at idx or 0 if out of range. */ +TOML_EXTERN const char* toml_raw_at(toml_array_t* arr, int idx); +TOML_EXTERN toml_array_t* toml_array_at(toml_array_t* arr, int idx); +TOML_EXTERN toml_table_t* toml_table_at(toml_array_t* arr, int idx); + + +/* Raw to String. Caller must call free(ret) after use. + * Return 0 on success, -1 otherwise. + */ +TOML_EXTERN int toml_rtos(const char* s, char** ret); + +/* Raw to Boolean. Return 0 on success, -1 otherwise. */ +TOML_EXTERN int toml_rtob(const char* s, int* ret); + +/* Raw to Integer. Return 0 on success, -1 otherwise. */ +TOML_EXTERN int toml_rtoi(const char* s, int64_t* ret); + +/* Raw to Double. Return 0 on success, -1 otherwise. */ +TOML_EXTERN int toml_rtod(const char* s, double* ret); +/* Same as toml_rtod, but return the sanitized double in string form as well */ +TOML_EXTERN int toml_rtod_ex(const char* s, double* ret, char* buf, int buflen); + +/* Timestamp types. The year, month, day, hour, minute, second, z + * fields may be NULL if they are not relevant. e.g. In a DATE + * type, the hour, minute, second and z fields will be NULLs. + */ +typedef struct toml_timestamp_t toml_timestamp_t; +struct toml_timestamp_t { + struct { /* internal. do not use. */ + int year, month, day; + int hour, minute, second, millisec; + char z[10]; + } __buffer; + int *year, *month, *day; + int *hour, *minute, *second, *millisec; + char* z; +}; + +/* Raw to Timestamp. Return 0 on success, -1 otherwise. */ +TOML_EXTERN int toml_rtots(const char* s, toml_timestamp_t* ret); + +/* misc */ +TOML_EXTERN int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret); +TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]); +TOML_EXTERN void toml_set_memutil(void* (*xxmalloc)(size_t), + void (*xxfree)(void*), + void* (*xxcalloc)(size_t, size_t), + void* (*xxrealloc)(void*, size_t)); + +#endif /* TOML_H */ diff --git a/vendored/libtomlc99/toml_cat.c b/vendored/libtomlc99/toml_cat.c new file mode 100644 index 0000000..186ebf4 --- /dev/null +++ b/vendored/libtomlc99/toml_cat.c @@ -0,0 +1,193 @@ +/* +MIT License + +Copyright (c) 2017 CK Tan +https://github.com/cktan/tomlc99 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include "toml.h" + +typedef struct node_t node_t; +struct node_t { + const char* key; + toml_table_t* tab; +}; + +node_t stack[20]; +int stacktop = 0; + + +static void print_table_title(const char* arrname) +{ + int i; + printf("%s", arrname ? "[[" : "["); + for (i = 1; i < stacktop; i++) { + printf("%s", stack[i].key); + if (i + 1 < stacktop) + printf("."); + } + if (arrname) + printf(".%s]]\n", arrname); + else + printf("]\n"); +} + + +static void print_array_of_tables(toml_array_t* arr, const char* key); +static void print_array(toml_array_t* arr); + + +static void print_table(toml_table_t* curtab) +{ + int i; + const char* key; + const char* raw; + toml_array_t* arr; + toml_table_t* tab; + + + for (i = 0; 0 != (key = toml_key_in(curtab, i)); i++) { + if (0 != (raw = toml_raw_in(curtab, key))) { + printf("%s = %s\n", key, raw); + } else if (0 != (arr = toml_array_in(curtab, key))) { + if (toml_array_kind(arr) == 't') { + print_array_of_tables(arr, key); + } + else { + printf("%s = [\n", key); + print_array(arr); + printf(" ]\n"); + } + } else if (0 != (tab = toml_table_in(curtab, key))) { + stack[stacktop].key = key; + stack[stacktop].tab = tab; + stacktop++; + print_table_title(0); + print_table(tab); + stacktop--; + } else { + abort(); + } + } +} + +static void print_array_of_tables(toml_array_t* arr, const char* key) +{ + int i; + toml_table_t* tab; + printf("\n"); + for (i = 0; 0 != (tab = toml_table_at(arr, i)); i++) { + print_table_title(key); + print_table(tab); + printf("\n"); + } +} + + +static void print_array(toml_array_t* curarr) +{ + toml_array_t* arr; + const char* raw; + toml_table_t* tab; + int i; + + switch (toml_array_kind(curarr)) { + + case 'v': + for (i = 0; 0 != (raw = toml_raw_at(curarr, i)); i++) { + printf(" %d: %s,\n", i, raw); + } + break; + + case 'a': + for (i = 0; 0 != (arr = toml_array_at(curarr, i)); i++) { + printf(" %d: \n", i); + print_array(arr); + } + break; + + case 't': + for (i = 0; 0 != (tab = toml_table_at(curarr, i)); i++) { + print_table(tab); + } + printf("\n"); + break; + + case '\0': + break; + + default: + abort(); + } +} + + + +static void cat(FILE* fp) +{ + char errbuf[200]; + + toml_table_t* tab = toml_parse_file(fp, errbuf, sizeof(errbuf)); + if (!tab) { + fprintf(stderr, "ERROR: %s\n", errbuf); + return; + } + + stack[stacktop].tab = tab; + stack[stacktop].key = ""; + stacktop++; + print_table(tab); + stacktop--; + + toml_free(tab); +} + + +int main(int argc, const char* argv[]) +{ + int i; + if (argc == 1) { + cat(stdin); + } else { + for (i = 1; i < argc; i++) { + + FILE* fp = fopen(argv[i], "r"); + if (!fp) { + fprintf(stderr, "ERROR: cannot open %s: %s\n", + argv[i], strerror(errno)); + exit(1); + } + cat(fp); + fclose(fp); + } + } + return 0; +} diff --git a/vendored/libtomlc99/toml_json.c b/vendored/libtomlc99/toml_json.c new file mode 100644 index 0000000..d443cb5 --- /dev/null +++ b/vendored/libtomlc99/toml_json.c @@ -0,0 +1,214 @@ +/* + MIT License + + Copyright (c) 2017 CK Tan + https://github.com/cktan/tomlc99 + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include "toml.h" + + +static void print_escape_string(const char* s) +{ + for ( ; *s; s++) { + int ch = *s; + switch (ch) { + case '\b': printf("\\b"); break; + case '\t': printf("\\t"); break; + case '\n': printf("\\n"); break; + case '\f': printf("\\f"); break; + case '\r': printf("\\r"); break; + case '"': printf("\\\""); break; + case '\\': printf("\\\\"); break; + default: printf("%c", ch); break; + } + } +} + +static void print_raw(const char* s) +{ + char* sval; + int64_t ival; + int bval; + double dval; + toml_timestamp_t ts; + char dbuf[100]; + + if (0 == toml_rtos(s, &sval)) { + printf("{\"type\":\"string\",\"value\":\""); + print_escape_string(sval); + printf("\"}"); + free(sval); + } else if (0 == toml_rtoi(s, &ival)) { + printf("{\"type\":\"integer\",\"value\":\"%" PRId64 "\"}", ival); + } else if (0 == toml_rtob(s, &bval)) { + printf("{\"type\":\"bool\",\"value\":\"%s\"}", bval ? "true" : "false"); + } else if (0 == toml_rtod_ex(s, &dval, dbuf, sizeof(dbuf))) { + printf("{\"type\":\"float\",\"value\":\"%s\"}", dbuf); + } else if (0 == toml_rtots(s, &ts)) { + char millisec[10]; + if (ts.millisec) + sprintf(millisec, ".%d", *ts.millisec); + else + millisec[0] = 0; + if (ts.year && ts.hour) { + printf("{\"type\":\"datetime\",\"value\":\"%04d-%02d-%02dT%02d:%02d:%02d%s%s\"}", + *ts.year, *ts.month, *ts.day, *ts.hour, *ts.minute, *ts.second, + millisec, + (ts.z ? ts.z : "")); + } else if (ts.year) { + printf("{\"type\":\"date\",\"value\":\"%04d-%02d-%02d\"}", + *ts.year, *ts.month, *ts.day); + } else if (ts.hour) { + printf("{\"type\":\"time\",\"value\":\"%02d:%02d:%02d%s\"}", + *ts.hour, *ts.minute, *ts.second, millisec); + } + } else { + fprintf(stderr, "unknown type\n"); + exit(1); + } +} + + +static void print_array(toml_array_t* arr); +static void print_table(toml_table_t* curtab) +{ + int i; + const char* key; + const char* raw; + toml_array_t* arr; + toml_table_t* tab; + + + printf("{"); + for (i = 0; 0 != (key = toml_key_in(curtab, i)); i++) { + + printf("%s\"", i > 0 ? "," : ""); + print_escape_string(key); + printf("\":"); + + if (0 != (raw = toml_raw_in(curtab, key))) { + print_raw(raw); + } else if (0 != (arr = toml_array_in(curtab, key))) { + print_array(arr); + } else if (0 != (tab = toml_table_in(curtab, key))) { + print_table(tab); + } else { + abort(); + } + } + printf("}"); +} + +static void print_table_array(toml_array_t* curarr) +{ + int i; + toml_table_t* tab; + + printf("["); + for (i = 0; 0 != (tab = toml_table_at(curarr, i)); i++) { + printf("%s", i > 0 ? "," : ""); + print_table(tab); + } + printf("]"); +} + +static void print_array(toml_array_t* curarr) +{ + toml_array_t* arr; + const char* raw; + int i; + + if (toml_array_kind(curarr) == 't') { + print_table_array(curarr); + return; + } + + printf("{\"type\":\"array\",\"value\":["); + switch (toml_array_kind(curarr)) { + + case 'v': + for (i = 0; 0 != (raw = toml_raw_at(curarr, i)); i++) { + printf("%s", i > 0 ? "," : ""); + print_raw(raw); + } + break; + + case 'a': + for (i = 0; 0 != (arr = toml_array_at(curarr, i)); i++) { + printf("%s", i > 0 ? "," : ""); + print_array(arr); + } + break; + + default: + break; + } + printf("]}"); +} + + + +static void cat(FILE* fp) +{ + char errbuf[200]; + + toml_table_t* tab = toml_parse_file(fp, errbuf, sizeof(errbuf)); + if (!tab) { + fprintf(stderr, "ERROR: %s\n", errbuf); + exit(1); + } + + print_table(tab); + printf("\n"); + + toml_free(tab); +} + + +int main(int argc, const char* argv[]) +{ + int i; + if (argc == 1) { + cat(stdin); + } else { + for (i = 1; i < argc; i++) { + + FILE* fp = fopen(argv[i], "r"); + if (!fp) { + fprintf(stderr, "ERROR: cannot open %s: %s\n", + argv[i], strerror(errno)); + exit(1); + } + cat(fp); + fclose(fp); + } + } + return 0; +} From 8be860f8274feebea189c48cc11d14e1410bd024 Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Tue, 14 Oct 2025 19:30:42 -0700 Subject: [PATCH 2/5] libutil: pull in timestamp and unit test Problem: libflux-conf will require timestamp utils from flux-core. Pull them in with unit test. --- src/libutil/Makefile.am | 11 +- src/libutil/test/timestamp.c | 306 +++++++++++++++++++++++++++++++++++ src/libutil/timestamp.c | 182 +++++++++++++++++++++ src/libutil/timestamp.h | 56 +++++++ 4 files changed, 553 insertions(+), 2 deletions(-) create mode 100644 src/libutil/test/timestamp.c create mode 100644 src/libutil/timestamp.c create mode 100644 src/libutil/timestamp.h diff --git a/src/libutil/Makefile.am b/src/libutil/Makefile.am index deb80ea..80cf89a 100644 --- a/src/libutil/Makefile.am +++ b/src/libutil/Makefile.am @@ -21,12 +21,15 @@ libutil_la_SOURCES = \ parse_size.h \ parse_size.c \ errprintf.h \ - errprintf.c + errprintf.c \ + timestamp.h \ + timestamp.c TESTS = \ test_errprintf.t \ test_fsd.t \ - test_parse_size.t + test_parse_size.t \ + test_timestamp.t test_ldadd = \ $(builddir)/libutil.la \ @@ -55,3 +58,7 @@ test_fsd_t_LDADD = $(test_ldadd) test_parse_size_t_SOURCES = test/parse_size.c test_parse_size_t_CPPFLAGS = $(test_cppflags) test_parse_size_t_LDADD = $(test_ldadd) + +test_timestamp_t_SOURCES = test/timestamp.c +test_timestamp_t_CPPFLAGS = $(test_cppflags) +test_timestamp_t_LDADD = $(test_ldadd) diff --git a/src/libutil/test/timestamp.c b/src/libutil/test/timestamp.c new file mode 100644 index 0000000..5194010 --- /dev/null +++ b/src/libutil/test/timestamp.c @@ -0,0 +1,306 @@ +/************************************************************\ + * Copyright 2023 Lawrence Livermore National Security, LLC + * (c.f. AUTHORS, NOTICE.LLNS, COPYING) + * + * This file is part of the Flux resource manager framework. + * For details, see https://github.com/flux-framework. + * + * SPDX-License-Identifier: LGPL-3.0 +\************************************************************/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "vendored/libtap/tap.h" +#include "timestamp.h" + +struct test_entry { + const char *entry; + time_t ts; + int sec; + int min; + int hour; + int mday; + int mon; + int year; + suseconds_t us; +}; + +/* N.B.: All expected outputs assume TZ=PST8PDT + */ +struct test_entry tests[] = { + { "2017-03-17T04:11:45.948349Z", + 1489723905, + 45, 11, 21, 16, 3, 2017, 948349 + }, + { "2020-06-05T23:34:22.960708Z", + 1591400062, + 22, 34, 16, 5, 6, 2020, 960708 + }, + { "1977-10-18T15:30:37.53737Z", + 246036637, + 37, 30, 8, 18, 10, 1977, 537370 + }, + { "1971-11-02T15:18:03.191981Z", + 57943083, + 3, 18, 7, 2, 11, 1971, 191981 + }, + { "1996-12-17T15:23:31.253948Z", + 850836211, + 31, 23, 7, 17, 12, 1996, 253948 + }, + { "2013-10-11T11:46:10.907826Z", + 1381491970, + 10, 46, 4, 11, 10, 2013, 907826 + }, + { "2011-02-03T07:44:19.881821Z", + 1296719059, + 19, 44, 23, 2, 2, 2011, 881821 + }, + { "1979-07-28T05:59:14.035254Z", + 301989554, + 14, 59, 22, 27, 7, 1979, 35254 + }, + { "1977-10-22T14:17:21.905639Z", + 246377841, + 21, 17, 7, 22, 10, 1977, 905639 + }, + { "2013-02-27T20:00:39.353657Z", + 1361995239, + 39, 0, 12, 27, 2, 2013, 353657 + }, + { "2023-04-08T23:14:34.029081Z", + 1680995674, + 34, 14, 16, 8, 4, 2023, 29081 + }, + { "2013-01-29T02:36:38.527697Z", + 1359426998, + 38, 36, 18, 28, 1, 2013, 527697 + }, + { "1996-11-12T23:58:38.277011Z", + 847843118, + 38, 58, 15, 12, 11, 1996, 277011 + }, + { "2007-01-27T18:13:58.749355Z", + 1169921638, + 58, 13, 10, 27, 1, 2007, 749355 + }, + { "1985-01-11T05:51:23.032399Z", + 474270683, + 23, 51, 21, 10, 1, 1985, 32399 + }, + { "1971-06-26T06:41:19.743417Z", + 46766479, + 19, 41, 23, 25, 6, 1971, 743417 + }, + { "1996-08-05T05:31:01.268064Z", + 839223061, + 1, 31, 22, 4, 8, 1996, 268064 + }, + { "2000-02-23T12:13:17.427706Z", + 951307997, + 17, 13, 4, 23, 2, 2000, 427706 + }, + { "1985-04-07T00:31:25.608501Z", + 481681885, + 25, 31, 16, 6, 4, 1985, 608501 + }, + { "1970-04-21T12:58:31.529143Z", + 9550711, + 31, 58, 4, 21, 4, 1970, 529143 + }, + { "1978-11-22T13:16:29.795159Z", + 280588589, + 29, 16, 5, 22, 11, 1978, 795159 + }, + { "1984-11-07T12:10:05.840087Z", + 468677405, + 5, 10, 4, 7, 11, 1984, 840087 + }, + { "1987-11-06T22:33:15.153931Z", + 563236395, + 15, 33, 14, 6, 11, 1987, 153931 + }, + { "1979-11-23T00:55:52.367158Z", + 312166552, + 52, 55, 16, 22, 11, 1979, 367158 + }, + { "1972-10-19T17:02:31.682269Z", + 88362151, + 31, 2, 10, 19, 10, 1972, 682269 + }, + { "2001-12-27T10:13:29.52Z", + 1009448009, + 29, 13, 2, 27, 12, 2001, 520000 + }, + { "1984-10-30T10:49:56.3Z", + 467981396, + 56, 49, 2, 30, 10, 1984, 300000 + }, + { "1989-04-14T05:06:09.000003Z", + 608533569, + 9, 6, 22, 13, 4, 1989, 3 + }, + { "1983-03-16T23:04:03.00003Z", + 416703843, + 3, 4, 15, 16, 3, 1983, 30 + }, + { "1988-05-11T02:47:16.003Z", + 579322036, + 16, 47, 19, 10, 5, 1988, 3000 + }, + { "1970-01-01T00:00:00.836367Z", + 0, + 0, 0, 16, 31, 12, 1969, 836367 + }, + { "1970-01-01T00:00:00.000000Z", + 0, + 0, 0, 16, 31, 12, 1969, 0 + }, + { "2011-08-28T16:30:40.000000Z", + 1314549040, + 40, 30, 9, 28, 8, 2011, 0 + }, + { "1970-01-01T00:00:00Z", + 0, + 0, 0, 16, 31, 12, 1969, 0 + }, + { "2017-01-14T05:18:47Z", + 1484371127, + 47, 18, 21, 13, 1, 2017, 0 + }, + { NULL, 0, 0, 0, 0, 0, 0, 0, 0 } +}; + +static void test_invalid () +{ + struct tm tm; + struct timeval tv; + struct test_entry *te = &tests[0]; + + ok (timestamp_parse ("", &tm, &tv) < 0, + "timestamp_parse empty string fails"); + ok (timestamp_parse ("1:00", &tm, &tv) < 0, + "timestamp_parse on invalid timestamp fails"); + ok (timestamp_parse ("1969-01-01T00:00:00Z", &tm, &tv) < 0, + "timestamp_parse on too old timestamp fails"); + + ok (timestamp_parse (NULL, NULL, NULL) < 0 && errno == EINVAL, + "timestamp_parse (NULL, NULL, NULL) fails with EINVAL"); + ok (timestamp_parse (NULL, &tm, &tv) < 0 && errno == EINVAL, + "timestamp_parse (NULL, &tm, &tv) fails with EINVAL"); + ok (timestamp_parse (te->entry, NULL, NULL) < 0 && errno == EINVAL, + "timestamp_parse (NULL, &tm, &tv) fails with EINVAL"); + + ok (timestamp_from_double (-1., &tm, &tv) < 0 && errno == EINVAL, + "timestamp_from_double (-1, &tm, &tv) fails with EINVAL"); + ok (timestamp_from_double (0., NULL, NULL) < 0 && errno == EINVAL, + "timestamp_from_double (0., NULL, NULL) fails with EINVAL"); + + ok (timestamp_parse (te->entry, &tm, NULL) == 0, + "timestamp_parse (ts, &tm, NULL) works"); + ok (tm.tm_year == te->year - 1900 + && tm.tm_mon == te->mon - 1 + && tm.tm_mday == te->mday + && tm.tm_min == te->min + && tm.tm_sec == te->sec, + "timestamp is expected values"); + + ok (timestamp_parse (te->entry, NULL, &tv) == 0, + "timestamp_parse (ts, NULL, &tv) works"); + ok (tv.tv_sec == te->ts + && tv.tv_usec == te->us, + "timsestamp is expected value"); +} + +static void test_entry_check (struct test_entry *test, + struct tm tm, + struct timeval tv) +{ + ok (tm.tm_sec == test->sec, + "tm_sec == %d (expected %d)", tm.tm_sec, test->sec); + ok (tm.tm_min == test->min, + "tm_min == %d (expected %d)", tm.tm_min, test->min); + /* N.B.: We do not test tm_hour since this may be influenced + * by incorrect, missing, or updated DST values in the local + * system's tzdata. + ok (tm.tm_mday == test->mday, + "tm_mday == %d (expected %d)", tm.tm_mday, test->mday); + */ + /* tm_mon is months since Jan 0-11 + */ + ok (tm.tm_mon == test->mon - 1, + "tm_mon == %d (expected %d)", tm.tm_mon, test->mon - 1); + /* tm_year is number of years since 1900 + */ + ok (tm.tm_year == test->year - 1900, + "tm_year == %d (expected %d)", tm.tm_year, test->year - 1900); + + ok (tv.tv_sec == test->ts, + "tv_sec == %u (expected %u)", tv.tv_sec, test->ts); + ok (tv.tv_usec == test->us, + "tv_usec == %u (expected %u)", tv.tv_usec, test->us); +} + +static void test_all () +{ + struct test_entry *test; + struct tm tm; + struct timeval tv; + + test = tests; + while (test->entry) { + char buf[1024]; + ok (timestamp_parse (test->entry, &tm, &tv) == 0, + "timestamp_parse: %s", test->entry); + timestamp_tostr ((time_t) tv.tv_sec, buf, sizeof (buf)); + diag ("%s", buf); + test_entry_check (test, tm, tv); + + /* Now test timestamp_from_double() + */ + double ts = tv.tv_sec + ((double) tv.tv_usec / 1000000); + + memset (&tm, 0, sizeof (tm)); + memset (&tv, 0, sizeof (tv)); + ok (timestamp_from_double (ts, &tm, &tv) == 0, + "timestamp_from_double (%f) works", + ts); + test_entry_check (test, tm, tv); + + ++test; + } +} + +void test_tzoffset (void) +{ + struct tm tm; + + ok (timestamp_tzoffset (NULL, NULL, 0) < 0 && errno == EINVAL, + "timestamp_tzoffset (NULL, NULL, 0) returns EINVAL"); + + memset (&tm, 0, sizeof (tm)); + ok (timestamp_tzoffset (&tm, NULL, 0) < 0 && errno == EINVAL, + "timestamp_tzoffset (&tm, NULL, 0) returns EINVAL"); +} + +int main (int argc, char *argv[]) +{ + + plan (NO_PLAN); + + /* All expected outputs assume a timezone of PST8PDT */ + setenv ("TZ", "PST8PDT", 1); + + test_all (); + test_invalid (); + test_tzoffset (); + + done_testing (); +} + +// vi:ts=4 sw=4 expandtab diff --git a/src/libutil/timestamp.c b/src/libutil/timestamp.c new file mode 100644 index 0000000..6d0c969 --- /dev/null +++ b/src/libutil/timestamp.c @@ -0,0 +1,182 @@ +/************************************************************\ + * Copyright 2017 Lawrence Livermore National Security, LLC + * (c.f. AUTHORS, NOTICE.LLNS, COPYING) + * + * This file is part of the Flux resource manager framework. + * For details, see https://github.com/flux-framework. + * + * SPDX-License-Identifier: LGPL-3.0 +\************************************************************/ + +#if HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include +#include +#include +#include + +#include "timestamp.h" +#include "ccan/str/str.h" + +/* + * GNU libc has timegm(3), but the manual states: + * + * These functions [timelocal(), timegm()] are nonstandard GNU extensions + * that are also present on the BSDs. Avoid their use. + * + * This "portable" version was found on sourceware.org, and appears to work: + * + * https://patchwork.sourceware.org/project/glibc/patch/20211011115406.11430-2-alx.manpages@gmail.com/ + * + */ +static time_t portable_timegm (struct tm *tm) +{ + time_t t; + + tm->tm_isdst = 0; + if ((t = mktime (tm)) == (time_t) -1) + return t; + return t - timezone; +} + +int timestamp_tostr (time_t t, char *buf, int size) +{ + struct tm tm; + if (t < 0 || !gmtime_r (&t, &tm)) + return -1; + if (strftime (buf, size, "%Y-%m-%dT%TZ", &tm) == 0) + return -1; + return 0; +} + +int timestamp_fromstr (const char *s, time_t *tp) +{ + struct tm tm; + time_t t; + if (!strptime (s, "%Y-%m-%dT%TZ", &tm)) + return -1; + if ((t = portable_timegm (&tm)) < 0) + return -1; + if (tp) + *tp = t; + return 0; +} + +int timestamp_parse (const char *s, + struct tm *tm, + struct timeval *tv) +{ + char *extra; + struct tm gm_tm; + time_t t; + + if (s == NULL || (!tm && !tv)) { + errno = EINVAL; + return -1; + } + + if (!(extra = strptime (s, "%Y-%m-%dT%T", &gm_tm)) + || (t = portable_timegm (&gm_tm)) < (time_t) -1) { + errno = EINVAL; + return -1; + } + + if (tm && !(localtime_r (&t, tm))) + return -1; + + if (tv) + tv->tv_sec = t; + + if (tv && extra[0] == '.') { + char *endptr; + double d; + + errno = 0; + d = strtod (extra, &endptr); + + /* Note: in this implementation, there should be a "Z" after the + * timestamp to indicate UTC or "Zulu" time. + */ + if (errno != 0 || *endptr != 'Z') + return -1; + + /* Note: cast to integer type truncates. To handle underflow from + * double arithmetic (e.g. result = 1234.999), add 0.5 and then + * allow the truncation to simulate floor(3). + */ + tv->tv_usec = (d * 1000000) + 0.5; + } + return 0; +} + +int timestamp_from_double (double ts, struct tm *tm, struct timeval *tv) +{ + if (ts < 0. || (!tm && !tv)) { + errno = EINVAL; + return -1; + } + if (tm) { + time_t t = (time_t) ts; + memset (tm, 0, sizeof (*tm)); + if (!localtime_r (&t, tm)) + return -1; + } + if (tv) { + tv->tv_sec = ts; + /* Note: cast to integer type truncates. To handle underflow from + * double arithmetic (e.g. result = 1234.999), add 0.5 and then + * allow the truncation to simulate floor(3). + */ + tv->tv_usec = ((ts - tv->tv_sec) * 1000000) + 0.5; + } + return 0; +} + +int timestamp_tzoffset (struct tm *tm, char *buf, int len) +{ + if (!tm || !buf || len <= 0) { + errno = EINVAL; + return -1; + } + if (strftime (buf, len, "%z", tm) == 0) + return -1; + /* Special case: use "Z" for UTC for backwards compatibility + */ + if (streq (buf, "+0000")) { + buf[0] = 'Z'; + buf[1] = '\0'; + return 0; + } + /* O/w, insert `:` in offset if it is of the form [+-]NNNN for + * enhanced readability. + */ + if (strlen (buf) == 5 + && len >= 7 + && (buf[0] == '-' || buf[0] == '+')) { + char minutes[3]; + + /* Save last two characters of current tz string: + * (Note: due to strlen check above, we know this only copies + * 2 characters, so it is safe to use strcpy()) + */ + strcpy (minutes, buf+3); + + /* Insert colon after [+-]NN + */ + buf[3] = ':'; + + /* Copy minutes back to end of string. + * (Note: we know minutes only contains 2 characters, so it is + * safe to use strcpy()) + */ + strcpy (buf+4, minutes); + } + return 0; +} + + +/* + * vi:tabstop=4 shiftwidth=4 expandtab + */ diff --git a/src/libutil/timestamp.h b/src/libutil/timestamp.h new file mode 100644 index 0000000..b901965 --- /dev/null +++ b/src/libutil/timestamp.h @@ -0,0 +1,56 @@ +/************************************************************\ + * Copyright 2017 Lawrence Livermore National Security, LLC + * (c.f. AUTHORS, NOTICE.LLNS, COPYING) + * + * This file is part of the Flux resource manager framework. + * For details, see https://github.com/flux-framework. + * + * SPDX-License-Identifier: LGPL-3.0 +\************************************************************/ + +#ifndef _UTIL_TIMESTAMP_H +#define _UTIL_TIMESTAMP_H + +#include +#include + +/* Convert time_t (GMT) to ISO 8601 timestamp string, + * e.g. "2003-08-24T05:14:50Z" + */ +int timestamp_tostr (time_t t, char *buf, int size); + +/* Convert from ISO 8601 string to time_t. + */ +int timestamp_fromstr (const char *s, time_t *tp); + +/* Convert from ISO 8601 timestamp string, including optional + * microsecond precision, to struct tm, timeval pair. + * + * e.g. "2022-10-15T14:43:18.159009Z" + * + * At least one of 'tm' or 'tv' must be provided. + */ +int timestamp_parse (const char *s, + struct tm *tm, + struct timeval *tv); + + +/* Convert a double precision timestamp to struct tm and timeval. + * At least one of 'tm' or 'tv' must be provided. + */ +int timestamp_from_double (double ts, struct tm *tm, struct timeval *tv); + +/* Get the current timezone offset for `tm` in the form [+-]HH:MM + * and place it into the supplied buffer. As a special case, +00:00 + * is converted to "Z" (Zulu time) for backwards compatibility when + * the current timezone is UTC. + * + * Returns -1 on failure, 0 for success. + */ +int timestamp_tzoffset (struct tm *tm, char *buf, int size); + +#endif /* !_UTIL_TIMESTAMP_H */ + +/* + * vi:tabstop=4 shiftwidth=4 expandtab + */ From 10043cf9c47ec279ffb2fd9a38e572948941688b Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Tue, 14 Oct 2025 19:31:45 -0700 Subject: [PATCH 3/5] libconf: pull in flux_conf_t from flux-core Problem: the flux_conf_t class can be useful in multiple framework projects. Pull it in from libflux with its helper tomltk from libutil. Pull in their unit tests. --- Makefile.am | 1 + configure.ac | 1 + src/libconf/Makefile.am | 67 +++++++ src/libconf/conf.c | 362 +++++++++++++++++++++++++++++++++ src/libconf/conf.h | 59 ++++++ src/libconf/conf_private.h | 24 +++ src/libconf/test/conf.c | 399 +++++++++++++++++++++++++++++++++++++ src/libconf/test/tomltk.c | 273 +++++++++++++++++++++++++ src/libconf/tomltk.c | 345 ++++++++++++++++++++++++++++++++ src/libconf/tomltk.h | 70 +++++++ 10 files changed, 1601 insertions(+) create mode 100644 src/libconf/Makefile.am create mode 100644 src/libconf/conf.c create mode 100644 src/libconf/conf.h create mode 100644 src/libconf/conf_private.h create mode 100644 src/libconf/test/conf.c create mode 100644 src/libconf/test/tomltk.c create mode 100644 src/libconf/tomltk.c create mode 100644 src/libconf/tomltk.h diff --git a/Makefile.am b/Makefile.am index 3943ec0..96ce9fe 100644 --- a/Makefile.am +++ b/Makefile.am @@ -10,6 +10,7 @@ SUBDIRS = \ src/libhostlist \ src/libidset \ src/liboptparse \ + src/libconf \ doc EXTRA_DIST = \ diff --git a/configure.ac b/configure.ac index 633451d..672fd8b 100644 --- a/configure.ac +++ b/configure.ac @@ -108,6 +108,7 @@ AC_CONFIG_FILES( \ src/libidset/flux-idset.pc \ src/liboptparse/Makefile \ src/liboptparse/flux-optparse.pc \ + src/libconf/Makefile \ doc/Makefile \ doc/test/Makefile \ ) diff --git a/src/libconf/Makefile.am b/src/libconf/Makefile.am new file mode 100644 index 0000000..da3ce2e --- /dev/null +++ b/src/libconf/Makefile.am @@ -0,0 +1,67 @@ +AM_CFLAGS = \ + $(WARNING_CFLAGS) \ + $(CODE_COVERAGE_CFLAGS) + +AM_LDFLAGS = \ + $(CODE_COVERAGE_LIBS) + +AM_CPPFLAGS = \ + -I$(top_srcdir) \ + -I$(top_srcdir)/vendored/libccan \ + -I$(top_srcdir)/src/include \ + $(JANSSON_CFLAGS) \ + $(CODE_COVERAGE_CPPFLAGS) + +noinst_LTLIBRARIES = \ + libconf.la + +fluxinclude_HEADERS = \ + conf.h + +lib_LTLIBRARIES = \ + libflux-conf.la + +libflux_conf_la_SOURCES = +libflux_conf_la_LIBADD = \ + $(builddir)/libconf.la \ + $(top_builddir)/src/libutil/libutil.la \ + $(top_builddir)/vendored/libtomlc99/libtomlc99.la \ + $(JANSSON_LIBS) +libflux_conf_la_LDFLAGS = \ + -export-symbols-regex "^(flux_conf_|__asan)" \ + -version-info 1:0:0 \ + -shared -export-dynamic --disable-static \ + $(ld_gc_sections) \ + $(san_ld_zdef_flag) + +libconf_la_SOURCES = \ + conf.c \ + conf_private.h \ + tomltk.h \ + tomltk.c + +TESTS = \ + test_tomltk.t \ + test_conf.t + +check_PROGRAMS = \ + $(TESTS) + +test_ldadd = \ + $(top_builddir)/vendored/libtap/libtap.la \ + $(builddir)/libconf.la \ + $(top_builddir)/src/libutil/libutil.la \ + $(top_builddir)/vendored/libtomlc99/libtomlc99.la \ + $(JANSSON_LIBS) + +TEST_EXTENSIONS = .t +T_LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) \ + $(top_srcdir)/config/tap-driver.sh + +test_tomltk_t_SOURCES = test/tomltk.c +test_tomltk_t_CPPFLAGS = $(AM_CPPFLAGS) +test_tomltk_t_LDADD = $(test_ldadd) + +test_conf_t_SOURCES = test/conf.c +test_conf_t_CPPFLAGS = $(AM_CPPFLAGS) +test_conf_t_LDADD = $(test_ldadd) diff --git a/src/libconf/conf.c b/src/libconf/conf.c new file mode 100644 index 0000000..16b568a --- /dev/null +++ b/src/libconf/conf.c @@ -0,0 +1,362 @@ +/************************************************************\ + * Copyright 2014 Lawrence Livermore National Security, LLC + * (c.f. AUTHORS, NOTICE.LLNS, COPYING) + * + * This file is part of the Flux resource manager framework. + * For details, see https://github.com/flux-framework. + * + * SPDX-License-Identifier: LGPL-3.0 +\************************************************************/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if !HAVE_JSON_OBJECT_UPDATE_RECURSIVE +#include "src/libmissing/json_object_update_recursive.h" +#endif + +#include "vendored/libtomlc99/toml.h" +#include "ccan/str/str.h" +#include "src/libutil/errno_safe.h" +#include "src/libutil/errprintf.h" + +#include "tomltk.h" + +#include "conf_private.h" +#include "conf.h" + +struct flux_conf { + json_t *obj; + int refcount; +}; + +void flux_conf_decref (const flux_conf_t *conf_const) +{ + flux_conf_t *conf = (flux_conf_t *)conf_const; + if (conf && --conf->refcount == 0) { + ERRNO_SAFE_WRAP (json_decref, conf->obj); + ERRNO_SAFE_WRAP (free, conf); + } +} + +const flux_conf_t *flux_conf_incref (const flux_conf_t *conf_const) +{ + flux_conf_t *conf = (flux_conf_t *)conf_const; + + if (conf) + conf->refcount++; + return conf; +} + +flux_conf_t *flux_conf_create (void) +{ + flux_conf_t *conf; + + if (!(conf = calloc (1, sizeof (*conf)))) + return NULL; + conf->refcount = 1; + if (!(conf->obj = json_object ())) { + flux_conf_decref (conf); + errno = ENOMEM; + return NULL; + } + return conf; +} + +flux_conf_t *flux_conf_copy (const flux_conf_t *conf) +{ + flux_conf_t *cpy; + + if (!(cpy = flux_conf_create ())) + return NULL; + json_decref (cpy->obj); + if (!(cpy->obj = json_deep_copy (conf->obj))) { + flux_conf_decref (cpy); + return NULL; + } + return cpy; +} + +static int conf_update_obj (flux_conf_t *conf, + const char *filename, + json_t *obj, + flux_error_t *error) +{ + if (json_object_update_recursive (conf->obj, obj) < 0) { + errprintf (error, + "%s: updating JSON object: out of memory", + filename); + errno = ENOMEM; + return -1; + } + return 0; +} + +static int conf_update_toml (flux_conf_t *conf, + const char *filename, + flux_error_t *error) +{ + struct tomltk_error toml_error; + toml_table_t *tab; + json_t *obj = NULL; + + if (!(tab = tomltk_parse_file (filename, &toml_error))) { + if (toml_error.lineno == -1) { + errprintf (error, + "%s: %s", + toml_error.filename, + toml_error.errbuf); + } + else { + errprintf (error, + "%s:%d: %s", + toml_error.filename, + toml_error.lineno, + toml_error.errbuf); + } + goto error; + } + if (!(obj = tomltk_table_to_json (tab))) { + errprintf (error, + "%s: converting TOML to JSON: %s", + filename, + strerror (errno)); + goto error; + } + if (conf_update_obj (conf, filename, obj, error) < 0) + goto error; + json_decref (obj); + toml_free (tab); + return 0; +error: + ERRNO_SAFE_WRAP (toml_free, tab); + ERRNO_SAFE_WRAP (json_decref, obj); + return -1; +} + +static int conf_update_json (flux_conf_t *conf, + const char *filename, + flux_error_t *error) +{ + json_error_t err; + json_t *obj = NULL; + + if (!(obj = json_load_file (filename, 0, &err))) { + errprintf (error, "%s:%d: %s", filename, err.line, err.text); + goto error; + } + if (conf_update_obj (conf, filename, obj, error) < 0) + goto error; + json_decref (obj); + return 0; +error: + ERRNO_SAFE_WRAP (json_decref, obj); + return -1; +} + +static const char *file_extension (const char *path) +{ + const char *p; + if (path && (p = strrchr (path, '.'))) + return p + 1; + return ""; +} + +static int conf_update (flux_conf_t *conf, + const char *filename, + flux_error_t *error) +{ + const char *ext = file_extension (filename); + if (streq (ext, "json")) + return conf_update_json (conf, filename, error); + return conf_update_toml (conf, filename, error); +} + +struct globerr { + int rc; + const char *msg; + int errnum; +}; + +struct globerr globerr_tab[] = { + { GLOB_NOMATCH, "No match", ENOENT }, + { GLOB_NOSPACE, "Out of memory", ENOMEM }, + { GLOB_ABORTED, "Read error", EINVAL }, + { 0, "Unknown glob error", EINVAL }, +}; + +/* Set errno and optionally 'error' based on glob error return code. + */ +void conf_globerr (flux_error_t *error, const char *pattern, int rc) +{ + struct globerr *entry; + for (entry = &globerr_tab[0]; entry->rc != 0; entry++) { + if (rc == entry->rc) + break; + } + errprintf (error, "%s: %s", pattern, entry->msg); + errno = entry->errnum; +} + +static flux_conf_t *conf_parse_dir (const char *path, flux_error_t *error) +{ + flux_conf_t *conf; + glob_t gl; + int rc; + size_t i; + char pattern[4096]; + + if (access (path, R_OK | X_OK) < 0) { + errprintf (error, "%s: %s", path, strerror (errno)); + return NULL; + } + rc = snprintf (pattern, sizeof (pattern), "%s/*.toml", path); + if (rc >= sizeof (pattern)) { + errno = EOVERFLOW; + errprintf (error, "%s: %s", path, strerror (errno)); + return NULL; + } + if (!(conf = flux_conf_create ())) { + errprintf (error, "%s: %s", path, strerror (errno)); + return NULL; + } + rc = glob (pattern, GLOB_ERR, NULL, &gl); + if (rc == 0) { + for (i = 0; i < gl.gl_pathc; i++) { + if (conf_update_toml (conf, gl.gl_pathv[i], error) < 0) + goto error_glob; + } + globfree (&gl); + } + else if (rc != GLOB_NOMATCH) { + conf_globerr (error, pattern, rc); + goto error; + } + return conf; + +error_glob: + ERRNO_SAFE_WRAP (globfree, &gl); +error: + flux_conf_decref (conf); + return NULL; +} + +static flux_conf_t *conf_parse_file (const char *path, flux_error_t *error) +{ + flux_conf_t *conf; + + if (!(conf = flux_conf_create ())) { + errprintf (error, "%s: %s", path, strerror (errno)); + return NULL; + } + if (conf_update (conf, path, error) < 0) { + flux_conf_decref (conf); + return NULL; + } + return conf; +} + +flux_conf_t *flux_conf_parse (const char *path, flux_error_t *error) +{ + struct stat st; + + if (!path) { + errno = EINVAL; + errprintf (error, "%s", strerror (errno)); + return NULL; + } + if (stat (path, &st) < 0) { + errprintf (error, "stat: %s: %s", path, strerror (errno)); + return NULL; + } + if (S_ISDIR(st.st_mode)) + return conf_parse_dir (path, error); + return conf_parse_file (path, error); +} + +int flux_conf_vunpack (const flux_conf_t *conf, + flux_error_t *error, + const char *fmt, + va_list ap) +{ + json_error_t j_error; + + if (!conf || !conf->obj || !fmt || *fmt == '\0') { + errno = EINVAL; + errprintf (error, "%s", strerror (errno)); + return -1; + } + if (json_vunpack_ex (conf->obj, &j_error, 0, fmt, ap) < 0) { + /* N.B. Cannot translate JSON error back to TOML file/line, + * so only the text[] portion of the JSON error is used here. + */ + errprintf (error, "%s", j_error.text); + errno = EINVAL; + return -1; + } + return 0; +} + +int flux_conf_unpack (const flux_conf_t *conf, + flux_error_t *error, + const char *fmt, ...) +{ + va_list ap; + int rc; + + va_start (ap, fmt); + rc = flux_conf_vunpack (conf, error, fmt, ap); + va_end (ap); + + return rc; +} + +flux_conf_t *flux_conf_vpack (const char *fmt, va_list ap) +{ + flux_conf_t *conf; + + if (!fmt) { + errno = EINVAL; + return NULL; + } + if (!(conf = flux_conf_create ())) + return NULL; + json_decref (conf->obj); + if (!(conf->obj = json_vpack_ex (NULL, 0, fmt, ap))) { + flux_conf_decref (conf); + errno = EINVAL; + return NULL; + } + + return conf; +} + +flux_conf_t *flux_conf_pack (const char *fmt, ...) +{ + va_list ap; + flux_conf_t *conf; + + if (!fmt) { + errno = EINVAL; + return NULL; + } + va_start (ap, fmt); + conf = flux_conf_vpack (fmt, ap); + va_end (ap); + + return conf; +} + +/* + * vi:tabstop=4 shiftwidth=4 expandtab + */ diff --git a/src/libconf/conf.h b/src/libconf/conf.h new file mode 100644 index 0000000..fac2ba4 --- /dev/null +++ b/src/libconf/conf.h @@ -0,0 +1,59 @@ +/************************************************************\ + * Copyright 2014 Lawrence Livermore National Security, LLC + * (c.f. AUTHORS, NOTICE.LLNS, COPYING) + * + * This file is part of the Flux resource manager framework. + * For details, see https://github.com/flux-framework. + * + * SPDX-License-Identifier: LGPL-3.0 +\************************************************************/ + +#ifndef _FLUX_CONF_H +#define _FLUX_CONF_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct flux_conf flux_conf_t; + +/* Create/copy/incref/decref config object + */ +flux_conf_t *flux_conf_create (void); +flux_conf_t *flux_conf_copy (const flux_conf_t *conf); +const flux_conf_t *flux_conf_incref (const flux_conf_t *conf); +void flux_conf_decref (const flux_conf_t *conf); + +/* Parse TOML config in 'path' and return a new flux_conf_t on success. + * If path is a directory, then parse all files matching *.toml in path. + */ +flux_conf_t *flux_conf_parse (const char *path, flux_error_t *error); + +/* Access config object. + * If error is non-NULL, it is filled with error details on failure. + */ +int flux_conf_vunpack (const flux_conf_t *conf, + flux_error_t *error, + const char *fmt, + va_list ap); + +int flux_conf_unpack (const flux_conf_t *conf, + flux_error_t *error, + const char *fmt, + ...); + +flux_conf_t *flux_conf_vpack (const char *fmt, va_list ap); +flux_conf_t *flux_conf_pack (const char *fmt, ...); + +#ifdef __cplusplus +} +#endif + +#endif /* !_FLUX_CONF_H */ + +/* + * vi:tabstop=4 shiftwidth=4 expandtab + */ diff --git a/src/libconf/conf_private.h b/src/libconf/conf_private.h new file mode 100644 index 0000000..8414018 --- /dev/null +++ b/src/libconf/conf_private.h @@ -0,0 +1,24 @@ +/************************************************************\ + * Copyright 2019 Lawrence Livermore National Security, LLC + * (c.f. AUTHORS, NOTICE.LLNS, COPYING) + * + * This file is part of the Flux resource manager framework. + * For details, see https://github.com/flux-framework. + * + * SPDX-License-Identifier: LGPL-3.0 +\************************************************************/ + +#ifndef _FLUX_CONF_PRIVATE_H +#define _FLUX_CONF_PRIVATE_H + +#include "conf.h" + +/* Set errno and optionally 'error' based on glob error return code. + */ +void conf_globerr (flux_error_t *error, const char *pattern, int rc); + +#endif /* !_FLUX_CONF_PRIVATE_H */ + +/* + * vi:tabstop=4 shiftwidth=4 expandtab + */ diff --git a/src/libconf/test/conf.c b/src/libconf/test/conf.c new file mode 100644 index 0000000..7da1b5c --- /dev/null +++ b/src/libconf/test/conf.c @@ -0,0 +1,399 @@ +/************************************************************\ + * Copyright 2014 Lawrence Livermore National Security, LLC + * (c.f. AUTHORS, NOTICE.LLNS, COPYING) + * + * This file is part of the Flux resource manager framework. + * For details, see https://github.com/flux-framework. + * + * SPDX-License-Identifier: LGPL-3.0 +\************************************************************/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "vendored/libtap/tap.h" +#include "ccan/str/str.h" + +#include "conf_private.h" +#include "conf.h" + +const char *t1 = \ +"i = 1\n" \ +"d = 3.14\n" \ +"s = \"foo\"\n" \ +"b = true\n" \ +"ts = 1979-05-27T07:32:00Z\n" \ +"ai = [ 1, 2, 3]\n" \ +"[tab]\n" \ +"subvalue = 42\n"; + +const char *tab2 = \ +"[tab2]\n" \ +"id = 2\n"; + +const char *tab3 = \ +"[tab3]\n" \ +"id = 3\n"; + +const char *tab3_json = \ +"{\"tab3\": {\"id\": 4}}"; + +const char *tab4 = \ +"[tab]\n" \ +"added = \"bar\""; + + +static void +create_test_file (const char *dir, + char *prefix, + char *ext, + char *path, + size_t pathlen, + const char *contents) +{ + int fd; + snprintf (path, + pathlen, + "%s/%s.XXXXXX.%s", + dir ? dir : "/tmp", + prefix, + ext); + fd = mkstemps (path, 5); + if (fd < 0) + BAIL_OUT ("mkstemp %s: %s", path, strerror (errno)); + if (write (fd, contents, strlen (contents)) != strlen (contents)) + BAIL_OUT ("write %s: %s", path, strerror (errno)); + if (close (fd) < 0) + BAIL_OUT ("close %s: %s", path, strerror (errno)); + diag ("created %s", path); +} + +void test_basic (void) +{ + int rc; + const char *tmpdir = getenv ("TMPDIR"); + char dir[PATH_MAX + 1]; + char path1[PATH_MAX + 1]; + char path2[PATH_MAX + 1]; + char path3[PATH_MAX + 1]; + char path4[PATH_MAX + 1]; + char pathj[PATH_MAX + 1]; + char invalid[PATH_MAX + 1]; + flux_error_t error; + flux_conf_t *conf; + int i, j, k; + double d; + const char *s; + int b; + + /* Create *.toml containing test data + */ + snprintf (dir, sizeof (dir), "%s/cf.XXXXXXX", tmpdir ? tmpdir : "/tmp"); + if (!mkdtemp (dir)) + BAIL_OUT ("mkdtemp %s: %s", dir, strerror (errno)); + + /* Empty directory is allowed + */ + conf = flux_conf_parse (dir, &error); + ok (conf != NULL, + "flux_conf_parse successfully parsed empty directory"); + flux_conf_decref (conf); + + /* Add files + */ + create_test_file (dir, "01", "toml", path1, sizeof (path1), t1); + create_test_file (dir, "02", "toml", path2, sizeof (path2), tab2); + create_test_file (dir, "03", "toml", path3, sizeof (path3), tab3); + create_test_file (dir, "04", "toml", path4, sizeof (path4), tab4); + create_test_file (NULL, "03", "json", pathj, sizeof (pathj), tab3_json); + + /* Parse of one file works + */ + conf = flux_conf_parse (path3, &error); + ok (conf != NULL, + "flux_conf_parse successfully parsed a single file"); + if (!conf) + BAIL_OUT ("cannot continue without config object"); + + /* Check table from path3 toml file + */ + i = 0; + rc = flux_conf_unpack (conf, + &error, + "{s:{s:i}}", + "tab3", + "id", &i); + ok (rc == 0 && i == 3, + "unpacked integer from [tab3] and got expected value"); + + flux_conf_decref (conf); + + /* Parse one file JSON edition + */ + conf = flux_conf_parse (pathj, &error); + ok (conf != NULL, + "flux_conf_parse works for just one file (JSON)"); + i = 0; + rc = flux_conf_unpack (conf, + &error, + "{s:{s:i}}", + "tab3", + "id", &i); + ok (rc == 0 && i == 4, + "unpacked integer from [tab3] and got expected value"); + flux_conf_decref (conf); + + /* Parse it + */ + conf = flux_conf_parse (dir, &error); + ok (conf != NULL, + "flux_conf_parse successfully parsed 3 files"); + if (!conf) + BAIL_OUT ("cannot continue without config object"); + + /* Check scalar contents + */ + i = 0; + d = 0; + s = NULL; + b = false; + rc = flux_conf_unpack (conf, + &error, + "{s:i s:f s:s s:b}", + "i", &i, + "d", &d, + "s", &s, + "b", &b); + ok (rc == 0, + "unpacked config object, scalar values"); + ok (i == 1, + "unpacked integer value"); + ok (d == 3.14, + "unpacked double value"); + ok (b == true, + "unpacked boolean value"); + ok (s != NULL && streq (s, "foo"), + "unpacked string value"); + + /* Check array contents + */ + i = j = k = 0; + rc = flux_conf_unpack (conf, + &error, + "{s:[i,i,i]}", + "ai", + &i, &j, &k); + ok (rc == 0 && i == 1 && j == 2 && k == 3, + "unpacked array value"); + + /* N.B. skip fully decoding timestamp object for now. + * Not sure if we'll need support for it in this interface. + * If we do, see tomltk.c for encoding. + */ + rc = flux_conf_unpack (conf, + &error, + "{s:{s:s}}", + "ts", + "iso-8601-ts", + &s); + ok (rc == 0, + "unpacked timestamp value", s); + diag ("timestamp=%s", s); + + /* Check table contents + */ + i = 0; + rc = flux_conf_unpack (conf, + &error, + "{s:{s:i}}", + "tab", + "subvalue", &i); + ok (rc == 0 && i == 42, + "unpacked integer from [tab] and got expected value"); + + /* Check that tab was updated with added value from tab4 + */ + rc = flux_conf_unpack (conf, + &error, + "{s:{s:s}}", + "tab", + "added", &s); + diag ("added = %s", s); + ok (rc == 0 && streq (s, "bar"), + "unpacked added string from [tab] and got expected value"); + + + /* Check table from second toml file + */ + i = 0; + rc = flux_conf_unpack (conf, + &error, + "{s:{s:i}}", + "tab2", + "id", &i); + ok (rc == 0 && i == 2, + "unpacked integer from [tab2] and got expected value"); + + /* Check table from third toml file + */ + i = 0; + rc = flux_conf_unpack (conf, + &error, + "{s:{s:i}}", + "tab3", + "id", &i); + ok (rc == 0 && i == 3, + "unpacked integer from [tab3] and got expected value"); + + /* Try to get something that's missing + */ + errno = 0; + ok (flux_conf_unpack (conf, &error, "{s:s}", "noexist", &s) < 0 + && errno == EINVAL, + "flux_conf_unpack key=noexist failed with EINVAL"); + ok (strstr (error.text, "noexist") != NULL, + "and error.text mentions noexist"); + diag ("%s", error.text); + + /* Bad args fail with EINVAL + */ + errno = 0; + ok (flux_conf_unpack (NULL, &error, "{s:i}", "i", &i) < 0 + && errno == EINVAL, + "flux_conf_unpack conf=NULL fails with EINVAL"); + + flux_conf_decref (conf); + + /* Now make an invalid file and ensure cf_update_glob() aborts + * all updates after any one failure + */ + create_test_file (dir, "99", "toml", invalid, sizeof (invalid), "key = \n"); + + conf = flux_conf_parse (invalid, &error); + ok (conf == NULL, + "flux_conf_parse failed on bad individual file"); + like (error.text, "99.*\\.toml", + "Failed file contained in error.text"); + + conf = flux_conf_parse (dir, &error); + ok (conf == NULL, + "flux_conf_parse choked on glob referencing some good and one bad file"); + + diag ("%s", error.text); + like (error.text, "99.*\\.toml", + "Failed file contained in error.text"); + + /* Parse invalid JSON file + */ + unlink (invalid); + create_test_file (dir, "foo", "json", invalid, sizeof (invalid), "{"); + conf = flux_conf_parse (invalid, &error); + ok (conf == NULL, + "flux_conf_parse choked on bad file"); + + diag ("%s", error.text); + like (error.text, "foo.*\\.json", + "Failed file contained in error.text"); + + /* Invalid pattern arg + */ + errno = 0; + ok (flux_conf_parse (NULL, &error) == NULL && errno == EINVAL, + "flux_conf_parse path=NULL fails with EINVAL"); + diag ("%s", error.text); + + /* Directory not found triggers ENOENT error + */ + errno = 0; + ok (flux_conf_parse ("/noexist", &error) == NULL && errno == ENOENT, + "flux_conf_parse pattern=/noexist fails with ENOENT"); + diag ("%s", error.text); + + if ( (unlink (path1) < 0) + || (unlink (path2) < 0) + || (unlink (path3) < 0) + || (unlink (path4) < 0) + || (unlink (pathj) < 0) + || (unlink (invalid) < 0) ) + BAIL_OUT ("unlink: %s", strerror (errno)); + if (rmdir (dir) < 0) + BAIL_OUT ("rmdir: %s: %s", dir, strerror (errno)); + +} + +void test_globerr (void) +{ + flux_error_t error; + + errno = 0; + memset (&error, 0, sizeof (error)); + conf_globerr (&error, "meep", GLOB_NOMATCH); + ok (errno == ENOENT + && streq (error.text, "meep: No match"), + "conf_globerr pat=meep rc=NOMATCH sets errno and error as expected"); + + errno = 0; + memset (&error, 0, sizeof (error)); + conf_globerr (&error, "moo", GLOB_NOSPACE); + ok (errno == ENOMEM + && streq (error.text, "moo: Out of memory"), + "conf_globerr pat=moo rc=NOSPACE sets errno and error as expected"); + + errno = 0; + memset (&error, 0, sizeof (error)); + conf_globerr (&error, "foo", GLOB_ABORTED); + ok (errno == EINVAL + && streq (error.text, "foo: Read error"), + "conf_globerr pat=moo rc=ABORTED sets errno and error as expected"); + + errno = 0; + memset (&error, 0, sizeof (error)); + conf_globerr (&error, "oops", 666); + ok (errno == EINVAL + && streq (error.text, "oops: Unknown glob error"), + "conf_globerr pat=oops rc=666 sets errno and error as expected"); +} + +void test_pack (void) +{ + flux_conf_t *conf; + + errno = 0; + ok (flux_conf_pack (NULL) == NULL && errno == EINVAL, + "flux_conf_pack fmt=NULL fails with EINVAL"); + + conf = flux_conf_pack ("{s:i}", "foo", 42); + ok (conf != NULL, + "flux_conf_pack works"); + int i; + ok (flux_conf_unpack (conf, NULL, "{s:i}", "foo", &i) == 0 && i == 42, + "flux_conf_unpack has expected result"); + + flux_conf_decref (conf); +} + +int main (int argc, char *argv[]) +{ + plan (NO_PLAN); + + test_basic (); + test_globerr (); + test_pack(); + + done_testing (); +} + +/* + * vi: ts=4 sw=4 expandtab + */ diff --git a/src/libconf/test/tomltk.c b/src/libconf/test/tomltk.c new file mode 100644 index 0000000..66b2ab6 --- /dev/null +++ b/src/libconf/test/tomltk.c @@ -0,0 +1,273 @@ +/************************************************************\ + * Copyright 2014 Lawrence Livermore National Security, LLC + * (c.f. AUTHORS, NOTICE.LLNS, COPYING) + * + * This file is part of the Flux resource manager framework. + * For details, see https://github.com/flux-framework. + * + * SPDX-License-Identifier: LGPL-3.0 +\************************************************************/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vendored/libtap/tap.h" +#include "vendored/libtomlc99/toml.h" +#include "ccan/str/str.h" +#include "tomltk.h" + +/* simple types only */ +const char *t1 = \ +"i = 1\n" \ +"d = 3.14\n" \ +"s = \"foo\"\n" \ +"b = true\n" \ +"ts = 1979-05-27T07:32:00Z\n"; + +/* table and array */ +const char *t2 = \ +"[t]\n" \ +"ia = [1, 2, 3]\n"; + +/* sub-table and value */ +const char *t3 = \ +"[t]\n" \ +"[t.a]\n" \ +"i = 42\n"; + +/* bad on line 4 */ +const char *bad1 = \ +"# line 1\n" \ +"# line 2\n" \ +"# line 3\n" \ +"'# line 4 <- unbalanced tic\n" +"# line 5\n"; + +static void jdiag (const char *prefix, json_t *obj) +{ + char *s = json_dumps (obj, JSON_INDENT(2)); + if (!s) + BAIL_OUT ("json_dumps: %s", strerror (errno)); + diag ("%s: %s", prefix, s); + free (s); +} + +/* Check whether json object represents to ISO 8601 time string. + */ +static bool check_ts (json_t *ts, const char *timestr) +{ + time_t t; + struct tm tm; + char buf[80]; + + if (tomltk_json_to_epoch (ts, &t) < 0) + return false; + if (!gmtime_r (&t, &tm)) + return false; + if (strftime (buf, sizeof (buf), "%Y-%m-%dT%TZ", &tm) == 0) + return false; + diag ("%s: %s ?= %s", __FUNCTION__, buf, timestr); + return streq (buf, timestr); +} + +void test_json_ts(void) +{ + time_t t, t2; + json_t *obj; + + /* Encode the current time, then decode and ensure it matches. + */ + if (time (&t) < 0) + BAIL_OUT ("time: %s", strerror (errno)); + obj = tomltk_epoch_to_json (t); + ok (obj != NULL, + "tomltk_epoch_to_json works"); + + ok (tomltk_json_to_epoch (obj, &t2) == 0 && t == t2, + "tomltk_json_to_epoch works, correct value"); + json_decref (obj); +} + +void test_tojson_t1 (void) +{ + toml_table_t *tab; + json_t *obj; + json_int_t i; + double d; + const char *s; + json_t *ts; + int b; + int rc; + struct tomltk_error error; + + tab = tomltk_parse (t1, strlen (t1), &error); + ok (tab != NULL, + "t1: tomltk_parse works"); + if (!tab) + BAIL_OUT ("t1: parse error line %d: %s", error.lineno, error.errbuf); + + obj = tomltk_table_to_json (tab); + ok (obj != NULL, + "t1: tomltk_table_to_json works"); + jdiag ("t1", obj); + rc = json_unpack (obj, "{s:I s:f s:s s:b s:o}", + "i", &i, + "d", &d, + "s", &s, + "b", &b, + "ts", &ts); + ok (rc == 0, + "t1: unpack successful"); + ok (i == 1 && d == 3.14 && s != NULL && streq (s, "foo") && b != 0 + && check_ts (ts, "1979-05-27T07:32:00Z"), + "t1: has expected values"); + json_decref (obj); + toml_free (tab); +} + +void test_tojson_t2 (void) +{ + toml_table_t *tab; + json_t *obj; + json_int_t ia[3]; + int rc; + struct tomltk_error error; + + tab = tomltk_parse (t2, strlen (t2), &error); + ok (tab != NULL, + "t2: tomltk_parse works"); + if (!tab) + BAIL_OUT ("t2: parse error line %d: %s", error.lineno, error.errbuf); + + obj = tomltk_table_to_json (tab); + ok (obj != NULL, + "t2: tomltk_table_to_json works"); + jdiag ("t2", obj); + rc = json_unpack (obj, "{s:{s:[I,I,I]}}", + "t", + "ia", &ia[0], &ia[1], &ia[2]); + ok (rc == 0, + "t2: unpack successful"); + ok (ia[0] == 1 && ia[1] == 2 && ia[2] == 3, + "t2: has expected values"); + json_decref (obj); + toml_free (tab); +} + +void test_tojson_t3 (void) +{ + toml_table_t *tab; + json_t *obj; + json_int_t i; + int rc; + struct tomltk_error error; + + tab = tomltk_parse (t3, strlen (t3), &error); + ok (tab != NULL, + "t3: tomltk_parse works"); + if (!tab) + BAIL_OUT ("t3: parse error line %d: %s", error.lineno, error.errbuf); + + obj = tomltk_table_to_json (tab); + ok (obj != NULL, + "t3: tomltk_table_to_json works"); + jdiag ("t3", obj); + rc = json_unpack (obj, "{s:{s:{s:I}}}", + "t", + "a", + "i", &i); + ok (rc == 0, + "t3: unpack successful"); + ok (i == 42, + "t3: has expected values"); + json_decref (obj); + toml_free (tab); +} + +void test_parse_lineno (void) +{ + toml_table_t *tab; + struct tomltk_error error; + + errno = 0; + tab = tomltk_parse (bad1, strlen (bad1), &error); + if (!tab) + diag ("filename='%s' lineno=%d msg='%s'", error.filename, + error.lineno, error.errbuf); + ok (tab == NULL && errno == EINVAL, + "bad1: parse failed"); + ok (strlen (error.filename) == 0, + "bad1: error.filename is \"\""); + ok (error.lineno == 4, + "bad1: error.lineno is 4"); + const char *msg = "unterminated s-quote"; + ok (streq (error.errbuf, msg), + "bad1: error is \"%s\"", msg); // no "line %d: " prefix +} + +void test_corner (void) +{ + time_t t; + json_t *obj; + + if (!(obj = tomltk_epoch_to_json (time (NULL)))) + BAIL_OUT ("tomltk_epoch_to_json now: %s", strerror (errno)); + + errno = 0; + ok (tomltk_parse_file (NULL, NULL) == NULL && errno == EINVAL, + "tomltk_parse_file filename=NULL fails with EINVAL"); + errno = 0; + ok (tomltk_parse ("foo", -1, NULL) == NULL && errno == EINVAL, + "tomltk_parse len=-1 fails with EINVAL"); + errno = 0; + ok (tomltk_table_to_json (NULL) == NULL && errno == EINVAL, + "tomltk_table_to_json NULL fails with EINVAL"); + + errno = 0; + ok (tomltk_json_to_epoch (NULL, &t) < 0 && errno == EINVAL, + "tomltk_json_to_epoch obj=NULL fails with EINVAL"); + + errno = 0; + ok (tomltk_ts_to_epoch (NULL, NULL) < 0 && errno == EINVAL, + "tomltk_ts_to_epoch ts=NULL fails with EINVAL"); + + errno = 0; + ok (tomltk_epoch_to_json (-1) == NULL && errno == EINVAL, + "tomltk_epoch_to_json t=-1 fails with EINVAL"); + + errno = 0; + ok (tomltk_parse_file (NULL, NULL) == NULL && errno == EINVAL, + "tomltk_parse_file filename=NULL fails with EINVAL"); + errno = 0; + ok (tomltk_parse_file ("/noexist", NULL) == NULL && errno == ENOENT, + "tomltk_parse_file filename=(noexist) fails with ENOENT"); + + json_decref (obj); +} + +int main (int argc, char *argv[]) +{ + plan (NO_PLAN); + + test_json_ts (); + test_tojson_t1 (); + test_tojson_t2 (); + test_tojson_t3 (); + test_parse_lineno (); + test_corner (); + + done_testing (); +} + +/* + * vi: ts=4 sw=4 expandtab + */ diff --git a/src/libconf/tomltk.c b/src/libconf/tomltk.c new file mode 100644 index 0000000..8ceb1e6 --- /dev/null +++ b/src/libconf/tomltk.c @@ -0,0 +1,345 @@ +/************************************************************\ + * Copyright 2017 Lawrence Livermore National Security, LLC + * (c.f. AUTHORS, NOTICE.LLNS, COPYING) + * + * This file is part of the Flux resource manager framework. + * For details, see https://github.com/flux-framework. + * + * SPDX-License-Identifier: LGPL-3.0 +\************************************************************/ + +#if HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include +#include +#include +#include +#include +#include + +#include "vendored/libtomlc99/toml.h" +#include "ccan/str/str.h" +#include "src/libutil/timestamp.h" +#include "tomltk.h" + +static int table_to_json (toml_table_t *tab, json_t **op); + +static void errprintf (struct tomltk_error *error, + const char *filename, int lineno, + const char *fmt, ...) +{ + va_list ap; + int saved_errno = errno; + + if (error) { + memset (error, 0, sizeof (*error)); + va_start (ap, fmt); + (void)vsnprintf (error->errbuf, sizeof (error->errbuf), fmt, ap); + va_end (ap); + if (filename) + strncpy (error->filename, filename, PATH_MAX); + error->lineno = lineno; + } + errno = saved_errno; +} + +/* Given an error message response from toml_parse(), parse the + * error message into line number and message, e.g. + * "line 42: bad key" + * is parsed to: + * error->lineno=42, error->errbuf="bad key" + */ +static void errfromtoml (struct tomltk_error *error, + const char *filename, char *errstr) +{ + if (error) { + char *msg = errstr; + int lineno = -1; + if (strstarts (errstr, "line ")) { + lineno = strtoul (errstr + 5, &msg, 10); + if (strstarts (msg, ": ")) + msg += 2; + } + return errprintf (error, filename, lineno, "%s", msg); + } +} + +/* Convert from TOML timestamp to struct tm (POSIX broken out time). + */ +static int tstotm (toml_timestamp_t *ts, struct tm *tm) +{ + if (!ts || !tm || !ts->year || !ts->month || !ts->day + || !ts->hour || !ts->minute || !ts->second) + return -1; + memset (tm, 0, sizeof (*tm)); + tm->tm_year = *ts->year - 1900; + tm->tm_mon = *ts->month - 1; + tm->tm_mday = *ts->day; + tm->tm_hour = *ts->hour; + tm->tm_min = *ts->minute; + tm->tm_sec = *ts->second; + return 0; +} + +int tomltk_ts_to_epoch (toml_timestamp_t *ts, time_t *tp) +{ + struct tm tm; + time_t t; + + if (!ts || tstotm (ts, &tm) < 0 || (t = timegm (&tm)) < 0) { + errno = EINVAL; + return -1; + } + if (tp) + *tp = t; + return 0; +} + +int tomltk_json_to_epoch (const json_t *obj, time_t *tp) +{ + const char *s; + + /* N.B. 'O' specifier not used, hence obj is not in danger + * of being modified by json_unpack. + */ + if (!obj || json_unpack ((json_t *)obj, "{s:s}", "iso-8601-ts", &s) < 0) { + errno = EINVAL; + return -1; + } + if (timestamp_fromstr (s, tp) < 0) { + errno = EINVAL; + return -1; + } + return 0; +} + +json_t *tomltk_epoch_to_json (time_t t) +{ + char timebuf[80]; + json_t *obj; + + if (timestamp_tostr (t, timebuf, sizeof (timebuf)) < 0) { + errno = EINVAL; + return NULL; + } + if (!(obj = json_pack ("{s:s}", "iso-8601-ts", timebuf))) { + errno = EINVAL; + return NULL; + } + return obj; +} + +/* Convert raw TOML value from toml_raw_in() or toml_raw_at() to JSON. + */ +static int value_to_json (const char *raw, json_t **op) +{ + char *s = NULL; + int b; + int64_t i; + double d; + toml_timestamp_t ts; + json_t *obj; + + if (toml_rtos (raw, &s) == 0) { + obj = json_string (s); + free (s); + if (!obj) + goto nomem; + } + else if (toml_rtob (raw, &b) == 0) { + if (!(obj = b ? json_true () : json_false ())) + goto nomem; + } + else if (toml_rtoi (raw, &i) == 0) { + if (!(obj = json_integer (i))) + goto nomem; + } + else if (toml_rtod (raw, &d) == 0) { + if (!(obj = json_real (d))) + goto nomem; + } + else if (toml_rtots (raw, &ts) == 0) { + time_t t; + if (tomltk_ts_to_epoch (&ts, &t) < 0 + || !(obj = tomltk_epoch_to_json (t))) + goto error; + } + else { + errno = EINVAL; + goto error; + } + *op = obj; + return 0; +nomem: + errno = ENOMEM; +error: + return -1; +} + +/* Convert TOML array to JSON. + */ +static int array_to_json (toml_array_t *arr, json_t **op) +{ + int i; + int saved_errno; + json_t *obj; + + if (!(obj = json_array ())) + goto nomem; + for (i = 0; ; i++) { + const char *raw; + json_t *val; + toml_table_t *tab; + toml_array_t *subarr; + + if ((raw = toml_raw_at (arr, i))) { + if (value_to_json (raw, &val) < 0) + goto error; + } + else if ((tab = toml_table_at (arr, i))) { + if (table_to_json (tab, &val) < 0) + goto error; + } + else if ((subarr = toml_array_at (arr, i))) { + if (array_to_json (subarr, &val) < 0) + goto error; + } + else + break; + if (json_array_append_new (obj, val) < 0) { + json_decref (val); + goto nomem; + } + } + *op = obj; + return 0; +nomem: + errno = ENOMEM; +error: + saved_errno = errno; + json_decref (obj); + errno = saved_errno; + return -1; +} + +/* Convert TOML table to JSON. + */ +static int table_to_json (toml_table_t *tab, json_t **op) +{ + int i; + int saved_errno; + json_t *obj; + + if (!(obj = json_object ())) + goto nomem; + for (i = 0; ; i++) { + const char *key; + const char *raw; + toml_table_t *subtab; + toml_array_t *arr; + json_t *val = NULL; + + if (!(key = toml_key_in (tab, i))) + break; + if ((raw = toml_raw_in (tab, key))) { + if (value_to_json (raw, &val) < 0) + goto error; + } + else if ((subtab = toml_table_in (tab, key))) { + if (table_to_json (subtab, &val) < 0) + goto error; + } + else if ((arr = toml_array_in (tab, key))) { + if (array_to_json (arr, &val) < 0) + goto error; + } + if (json_object_set_new (obj, key, val) < 0) { + json_decref (val); + goto nomem; + } + } + *op = obj; + return 0; +nomem: + errno = ENOMEM; +error: + saved_errno = errno; + json_decref (obj); + errno = saved_errno; + return -1; +} + +json_t *tomltk_table_to_json (toml_table_t *tab) +{ + json_t *obj; + + if (!tab) { + errno = EINVAL; + return NULL; + } + if (table_to_json (tab, &obj) < 0) + return NULL; + return obj; +} + +toml_table_t *tomltk_parse (const char *conf, int len, + struct tomltk_error *error) +{ + char errbuf[200]; + char *cpy; + toml_table_t *tab; + + if (len < 0 || (!conf && len != 0)) { + errprintf (error, NULL, -1, "invalid argument"); + errno = EINVAL; + return NULL; + } + if (!(cpy = calloc (1, len + 1))) { + errprintf (error, NULL, -1, "out of memory"); + errno = ENOMEM; + return NULL; + } + memcpy (cpy, conf, len); + tab = toml_parse (cpy, errbuf, sizeof (errbuf)); + free (cpy); + if (!tab) { + errfromtoml (error, NULL, errbuf); + errno = EINVAL; + return NULL; + } + return tab; +} + +toml_table_t *tomltk_parse_file (const char *filename, + struct tomltk_error *error) +{ + char errbuf[200]; + FILE *fp; + toml_table_t *tab; + + if (!filename) { + errprintf (error, NULL, -1, "invalid argument"); + errno = EINVAL; + return NULL; + } + if (!(fp = fopen (filename, "r"))) { + errprintf (error, filename, -1, "%s", strerror (errno)); + return NULL; + } + // N.B. toml_parse_file() doesn't give us any way to distinguish parse + // error from read error + tab = toml_parse_file (fp, errbuf, sizeof (errbuf)); + (void)fclose (fp); + if (!tab) { + errfromtoml (error, filename, errbuf); + errno = EINVAL; + return NULL; + } + return tab; +} + +/* + * vi:tabstop=4 shiftwidth=4 expandtab + */ diff --git a/src/libconf/tomltk.h b/src/libconf/tomltk.h new file mode 100644 index 0000000..075c379 --- /dev/null +++ b/src/libconf/tomltk.h @@ -0,0 +1,70 @@ +/************************************************************\ + * Copyright 2017 Lawrence Livermore National Security, LLC + * (c.f. AUTHORS, NOTICE.LLNS, COPYING) + * + * This file is part of the Flux resource manager framework. + * For details, see https://github.com/flux-framework. + * + * SPDX-License-Identifier: LGPL-3.0 +\************************************************************/ + +#ifndef _UTIL_TOMLTK_H +#define _UTIL_TOMLTK_H + +/* tomltk - toolkit for tomlc99 and jansson */ + +#include +#include +#include +#include "vendored/libtomlc99/toml.h" + +struct tomltk_error { + char filename[PATH_MAX + 1]; + int lineno; + char errbuf[200]; +}; + +// TOML timestamp is represented as JSON object like this: +// { "iso-8601-ts" : "2003-08-24T05:14:50Z" } + +/* Convert TOML table to a JSON object. + * Return new object on success, NULL on failure with errno set. + */ +json_t *tomltk_table_to_json (toml_table_t *tab); + +/* Convert timestamp JSON object to a time_t (UTC). + * Return 0 on success, or -1 on failure with errno set. + */ +int tomltk_json_to_epoch (const json_t *obj, time_t *t); + +/* Convert time_t (UTC) to a timestamp JSON object. + * Return new object on success, NULL on failure with errno set. + */ +json_t *tomltk_epoch_to_json (time_t t); + +/* Convert TOML timestamp to a time_t (UTC) + * Return 0 on success, or -1 on failure with errno set. + */ +int tomltk_ts_to_epoch (toml_timestamp_t *ts, time_t *tp); + +/* Wrapper for toml_parse() that internally copies 'conf', + * adding NULL termination. On success, 0 is returned. + * On failure -1 is returned with errno set. If 'error' is + * non-NULL, an error description is written there. + */ +toml_table_t *tomltk_parse (const char *conf, int len, + struct tomltk_error *error); + +/* Wrapper for toml_parse_file() that internally + * opens/closes 'filename'. On success, 0 is returned. + * On failure -1 is returned with errno set. If 'error' is + * non-NULL, and error description is written there. + */ +toml_table_t *tomltk_parse_file (const char *filename, + struct tomltk_error *error); + +#endif /* !_UTIL_TOMLTK_H */ + +/* + * vi:tabstop=4 shiftwidth=4 expandtab + */ From 342b831939f137bbd076cd3d45a372578594c13d Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Tue, 14 Oct 2025 19:42:26 -0700 Subject: [PATCH 4/5] libconf: add pkgconfig file Problem: libflux-conf will need a pkgconfig file. Add one. --- configure.ac | 1 + src/libconf/Makefile.am | 2 ++ src/libconf/flux-conf.pc.in | 10 ++++++++++ 3 files changed, 13 insertions(+) create mode 100644 src/libconf/flux-conf.pc.in diff --git a/configure.ac b/configure.ac index 672fd8b..7d95594 100644 --- a/configure.ac +++ b/configure.ac @@ -109,6 +109,7 @@ AC_CONFIG_FILES( \ src/liboptparse/Makefile \ src/liboptparse/flux-optparse.pc \ src/libconf/Makefile \ + src/libconf/flux-conf.pc \ doc/Makefile \ doc/test/Makefile \ ) diff --git a/src/libconf/Makefile.am b/src/libconf/Makefile.am index da3ce2e..409cb43 100644 --- a/src/libconf/Makefile.am +++ b/src/libconf/Makefile.am @@ -12,6 +12,8 @@ AM_CPPFLAGS = \ $(JANSSON_CFLAGS) \ $(CODE_COVERAGE_CPPFLAGS) +pkgconfig_DATA = flux-conf.pc + noinst_LTLIBRARIES = \ libconf.la diff --git a/src/libconf/flux-conf.pc.in b/src/libconf/flux-conf.pc.in new file mode 100644 index 0000000..17e35ec --- /dev/null +++ b/src/libconf/flux-conf.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: flux-idset +Description: Flux Resource Manager Configuration Library +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -lflux-conf +Cflags: -I${includedir} From c999893625b94500ab62f0d4bc3fa8f615b4d188 Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Tue, 14 Oct 2025 20:10:31 -0700 Subject: [PATCH 5/5] doc: pull in flux_conf_t man pages Problem: there are no man pages for flux_conf_t. Pull them in from flux-core. Also pull in json_pack/json_unpack descriptions they reference. Update dictionary. --- doc/Makefile.am | 11 +++- doc/man3/common/json_pack.rst | 77 +++++++++++++++++++++++++++ doc/man3/common/json_unpack.rst | 71 +++++++++++++++++++++++++ doc/man3/flux_conf_create.rst | 94 +++++++++++++++++++++++++++++++++ doc/manpages.py | 7 +++ doc/test/spell.en.pws | 12 +++++ 6 files changed, 270 insertions(+), 2 deletions(-) create mode 100644 doc/man3/common/json_pack.rst create mode 100644 doc/man3/common/json_unpack.rst create mode 100644 doc/man3/flux_conf_create.rst diff --git a/doc/Makefile.am b/doc/Makefile.am index 627dc47..975e46d 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -12,7 +12,8 @@ MAN3_FILES_PRIMARY = \ man3/idset_encode.3 \ man3/idset_decode.3 \ man3/idset_add.3 \ - man3/idset_alloc.3 + man3/idset_alloc.3 \ + man3/flux_conf_create.3 MAN3_FILES_SECONDARY = \ man3/hostlist_destroy.3 \ @@ -59,7 +60,13 @@ MAN3_FILES_SECONDARY = \ man3/idset_has_intersection.3 \ man3/idset_clear_all.3 \ man3/idset_free.3 \ - man3/idset_free_check.3 + man3/idset_free_check.3 \ + man3/flux_conf_incref.3 \ + man3/flux_conf_decref.3 \ + man3/flux_conf_copy.3 \ + man3/flux_conf_unpack.3 \ + man3/flux_conf_pack.3 \ + man3/flux_conf_parse.3 RST_FILES = \ $(MAN3_FILES_PRIMARY:.3=.rst) diff --git a/doc/man3/common/json_pack.rst b/doc/man3/common/json_pack.rst new file mode 100644 index 0000000..b233b4d --- /dev/null +++ b/doc/man3/common/json_pack.rst @@ -0,0 +1,77 @@ +Flux API functions that are based on Jansson's :func:`json_pack` +accept the following tokens in their format string. +The type in parenthesis denotes the resulting JSON type, and +the type in brackets (if any) denotes the C type that is expected as +the corresponding argument or arguments. + +**s** (string)['const char \*'] + Convert a null terminated UTF-8 string to a JSON string. + +**s?** (string)['const char \*'] + Like **s**, but if the argument is NULL, outputs a JSON null value. + +**s#** (string)['const char \*', 'int'] + Convert a UTF-8 buffer of a given length to a JSON string. + +**s%** (string)['const char \*', 'size_t'] + Like **s#** but the length argument is of type size_t. + +**+** ['const char \*'] + Like **s**, but concatenate to the previous string. + Only valid after a string. + +**+#** ['const char \*', 'int'] + Like **s#**, but concatenate to the previous string. + Only valid after a string. + +**+%** ['const char \*', 'size_t'] + Like **+#**, but the length argument is of type size_t. + +**n** (null) + Output a JSON null value. No argument is consumed. + +**b** (boolean)['int'] + Convert a C int to JSON boolean value. Zero is converted to + *false* and non-zero to *true*. + +**i** (integer)['int'] + Convert a C int to JSON integer. + +**I** (integer)['int64_t'] + Convert a C int64_t to JSON integer. + Note: Jansson expects a json_int_t here without committing to a size, + but Flux guarantees that this is a 64-bit integer. + +**f** (real)['double'] + Convert a C double to JSON real. + +**o** (any value)['json_t \*'] + Output any given JSON value as-is. If the value is added to an array + or object, the reference to the value passed to **o** is stolen by the + container. + +**O** (any value)['json_t \*'] + Like **o**, but the argument's reference count is incremented. This + is useful if you pack into an array or object and want to keep the reference + for the JSON value consumed by **O** to yourself. + +**o?**, **O?** (any value)['json_t \*'] + Like **o** and **O**, respectively, but if the argument is NULL, + output a JSON null value. + +**[fmt]** (array) + Build an array with contents from the inner format string. **fmt** may + contain objects and arrays, i.e. recursive value building is supported. + +**{fmt}** (object) + Build an object with contents from the inner format string **fmt**. + The first, third, etc. format specifier represent a key, and must be a + string as object keys are always strings. The second, fourth, etc. + format specifier represent a value. Any value may be an object or array, + i.e. recursive value building is supported. + +Whitespace, **:** (colon) and **,** (comma) are ignored. + +These descriptions came from the Jansson 2.9 manual. + +See also: Jansson API: Building Values: http://jansson.readthedocs.io/en/2.9/apiref.html#building-values diff --git a/doc/man3/common/json_unpack.rst b/doc/man3/common/json_unpack.rst new file mode 100644 index 0000000..f3afe8b --- /dev/null +++ b/doc/man3/common/json_unpack.rst @@ -0,0 +1,71 @@ +Flux API functions that are based on Jansson's :func:`json_unpack` +accept the following tokens in their format string. +The type in parenthesis denotes the resulting JSON type, and +the type in brackets (if any) denotes the C type that is expected as +the corresponding argument or arguments. + +**s** (string)['const char \*'] + Convert a JSON string to a pointer to a null terminated UTF-8 string. + The resulting string is extracted by using 'json_string_value()' + internally, so it exists as long as there are still references to the + corresponding JSON string. + +**s%** (string)['const char \*', 'size_t'] + Convert a JSON string to a pointer to a null terminated UTF-8 + string and its length. + +**n** (null) + Expect a JSON null value. Nothing is extracted. + +**b** (boolean)['int'] + Convert a JSON boolean value to a C int, so that *true* is converted to 1 + and *false* to 0. + +**i** (integer)['int'] + Convert a JSON integer to a C int. + +**I** (integer)['int64_t'] + Convert a JSON integer to a C int64_t. + Note: Jansson expects a json_int_t here without committing to a size, + but Flux guarantees that this is a 64-bit integer. + +**f** (real)['double'] + Convert JSON real to a C double. + +**F** (real)['double'] + Convert JSON number (integer or real) to a C double. + +**o** (any value)['json_t \*'] + Store a JSON value, with no conversion, to a json_t pointer. + +**O** (any value)['json_t \*'] + Like **o**, but the JSON value's reference count is incremented. + +**[fmt]** (array) + Convert each item in the JSON array according to the inner format + string. **fmt** may contain objects and arrays, i.e. recursive value + extraction is supported. + +**{fmt}** (object) + Convert each item in the JSON object according to the inner format + string **fmt**. The first, third, etc. format specifier represent a + key, and must by **s**. The corresponding argument to unpack functions + is read as the object key. The second, fourth, etc. format specifier + represent a value and is written to the address given as the corresponding + argument. Note that every other argument is read from and every other + is written to. **fmt** may contain objects and arrays as values, i.e. + recursive value extraction is supported. Any **s** representing a key + may be suffixed with **?** to make the key optional. If the key is not + found, nothing is extracted. + +**!** + This special format specifier is used to enable the check that all + object and array items are accessed, on a per-value basis. It must + appear inside an array or object as the last format specifier before + the closing bracket or brace. + +Whitespace, **:** (colon) and **,** (comma) are ignored. + +These descriptions came from the Jansson 2.9 manual. + +See also: Jansson API: Parsing and Validating Values: http://jansson.readthedocs.io/en/2.9/apiref.html#parsing-and-validating-values diff --git a/doc/man3/flux_conf_create.rst b/doc/man3/flux_conf_create.rst new file mode 100644 index 0000000..95f16fc --- /dev/null +++ b/doc/man3/flux_conf_create.rst @@ -0,0 +1,94 @@ +=================== +flux_conf_create(3) +=================== + +.. default-domain:: c + +SYNOPSIS +======== + +.. code-block:: c + + #include + + flux_conf_t *flux_conf_create (void); + + const flux_conf_t *flux_conf_incref (const flux_conf_t *conf); + + void flux_conf_decref (const flux_conf_t *conf); + + flux_conf_t *flux_conf_copy (const flux_conf_t *conf); + + int flux_conf_unpack (const flux_conf_t *conf, + flux_error_t *error, + const char *fmt, + ...); + + flux_conf_t *flux_conf_pack (const char *fmt, ...); + + flux_conf_t *flux_conf_parse (const char *path, flux_error_t *error); + +Link with :command:`-lflux-conf`. + +DESCRIPTION +=========== + +Flux configuration is represented by a :type:`flux_conf_t` object. + +:func:`flux_conf_create` creates an empty object with a reference +count of one. + +:func:`flux_conf_incref` increments the object reference count. +:func:`flux_conf_decref` decrements the object reference count +and destroys it when the count reaches zero. + +:func:`flux_conf_copy` duplicates an object. + +:func:`flux_conf_unpack` unpacks an object using Jansson +:func:`json_unpack` style arguments. + +:func:`flux_conf_pack` creates an object using Jansson +:func:`json_pack` style arguments. + +:func:`flux_conf_parse` parse a TOML configuration file at :var:`path` +and creates a config object that contains the result. + +ENCODING JSON PAYLOADS +====================== + +.. include:: common/json_pack.rst + +DECODING JSON PAYLOADS +====================== + +.. include:: common/json_unpack.rst + +RETURN VALUE +============ + +:func:`flux_conf_create`, :func:`flux_conf_copy`, :func:`flux_conf_pack`, +and :func:`flux_conf_incref` return a :type:`flux_conf_t` object on success. +On error, NULL is returned, and :var:`errno` is set. + +:func:`flux_conf_unpack` returns 0 on success, or -1 on failure with +:var:`errno` set. + +:func:`flux_conf_parse` returns 0 on success. On error, it returns -1, +:var:`errno` set, and if :var:`error` is non-NULL, it is filled with +a human readable error message. + +ERRORS +====== + +EINVAL + Invalid argument. + +ENOMEM + Out of memory. + +RESOURCES +========= + +.. include:: common/resources.rst + +TOML: Tom's Oblivious, Minimal Language https://github.com/toml-lang/toml diff --git a/doc/manpages.py b/doc/manpages.py index adc5756..714cdbe 100644 --- a/doc/manpages.py +++ b/doc/manpages.py @@ -69,4 +69,11 @@ ('man3/idset_alloc','idset_alloc', 'Allocate an id from an idset', [author], 3), ('man3/idset_alloc','idset_free', 'Allocate an id from an idset', [author], 3), ('man3/idset_alloc','idset_free_check', 'Allocate an id from an idset', [author], 3), + ('man3/flux_conf_create','flux_conf_create', 'create a config object', [author], 3), + ('man3/flux_conf_create','flux_conf_incref', 'take reference on config object', [author], 3), + ('man3/flux_conf_create','flux_conf_decref', 'drop reference on config object', [author], 3), + ('man3/flux_conf_create','flux_conf_copy', 'copy config object', [author], 3), + ('man3/flux_conf_create','flux_conf_unpack', 'parse config object', [author], 3), + ('man3/flux_conf_create','flux_conf_pack', 'build config object', [author], 3), + ('man3/flux_conf_create','flux_conf_parse', 'parse TOML config', [author], 3), ] diff --git a/doc/test/spell.en.pws b/doc/test/spell.en.pws index 26880c0..909f2ab 100644 --- a/doc/test/spell.en.pws +++ b/doc/test/spell.en.pws @@ -40,3 +40,15 @@ maxid NUL ssize typedef +conf +config +decref +fmt +incref +jansson +jansson's +json +TOML +github +UTF +whitespace