diff --git a/CMakeLists.txt b/CMakeLists.txt index cf4e528..e11bcf4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,5 @@ cmake_minimum_required(VERSION 3.14.0) -project(toml VERSION 0.1) - -### These flags should be removed eventually -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) +project(tominal VERSION 0.1) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) @@ -17,12 +12,12 @@ set(LIBRARY_OUTPUT_PATH "${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE}") option(TOML_BUILD_EXAMPLE "Build examples" OFF) option(TOML_BUILD_TEST "Build tests" ON) -# Import tomlplusplus interface library -add_library(toml INTERFACE IMPORTED) +add_library(tominal INTERFACE IMPORTED) set_target_properties( - toml PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${PROJECT_SOURCE_DIR}" + tominal PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${PROJECT_SOURCE_DIR}/include" ) +target_compile_features(tominal INTERFACE cxx_std_17) if(TOML_BUILD_TEST) include(FetchContent) @@ -35,5 +30,9 @@ if(TOML_BUILD_TEST) FetchContent_MakeAvailable(googletest) enable_testing() - add_subdirectory(test) + add_subdirectory(tests) +endif() + +if(TOML_BUILD_EXAMPLE) + add_subdirectory(examples) endif() \ No newline at end of file diff --git a/README.md b/README.md index 18974e7..7709649 100644 --- a/README.md +++ b/README.md @@ -23,39 +23,40 @@ C++ Alternatives: To parse a TOML document from a file, you can do the following: ```cpp -#include "toml/toml.h" +#include "tominal/toml.h" // ## Parsing // `parse_file()` returns a `node_view` of a `toml::table`, which owns // a copy of `std::shared_ptr` so there is no lifetime issue. // It will return an empty view if it catches an `toml::parse_error`. -auto config = cpptoml::parse_file("examples/example.toml").ok(); +auto config = toml::parse_file("examples/example.toml").ok(); // ## Obtaining Basic Values -auto title = config["title"].value_or(""sv); // "TOML Example"sv -// or you want to have a `std::optional` -auto optional_title = config["title"].value().value(); +auto title = config["title"].value_or("TOML"sv); // "TOML Example"sv +// or to have the default `std::string_view` if node_view is empty +auto default_title = config["title"].value_or_default(); // ## Nested Tables // Nested tables can be queried directly by chaining `[]` operator through // the `node_view` and you can use a `map` function to convert to your data type -auto author = config["owner"]["name"].value_or(""sv); // "Tom Preston-Werner" +auto author = config["owner"]["name"].value_or("TOML"); +auto enabled = config["database.enabled"].value_or_default(); -// empty `std::optional` will be mapped if node does not exist -auto dob = config["owner"]["dob"].map([](const auto &val) { +// map a node_view in place if it exists: +auto dob = config["owner.dob"].map([](const auto &val) { return val.year * 10000 + val.month * 100 + val.day; -}); // std::optional{19790527} +}); +config["owner.dob"].map([](const auto &val) {}); // void callback + // ## Arrays of Values / Tables // Similarly to `toml::table`, you can access the `node` of a table by `[]` // operator. The are also `collect` and `map_collect` provided for convenience: -auto gamma = config["clients"][0]["data"][0][0].value_or(""sv); -auto data0 = config["clients"][0]["data"][0].collect(); // std::vector{"gamma"sv, "delta"sv} - -auto ports = config["database"]["ports"].map_collect( - [](const auto &val) { - return val - 1; - }); // std::vector{8000, 8000, 8001} +auto gamma = config["clients"][0]["data"][1][1].value_or_default(); +auto data0 = config["clients"][1]["host"].collect(); // std::vector{"omega"sv} +auto ports = config["database"]["ports"].map_collect([](const auto &val) { + return val - 8000; // std::vector{0, 0, 1} +}); ``` A problem I had with `cpptoml` was that I was not able to dereference an `rvalue` table or array: @@ -97,12 +98,12 @@ Here are the fields of the date/time objects in cpptoml: - minute\_offset (`offset_date_time`) -## Test Results - TODO +## Test Results From [the toml-test suite][toml-test]: ``` -126 passed, 0 failed // TODO with TOML v1.0.0-rc.1 +116 passed, 12 failed // toml-test suite is only compliant with TOML v0.4.0 ``` ## Compilation @@ -123,7 +124,7 @@ You can look at the files files `parse.cpp`, `parse_stdin.cpp`, and `build_toml.cpp` in the root directory for some more examples. `parse_stdin.cpp` shows how to use the visitor pattern to traverse an -entire `cpptoml::table` for serialization. +entire `toml::table` for serialization. `build_toml.cpp` shows how to construct a TOML representation in-memory and then serialize it to a stream. @@ -136,4 +137,4 @@ then serialize it to a stream. [toml11]: https://github.com/ToruNiina/toml11 [tinytoml]: https://github.com/mayah/tinytoml [boost.toml]: https://github.com/ToruNiina/Boost.toml -[tomlplusplus]: https://github.com/marzer/tomlplusplus +[tomlplusplus]: https://github.com/marzer/tomlplusplus \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c993d54..eb881bd 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,17 +1,19 @@ +### These flags should be removed eventually +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + add_executable(parse parse.cpp) -target_link_libraries(parse cpptoml) -set_target_properties(parse PROPERTIES - CXX_STANDARD 11 - CXX_EXTENSIONS OFF - CXX_STANDARD_REQUIRED YES) +target_link_libraries(parse tominal) -add_executable(cpptoml-parser parse_stdin.cpp) -target_link_libraries(cpptoml-parser cpptoml) -set_target_properties(cpptoml-parser PROPERTIES - CXX_STANDARD 11 - CXX_EXTENSIONS OFF - CXX_STANDARD_REQUIRED YES) +add_executable(toml-parser parse_stdin.cpp) +target_link_libraries(toml-parser tominal) +if(FALSE) add_executable(cpptoml-build build_toml.cpp) target_link_libraries(cpptoml-build cpptoml) set_target_properties(cpptoml-build PROPERTIES @@ -25,3 +27,4 @@ set_target_properties(cpptoml-conversions PROPERTIES CXX_STANDARD 11 CXX_EXTENSIONS OFF CXX_STANDARD_REQUIRED YES) +endif() \ No newline at end of file diff --git a/examples/example.toml b/examples/example.toml index 08d7d7b..8412255 100644 --- a/examples/example.toml +++ b/examples/example.toml @@ -23,13 +23,11 @@ enabled = true ip = "10.0.0.2" dc = "eqdc10" -[[clients]] -data = [ ["gamma", "delta"], [1, 2]] - [[clients]] # array of tables +data = [ ["gamma", "delta"], [1, 2]] -# Line breaks are OK when inside arrays +[[clients]] hosts = [ - { name = "alpha" }, + { name = "alpha" }, # Line breaks are OK when inside arrays "omega", # trailing comma and mixed type both allowd in array ] \ No newline at end of file diff --git a/examples/parse.cpp b/examples/parse.cpp index 48bd791..d72c4b5 100644 --- a/examples/parse.cpp +++ b/examples/parse.cpp @@ -1,7 +1,5 @@ -#include "cpptoml.h" - #include -#include +#include "tominal/toml.h" int main(int argc, char** argv) { @@ -11,14 +9,14 @@ int main(int argc, char** argv) return 1; } - try + if (auto result = toml::parse_file(argv[1]); result.is_ok()) { - std::shared_ptr g = cpptoml::parse_file(argv[1]); - std::cout << (*g) << std::endl; + auto view = result.ok(); + std::cout << view << std::endl; } - catch (const cpptoml::parse_exception& e) + else { - std::cerr << "Failed to parse " << argv[1] << ": " << e.what() << std::endl; + std::cerr << "Failed to parse " << argv[1] << ": " << result.err().what() << std::endl; return 1; } diff --git a/examples/parse_stdin.cpp b/examples/parse_stdin.cpp index 4c65c2c..cd6ff72 100644 --- a/examples/parse_stdin.cpp +++ b/examples/parse_stdin.cpp @@ -1,7 +1,6 @@ -#include "cpptoml.h" - #include #include +#include "tominal/toml.h" /** * A visitor for toml objects that writes to an output stream in the JSON @@ -15,49 +14,49 @@ class toml_test_writer // nothing } - void visit(const cpptoml::value& v) + void visit(const toml::value& v) { stream_ << "{\"type\":\"string\",\"value\":\"" - << cpptoml::toml_writer::escape_string(v.get()) << "\"}"; + << toml::toml_writer::escape_string(v.get()) << "\"}"; } - void visit(const cpptoml::value& v) + void visit(const toml::value& v) { stream_ << "{\"type\":\"integer\",\"value\":\"" << v.get() << "\"}"; } - void visit(const cpptoml::value& v) + void visit(const toml::value& v) { stream_ << "{\"type\":\"float\",\"value\":\"" << v.get() << "\"}"; } - void visit(const cpptoml::value& v) + void visit(const toml::value& v) { stream_ << "{\"type\":\"local_date\",\"value\":\"" << v.get() << "\"}"; } - void visit(const cpptoml::value& v) + void visit(const toml::value& v) { stream_ << "{\"type\":\"local_time\",\"value\":\"" << v.get() << "\"}"; } - void visit(const cpptoml::value& v) + void visit(const toml::value& v) { stream_ << "{\"type\":\"local_datetime\",\"value\":\"" << v.get() << "\"}"; } - void visit(const cpptoml::value& v) + void visit(const toml::value& v) { stream_ << "{\"type\":\"datetime\",\"value\":\"" << v.get() << "\"}"; } - void visit(const cpptoml::value& v) + void visit(const toml::value& v) { stream_ << "{\"type\":\"bool\",\"value\":\"" << v << "\"}"; } - void visit(const cpptoml::array& arr) + void visit(const toml::array& arr) { stream_ << "{\"type\":\"array\",\"value\":["; auto it = arr.get().begin(); @@ -70,27 +69,13 @@ class toml_test_writer stream_ << "]}"; } - void visit(const cpptoml::table_array& tarr) - { - stream_ << "["; - auto arr = tarr.get(); - auto ait = arr.begin(); - while (ait != arr.end()) - { - (*ait)->accept(*this); - if (++ait != arr.end()) - stream_ << ", "; - } - stream_ << "]"; - } - - void visit(const cpptoml::table& t) + void visit(const toml::table& t) { stream_ << "{"; auto it = t.begin(); while (it != t.end()) { - stream_ << '"' << cpptoml::toml_writer::escape_string(it->first) + stream_ << '"' << toml::toml_writer::escape_string(it->first) << "\":"; it->second->accept(*this); if (++it != t.end()) @@ -106,15 +91,15 @@ class toml_test_writer int main() { std::cout.precision(std::numeric_limits::max_digits10); - cpptoml::parser p{std::cin}; + toml::parser p{std::cin}; try { - std::shared_ptr g = p.parse(); + std::shared_ptr g = p.parse(); toml_test_writer writer{std::cout}; g->accept(writer); std::cout << std::endl; } - catch (const cpptoml::parse_exception& ex) + catch (const toml::parse_error& ex) { std::cerr << "Parsing failed: " << ex.what() << std::endl; return 1; diff --git a/tominal/toml.h b/include/tominal/toml.h similarity index 97% rename from tominal/toml.h rename to include/tominal/toml.h index 6f96c6d..b4850bf 100644 --- a/tominal/toml.h +++ b/include/tominal/toml.h @@ -8,6 +8,8 @@ { #define TOML_NAMESPACE_END } +#include + namespace toml { TOML_NAMESPACE_BEGIN diff --git a/tominal/toml_array.h b/include/tominal/toml_array.h similarity index 98% rename from tominal/toml_array.h rename to include/tominal/toml_array.h index 96bd758..080f5de 100644 --- a/tominal/toml_array.h +++ b/include/tominal/toml_array.h @@ -1,7 +1,6 @@ #pragma once #include - #include "toml_base.h" #include "toml_node.h" @@ -178,7 +177,8 @@ class array final : public node } template ::type, - typename V = std::invoke_result_t> + typename V = std::invoke_result_t, + typename = std::enable_if_t>> std::vector map_collect(F &&f) const { std::vector result; diff --git a/tominal/toml_base.h b/include/tominal/toml_base.h similarity index 99% rename from tominal/toml_base.h rename to include/tominal/toml_base.h index 8f9af1b..e6ca80c 100644 --- a/tominal/toml_base.h +++ b/include/tominal/toml_base.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include diff --git a/tominal/toml_date_time.h b/include/tominal/toml_date_time.h similarity index 88% rename from tominal/toml_date_time.h rename to include/tominal/toml_date_time.h index bed0180..a8421ec 100644 --- a/tominal/toml_date_time.h +++ b/include/tominal/toml_date_time.h @@ -1,5 +1,6 @@ #pragma once +#include #include "toml_base.h" namespace toml @@ -79,9 +80,9 @@ inline std::ostream &operator<<(std::ostream &os, const local_date &dt) os.fill('0'); using std::setw; - os << setw(4) << static_cast(dt.year) << '-' - << setw(2) << static_cast(dt.month) << '-' - << setw(2) << static_cast(dt.day); + os << setw(4) << static_cast(dt.year) << '-' + << setw(2) << static_cast(dt.month) << '-' + << setw(2) << static_cast(dt.day); return os; } @@ -92,9 +93,9 @@ inline std::ostream &operator<<(std::ostream &os, const local_time <ime) os.fill('0'); using std::setw; - os << setw(2) << static_cast(ltime.hour) << ':' - << setw(2) << static_cast(ltime.minute) << ':' - << setw(2) << static_cast(ltime.second); + os << setw(2) << static_cast(ltime.hour) << ':' + << setw(2) << static_cast(ltime.minute) << ':' + << setw(2) << static_cast(ltime.second); if (ltime.nanosecond > 0) { diff --git a/tominal/toml_node.h b/include/tominal/toml_node.h similarity index 83% rename from tominal/toml_node.h rename to include/tominal/toml_node.h index cea0dcd..acdfba2 100644 --- a/tominal/toml_node.h +++ b/include/tominal/toml_node.h @@ -1,5 +1,6 @@ #pragma once +#include #include "toml_base.h" namespace toml @@ -133,14 +134,6 @@ class node : public std::enable_shared_from_this switch (type()) { - case base_type::None: - [[fallthrough]]; - case base_type::Table: - [[fallthrough]]; - case base_type::Array: - { - return std::nullopt; - } case base_type::String: { if constexpr (value_type_traits::value == base_type::String) @@ -206,9 +199,10 @@ class node : public std::enable_shared_from_this } case base_type::LocalDateTime: { - if constexpr (value_type_traits::value == base_type::LocalDateTime) + if constexpr (value_type_traits::value == base_type::LocalDateTime || + value_type_traits::value == base_type::LocalDate) { - return {as_value()->get()}; + return {static_cast(as_value()->get())}; } else { @@ -217,9 +211,11 @@ class node : public std::enable_shared_from_this } case base_type::OffsetDateTime: { - if constexpr (value_type_traits::value == base_type::OffsetDateTime) + if constexpr (value_type_traits::value == base_type::OffsetDateTime || + value_type_traits::value == base_type::LocalDateTime || + value_type_traits::value == base_type::LocalDate) { - return {as_value()->get()}; + return {static_cast(as_value()->get())}; } else { @@ -231,24 +227,30 @@ class node : public std::enable_shared_from_this } } - template + template >::type> inline auto value_or(T &&default_value) const noexcept { static_assert(is_value_promotable>, "default value must be of (or be promotable to) one of the TOML types"); - using return_type = typename value_type_traits>::type; - if (auto val = this->value()) + if (auto val = this->value()) { return *val; } else { - return return_type{std::forward(default_value)}; + return U{std::forward(default_value)}; } } - template > + template >> + inline auto value_or_default() const noexcept + { + return this->value_or({}); + } + + template , + typename = std::enable_if_t>> std::optional map(F &&f) const { if constexpr (is_value_promotable) @@ -275,6 +277,33 @@ class node : public std::enable_shared_from_this return std::nullopt; } + template , + typename = std::enable_if_t>> + void map(F &&f) const + { + if constexpr (is_value_promotable) + { + if (const auto val = this->value()) + { + f(val.value()); + } + } + else if constexpr (std::is_same_v) + { + if (const auto arr = this->as_array()) + { + f(*arr); + } + } + else if constexpr (std::is_same_v) + { + if (const auto tbl = this->as_table()) + { + f(*tbl); + } + } + } + template void accept(Visitor &&visitor, Args &&... args) const; diff --git a/tominal/toml_node_view.h b/include/tominal/toml_node_view.h similarity index 74% rename from tominal/toml_node_view.h rename to include/tominal/toml_node_view.h index ce62308..72cce5b 100644 --- a/tominal/toml_node_view.h +++ b/include/tominal/toml_node_view.h @@ -74,6 +74,18 @@ class node_view final return node_ ? node_->as_table() : nullptr; } + bool contains(std::string_view key) const + { + if (auto tbl = as_table()) + { + return tbl->contains(key); + } + else + { + return false; + } + } + template std::optional value() const noexcept { @@ -101,15 +113,31 @@ class node_view final } } + template + inline auto value_or_default() const noexcept + { + using return_type = decltype(node_->value_or_default()); + + return node_ ? node_->value_or_default() : return_type{}; + } + node_view operator[](std::string_view key) const { + node_view result{nullptr}; + auto position = key.find('.'); + if (auto tbl = this->as_table()) { - return {(*tbl)[key]}; + result = node_view((*tbl)[key.substr(0, position)]); + } + + if (position != std::string_view::npos && position + 1 < key.size()) + { + return result[key.substr(position + 1)]; } else { - return {nullptr}; + return result; } } @@ -125,12 +153,23 @@ class node_view final } } - template > - std::optional map(F f) const + template , + typename = std::enable_if_t>> + std::optional map(F &&f) const { return node_ ? node_->map(f) : std::nullopt; } + template , + typename = std::enable_if_t>> + void map(F &&f) const + { + if (node_) + { + node_->map(f); + } + } + template ::type> std::vector collect() const { @@ -144,7 +183,8 @@ class node_view final } } - template > + template , + typename = std::enable_if_t>> std::vector map_collect(F &&f) const { if (auto arr = this->as_array()) diff --git a/tominal/toml_parser.h b/include/tominal/toml_parser.h similarity index 99% rename from tominal/toml_parser.h rename to include/tominal/toml_parser.h index 982d5a2..6805916 100644 --- a/tominal/toml_parser.h +++ b/include/tominal/toml_parser.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include diff --git a/tominal/toml_table.h b/include/tominal/toml_table.h similarity index 100% rename from tominal/toml_table.h rename to include/tominal/toml_table.h diff --git a/tominal/toml_value.h b/include/tominal/toml_value.h similarity index 100% rename from tominal/toml_value.h rename to include/tominal/toml_value.h diff --git a/tominal/toml_writer.h b/include/tominal/toml_writer.h similarity index 99% rename from tominal/toml_writer.h rename to include/tominal/toml_writer.h index d70e08a..8b19471 100644 --- a/tominal/toml_writer.h +++ b/include/tominal/toml_writer.h @@ -1,3 +1,6 @@ +#pragma once + +#include #include "toml_value.h" #include "toml_array.h" #include "toml_table.h" diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt deleted file mode 100644 index e4447a8..0000000 --- a/test/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -include(GoogleTest) - -add_executable(toml_test - parse_test.cc -) - -target_link_libraries( - toml_test - toml - gtest_main -) - -gtest_discover_tests(toml_test) \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..4701aad --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,21 @@ +include(GoogleTest) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +add_executable(toml_test + parse_test.cc +) + +target_link_libraries( + toml_test + tominal + gtest_main +) + +gtest_discover_tests(toml_test) \ No newline at end of file diff --git a/test/parse_test.cc b/tests/parse_test.cc similarity index 79% rename from test/parse_test.cc rename to tests/parse_test.cc index 96f43ea..bd703ae 100644 --- a/test/parse_test.cc +++ b/tests/parse_test.cc @@ -12,8 +12,8 @@ TEST(toml_test, parse_example) auto view = parse_file("../examples/example.toml").ok(); EXPECT_TRUE(bool(view)); - EXPECT_EQ(view["title"].value_or(""sv), "TOML Example"sv); - EXPECT_EQ(view["owner"]["name"].value_or(""sv), "Tom Preston-Werner"sv); + EXPECT_EQ(view["title"].value_or_default(), "TOML Example"); + EXPECT_EQ(view["owner"]["name"].value_or("Tom"sv), "Tom Preston-Werner"sv); EXPECT_TRUE(view["owner"]["dob"].map([](const auto &val) { return val.year == 1979 && @@ -23,6 +23,8 @@ TEST(toml_test, parse_example) }) .value()); + EXPECT_EQ(view["owner.doc"].value_or_default().month, 0); + EXPECT_FALSE(view["database"]["enabled"].map([](const auto &val) { return !val; }) @@ -31,11 +33,11 @@ TEST(toml_test, parse_example) EXPECT_EQ(view["clients"][0]["data"][0][0].value_or(""sv), "gamma"sv); EXPECT_EQ(view["clients"][0]["data"][0].collect(), (std::vector{"gamma"sv, "delta"sv})); - EXPECT_EQ(view["database"]["ports"].map( - [](const auto &val) { - return std::make_pair(val.at(1)->value_or(0), - val.at(2)->value_or(0)); - }) + EXPECT_EQ(view["database.ports"].map( + [](const auto &val) { + return std::make_pair(val.at(1)->value_or(0), + val.at(2)->value_or(0)); + }) .value(), (std::pair{8001, 8002})); }