From acbc2a73cb3553c8f178b95dde34ae1c1cfa9b57 Mon Sep 17 00:00:00 2001 From: KerstinKeller Date: Fri, 14 Jun 2019 17:24:21 +0200 Subject: [PATCH 01/12] Allow to install tom11 library with CMake. Add option to build tests. --- CMakeLists.txt | 69 ++++++++++++++++++++++++++++++++++++- cmake/toml11Config.cmake.in | 2 ++ tests/CMakeLists.txt | 3 +- 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 cmake/toml11Config.cmake.in diff --git a/CMakeLists.txt b/CMakeLists.txt index f76b700..daf57fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,17 @@ cmake_minimum_required(VERSION 2.8) enable_testing() + project(toml11) +set(toml11_VERSION_MAYOR 2) +set(toml11_VERSION_MINOR 3) +set(toml11_VERSION_PATCH 1) +set(toml11_VERSION + "${toml11_VERSION_MAYOR}.${toml11_VERSION_MINOR}.${toml11_VERSION_PATCH}" +) + +option(toml11_BUILD_TEST "Build toml tests" ON) + include(CheckCXXCompilerFlag) if("${CMAKE_VERSION}" VERSION_GREATER 3.1) set(CMAKE_CXX_EXTENSIONS OFF) @@ -34,5 +44,62 @@ else() endif() endif() -include_directories(${PROJECT_SOURCE_DIR}) +# Set some common directories +include(GNUInstallDirs) +set(toml11_install_cmake_dir ${CMAKE_INSTALL_LIBDIR}/cmake/toml11) +set(toml11_install_include_dir ${CMAKE_INSTALL_INCLUDEDIR}) +set(toml11_config_dir ${CMAKE_CURRENT_BINARY_DIR}/cmake/) +set(toml11_config ${toml11_config_dir}/toml11Config.cmake) +set(toml11_config_version ${toml11_config_dir}/toml11ConfigVersion.cmake) + +add_library(toml11 INTERFACE) +target_include_directories(toml11 INTERFACE + $ + $ +) +add_library(toml11::toml11 ALIAS toml11) + +# Write config and version config files +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + ${toml11_config_version} + VERSION ${toml11_VERSION} + COMPATIBILITY SameMajorVersion +) + +configure_package_config_file( + cmake/toml11Config.cmake.in + ${toml11_config} + INSTALL_DESTINATION ${toml11_install_cmake_dir} + PATH_VARS toml11_install_cmake_dir +) + +# Install config files +install(FILES ${toml11_config} ${toml11_config_version} + DESTINATION ${toml11_install_cmake_dir} +) + +# Install header files +install( + FILES toml.hpp + DESTINATION "${toml11_install_include_dir}" +) +install( + DIRECTORY "toml" + DESTINATION "${toml11_install_include_dir}" + FILES_MATCHING PATTERN "*.hpp" +) + +# Export targets and install them +install(TARGETS toml11 + EXPORT toml11Targets +) +install(EXPORT toml11Targets + FILE toml11Targets.cmake + DESTINATION ${toml11_install_cmake_dir} + NAMESPACE toml11:: +) + +if (toml11_BUILD_TEST) add_subdirectory(tests) +endif () \ No newline at end of file diff --git a/cmake/toml11Config.cmake.in b/cmake/toml11Config.cmake.in new file mode 100644 index 0000000..80c76f7 --- /dev/null +++ b/cmake/toml11Config.cmake.in @@ -0,0 +1,2 @@ +@PACKAGE_INIT@ +include("@PACKAGE_toml11_install_cmake_dir@/toml11Targets.cmake") \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 04ab6c4..6577085 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -88,7 +88,7 @@ add_definitions(-DUNITTEST_FRAMEWORK_LIBRARY_EXIST) foreach(TEST_NAME ${TEST_NAMES}) add_executable(${TEST_NAME} ${TEST_NAME}.cpp) - target_link_libraries(${TEST_NAME} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) + target_link_libraries(${TEST_NAME} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} toml11::toml11) target_include_directories(${TEST_NAME} PRIVATE ${Boost_INCLUDE_DIRS}) add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME} WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) @@ -105,3 +105,4 @@ endforeach(TEST_NAME) add_executable(test_multiple_translation_unit test_multiple_translation_unit_1.cpp test_multiple_translation_unit_2.cpp) +target_link_libraries(test_multiple_translation_unit toml11::toml11) From 62e8d58d8d3a3020a5bf3ced5c9a96ec29e4781e Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Sun, 16 Jun 2019 17:32:29 +0900 Subject: [PATCH 02/12] feat: guess possible format errors --- toml/parser.hpp | 130 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 113 insertions(+), 17 deletions(-) diff --git a/toml/parser.hpp b/toml/parser.hpp index a345611..6271d22 100644 --- a/toml/parser.hpp +++ b/toml/parser.hpp @@ -1410,41 +1410,132 @@ parse_inline_table(location& loc) } template -value_t guess_number_type(const location& l) +result guess_number_type(const location& l) { + // This function tries to find some (common) mistakes by checking characters + // that follows the last character of a value. But it is often difficult + // because some non-newline characters can appear after a value. E.g. + // spaces, tabs, commas (in an array or inline table), closing brackets + // (of an array or inline table), comment-sign (#). Since this function + // does not parse further, those characters are always allowed to be there. location loc = l; - if(lex_offset_date_time::invoke(loc)) {return value_t::OffsetDatetime;} + if(lex_offset_date_time::invoke(loc)) {return ok(value_t::OffsetDatetime);} loc.reset(l.iter()); - if(lex_local_date_time::invoke(loc)) {return value_t::LocalDatetime;} + if(lex_local_date_time::invoke(loc)) + { + // bad offset may appear after this. + if(loc.iter() != loc.end() && (*loc.iter() == '+' || *loc.iter() == '-' + || *loc.iter() == 'Z' || *loc.iter() == 'z')) + { + return err(format_underline("[error] bad offset: should be [+-]HH:MM or Z", + {{std::addressof(l), "[+-]HH:MM or Z"}}, + {"OK: +09:00, -05:30", "NG: +9:00, -5:30"})); + } + return ok(value_t::LocalDatetime); + } loc.reset(l.iter()); - if(lex_local_date::invoke(loc)) {return value_t::LocalDate;} + if(lex_local_date::invoke(loc)) + { + // bad time may appear after this. + // A space is allowed as a delimiter between local time. But there are + // both cases in which a space becomes valid or invalid. + // - invalid: 2019-06-16 7:00:00 + // - valid : 2019-06-16 07:00:00 + if(loc.iter() != loc.end()) + { + const auto c = *loc.iter(); + if(c == 'T' || c == 't' || ('0' <= c && c <= '9')) + { + return err(format_underline("[error] bad time: should be HH:MM:SS.subsec", + {{std::addressof(l), "HH:MM:SS.subsec"}}, + {"OK: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999", + "NG: 1979-05-27T7:32:00, 1979-05-27 7:32"})); + } + if(c == ' ' && std::next(loc.iter()) != loc.end() && + ('0' <= *std::next(loc.iter()) && *std::next(loc.iter())<= '9')) + { + return err(format_underline("[error] bad time: should be HH:MM:SS.subsec", + {{std::addressof(l), "HH:MM:SS.subsec"}}, + {"OK: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999", + "NG: 1979-05-27T7:32:00, 1979-05-27 7:32"})); + } + } + return ok(value_t::LocalDate); + } loc.reset(l.iter()); - if(lex_local_time::invoke(loc)) {return value_t::LocalTime;} + if(lex_local_time::invoke(loc)) {return ok(value_t::LocalTime);} loc.reset(l.iter()); - if(lex_float::invoke(loc)) {return value_t::Float;} + if(lex_float::invoke(loc)) + { + if(loc.iter() != loc.end() && *loc.iter() == '_') + { + return err(format_underline("[error] bad float: `_` should be surrounded by digits", + {{std::addressof(l), "here"}}, + {"OK: +1.0, -2e-2, 3.141_592_653_589, inf, nan", + "NG: _1.0, 1.0_, 1_.0, 1.0__0"})); + } + return ok(value_t::Float); + } loc.reset(l.iter()); - return value_t::Integer; + if(lex_integer::invoke(loc)) + { + if(loc.iter() != loc.end()) + { + const auto c = *loc.iter(); + if(c == '_') + { + return err(format_underline("[error] bad integer: `_` should be surrounded by digits", + {{std::addressof(l), "here"}}, + {"OK: -42, 1_000, 1_2_3_4_5, 0xCOFFEE, 0b0010, 0o755", + "NG: 1__000, 0123"})); + } + if('0' <= c && c <= '9') + { + return err(format_underline("[error] bad integer: leading zero", + {{std::addressof(l), "here"}}, + {"OK: -42, 1_000, 1_2_3_4_5, 0xCOFFEE, 0b0010, 0o755", + "NG: 1__000, 0123"})); + } + if(c == ':' || c == '-') + { + return err(format_underline("[error] bad datetime: invalid format", + {{std::addressof(l), "here"}}, + {"OK: 1979-05-27T07:32:00-07:00, 1979-05-27 07:32:00.999999Z", + "NG: 1979-05-27T7:32:00-7:00, 1979-05-27 7:32-00:30"})); + } + if(c == '.' || c == 'e' || c == 'E') + { + return err(format_underline("[error] bad float: invalid format", + {{std::addressof(l), "here"}}, + {"OK: +1.0, -2e-2, 3.141_592_653_589, inf, nan", + "NG: _1.0, 1.0_, 1_.0, 1.0__0"})); + } + } + return ok(value_t::Integer); + } + return err(format_underline("[error] bad format: unknown value appeared", + {{std::addressof(l), "here"}})); } template -value_t guess_value_type(const location& loc) +result guess_value_type(const location& loc) { switch(*loc.iter()) { - case '"' : {return value_t::String; } - case '\'': {return value_t::String; } - case 't' : {return value_t::Boolean;} - case 'f' : {return value_t::Boolean;} - case '[' : {return value_t::Array; } - case '{' : {return value_t::Table; } - case 'i' : {return value_t::Float; } // inf. - case 'n' : {return value_t::Float; } // nan. + case '"' : {return ok(value_t::String); } + case '\'': {return ok(value_t::String); } + case 't' : {return ok(value_t::Boolean);} + case 'f' : {return ok(value_t::Boolean);} + case '[' : {return ok(value_t::Array); } + case '{' : {return ok(value_t::Table); } + case 'i' : {return ok(value_t::Float); } // inf. + case 'n' : {return ok(value_t::Float); } // nan. default : {return guess_number_type(loc);} } } @@ -1459,7 +1550,12 @@ result parse_value(location& loc) {{std::addressof(loc), ""}})); } - switch(guess_value_type(loc)) + const auto type = guess_value_type(loc); + if(!type) + { + return err(type.unwrap_err()); + } + switch(type.unwrap()) { case value_t::Boolean : {return parse_boolean(loc); } case value_t::Integer : {return parse_integer(loc); } From cf1c9371b69d8e311b7a2cd494a74da5ac1aa7a1 Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Sun, 16 Jun 2019 17:52:42 +0900 Subject: [PATCH 03/12] fix: correct example and positions in err msgs --- toml/parser.hpp | 52 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/toml/parser.hpp b/toml/parser.hpp index 6271d22..89e8049 100644 --- a/toml/parser.hpp +++ b/toml/parser.hpp @@ -1430,7 +1430,7 @@ result guess_number_type(const location& l) || *loc.iter() == 'Z' || *loc.iter() == 'z')) { return err(format_underline("[error] bad offset: should be [+-]HH:MM or Z", - {{std::addressof(l), "[+-]HH:MM or Z"}}, + {{std::addressof(loc), "[+-]HH:MM or Z"}}, {"OK: +09:00, -05:30", "NG: +9:00, -5:30"})); } return ok(value_t::LocalDatetime); @@ -1447,18 +1447,26 @@ result guess_number_type(const location& l) if(loc.iter() != loc.end()) { const auto c = *loc.iter(); - if(c == 'T' || c == 't' || ('0' <= c && c <= '9')) + if(c == 'T' || c == 't') { return err(format_underline("[error] bad time: should be HH:MM:SS.subsec", - {{std::addressof(l), "HH:MM:SS.subsec"}}, + {{std::addressof(loc), "HH:MM:SS.subsec"}}, + {"OK: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999", + "NG: 1979-05-27T7:32:00, 1979-05-27 7:32"})); + } + if('0' <= c && c <= '9') + { + return err(format_underline("[error] bad time: missing T", + {{std::addressof(loc), "T or space required here"}}, {"OK: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999", "NG: 1979-05-27T7:32:00, 1979-05-27 7:32"})); } if(c == ' ' && std::next(loc.iter()) != loc.end() && ('0' <= *std::next(loc.iter()) && *std::next(loc.iter())<= '9')) { + loc.advance(); return err(format_underline("[error] bad time: should be HH:MM:SS.subsec", - {{std::addressof(l), "HH:MM:SS.subsec"}}, + {{std::addressof(loc), "HH:MM:SS.subsec"}}, {"OK: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999", "NG: 1979-05-27T7:32:00, 1979-05-27 7:32"})); } @@ -1475,9 +1483,9 @@ result guess_number_type(const location& l) if(loc.iter() != loc.end() && *loc.iter() == '_') { return err(format_underline("[error] bad float: `_` should be surrounded by digits", - {{std::addressof(l), "here"}}, + {{std::addressof(loc), "here"}}, {"OK: +1.0, -2e-2, 3.141_592_653_589, inf, nan", - "NG: _1.0, 1.0_, 1_.0, 1.0__0"})); + "NG: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0"})); } return ok(value_t::Float); } @@ -1491,36 +1499,52 @@ result guess_number_type(const location& l) if(c == '_') { return err(format_underline("[error] bad integer: `_` should be surrounded by digits", - {{std::addressof(l), "here"}}, - {"OK: -42, 1_000, 1_2_3_4_5, 0xCOFFEE, 0b0010, 0o755", + {{std::addressof(loc), "here"}}, + {"OK: -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755", "NG: 1__000, 0123"})); } if('0' <= c && c <= '9') { + // leading zero. point '0' + loc.retrace(); return err(format_underline("[error] bad integer: leading zero", - {{std::addressof(l), "here"}}, - {"OK: -42, 1_000, 1_2_3_4_5, 0xCOFFEE, 0b0010, 0o755", + {{std::addressof(loc), "here"}}, + {"OK: -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755", "NG: 1__000, 0123"})); } if(c == ':' || c == '-') { return err(format_underline("[error] bad datetime: invalid format", - {{std::addressof(l), "here"}}, + {{std::addressof(loc), "here"}}, {"OK: 1979-05-27T07:32:00-07:00, 1979-05-27 07:32:00.999999Z", "NG: 1979-05-27T7:32:00-7:00, 1979-05-27 7:32-00:30"})); } if(c == '.' || c == 'e' || c == 'E') { return err(format_underline("[error] bad float: invalid format", - {{std::addressof(l), "here"}}, + {{std::addressof(loc), "here"}}, {"OK: +1.0, -2e-2, 3.141_592_653_589, inf, nan", - "NG: _1.0, 1.0_, 1_.0, 1.0__0"})); + "NG: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0"})); } } return ok(value_t::Integer); } + if(loc.iter() != loc.end() && *loc.iter() == '.') + { + return err(format_underline("[error] bad float: invalid format", + {{std::addressof(loc), "integer part required before this"}}, + {"OK: +1.0, -2e-2, 3.141_592_653_589, inf, nan", + "NG: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0"})); + } + if(loc.iter() != loc.end() && *loc.iter() == '_') + { + return err(format_underline("[error] bad number: `_` should be surrounded by digits", + {{std::addressof(loc), "`_` is not surrounded by digits"}}, + {"OK: -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755", + "NG: 1__000, 0123"})); + } return err(format_underline("[error] bad format: unknown value appeared", - {{std::addressof(l), "here"}})); + {{std::addressof(loc), "here"}})); } template From cbaaaaca7cb9639a375178558ba2b27525926414 Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Sun, 16 Jun 2019 19:52:59 +0900 Subject: [PATCH 04/12] :rewind: revert recursive find function I found that in a user-code (I'm also one of the users of this library), this new feature sometimes causes an error. Some of my code won't compile because of this change. Since toml::table is convertible to toml::value *implicitly*, if toml::find(table, key, tablename) was called, the overload resolution becomes ambiguous with toml::find( value, key1, key2). But dropping support for toml::find(toml::table, key, tablename) is a breaking change. So I concluded that now is not the right time yet. --- README.md | 86 +++++---------------------------- tests/test_get_related_func.cpp | 80 +++--------------------------- toml/get.hpp | 26 ---------- 3 files changed, 20 insertions(+), 172 deletions(-) diff --git a/README.md b/README.md index b7a6d58..0dce0d1 100644 --- a/README.md +++ b/README.md @@ -451,58 +451,36 @@ const auto value = toml::expect(data.at("number")) ## Finding a value from a table -toml11 provides utility function to find a value from `toml::value` and `toml::table`. +toml11 provides utility function to find a value from `toml::table`. +Of course, you can do this in your own way with `toml::get` because +it just searches an `unordered_map` and returns a value if it exists. ```cpp const auto data = toml::parse("example.toml"); -// find a value named "num" from `data`. -const auto num = toml::find(data, "num"); +const auto num = toml::find(data, "num", /*for err msg*/"example.toml"); ``` If the value does not exist, it throws `std::out_of_range` with an error message. -But, since `toml::table` is just an alias of `std::unordered_map`, -you need to pass a name to the function to show the name in the exception. - -```cpp -const auto num = toml::find(data, "num", "example.toml"); -``` ```console terminate called after throwing an instance of 'std::out_of_range' what(): [error] key "num" not found in example.toml -# ^^^^^^^^^^^^ this part ``` -Of course, you can do this in your own way with `toml::get` because -it just searches an `unordered_map` and returns a value if it exists. +You can use this with a `toml::value` that is expected to be a `toml::table`. +It automatically casts the value to table. ```cpp -const toml::table data = toml::parse("example.toml"); -if(data.count("num") == 1) -{ - const auto num = toml::get(data.at("num")); - // more stuff ... -} -``` - ----- - -You can also use this with a `toml::value` that is expected to contain a `toml::table`. -It automatically casts the `toml::value` to a `toml::table`. If it failed to cast, -it would throw a `toml::type_error`. - -```cpp -// # expecting the following example.toml +const auto data = toml::parse("example.toml"); +const auto num = toml::find(data.at("table"), "num"); +// expecting the following example.toml // [table] // num = 42 -const toml::table data = toml::parse("example.toml"); -const toml::value table = data.at("table"); -const auto num = toml::find(table, "num"); ``` -In this case, because the `toml::value table` knows the locatoin of itself, -you don't need to pass the name to show it in an error message. -`toml::find` will automatically format an error message with the location of the table. +In this case, because the value `data.at("table")` knows the locatoin of itself, +you don't need to pass where you find the value. +`toml::find` will show you an error message including table location. ```console terminate called after throwing an instance of 'std::out_of_range' @@ -512,45 +490,7 @@ terminate called after throwing an instance of 'std::out_of_range' | ~~~~~~~ in this table ``` -The default return value of the `toml::find` is a `toml::value`. - -```cpp -const toml::value& subtable = toml::find(table, "subtable"); -``` - -There are several ways to find a value buried in a deep recursion of tables. - -First, you can call `toml::find` as many as you need. - -```cpp -// # expecting the following example.toml -// answer.to.the.ultimate.question = 42 -// # is equivalent to {"answer": {"to":{"the":{"ultimate:{"question":42}}}}} - -const toml::table data = toml::parse("example.toml"); -const int a = toml::find(toml::find(toml::find(toml::find(toml::find( - data, "answer"), "to"), "the"), "ultimate"), "question"); -``` - -But it is a bother. - -After toml11 v2.4.0, you can pass a `toml::value` and as many number of keys as you want. - -```cpp -const toml::table data = toml::parse("example.toml"); -const int a = toml::find(data.at("answer"), "to", "the", "ultimate", "question"); -``` - -__NOTE__: -Currently, this function does not support `toml::table` because of some -technical reason. Please make sure that the type of the first argument is -`toml::value`. The main reason is that the `toml::table` may take an additional string -as the third argumnet to show its location in an error message. And the -most confusing part is that `toml::parse` returns `toml::table`, not a -`toml::value`. This confusing API will hopefully be resolved in the next -major update, v3 (it will contain some unavoidable breaking changes). - ----- +If it's not a `toml::table`, the same error as "invalid type" would be thrown. There is another utility function, `toml::find_or`. It is almost same as `toml::find`, but returns a default value if the value is diff --git a/tests/test_get_related_func.cpp b/tests/test_get_related_func.cpp index e6e448c..30eb362 100644 --- a/tests/test_get_related_func.cpp +++ b/tests/test_get_related_func.cpp @@ -12,9 +12,8 @@ #include #include -BOOST_AUTO_TEST_CASE(test_find_for_value) +BOOST_AUTO_TEST_CASE(test_find) { - // value itself is not a table { toml::value v(true); bool thrown = false; @@ -28,87 +27,22 @@ BOOST_AUTO_TEST_CASE(test_find_for_value) } BOOST_CHECK(thrown); } - // the value corresponding to the key is not the expected type + { - toml::value v{{"key", 42}}; - bool thrown = false; - try - { - toml::find(v, "key"); - } - catch(toml::type_error const& te) - { - thrown = true; - } - BOOST_CHECK(thrown); + toml::table v{{"num", 42}}; + BOOST_CHECK_EQUAL(42, toml::find(v, "num")); + toml::find(v, "num") = 54; + BOOST_CHECK_EQUAL(54, toml::find(v, "num")); } { toml::value v = toml::table{{"num", 42}}; BOOST_CHECK_EQUAL(42, toml::find(v, "num")); - - // reference that can be used to modify the content - auto& num = toml::find(v, "num"); - num = 54; + toml::find(v, "num") = 54; BOOST_CHECK_EQUAL(54, toml::find(v, "num")); } - - // recursively search tables - { - toml::value v = toml::table{ - {"a", toml::table{ - {"b", toml::table{ - {"c", toml::table{ - {"d", 42} - }} - }} - }} - }; - BOOST_CHECK_EQUAL(42, toml::find(v, "a", "b", "c", "d")); - - // reference that can be used to modify the content - auto& num = toml::find(v, "a", "b", "c", "d"); - num = 54; - BOOST_CHECK_EQUAL(54, toml::find(v, "a", "b", "c", "d")); - - const std::string a("a"), b("b"), c("c"), d("d"); - auto& num2 = toml::find(v, a, b, c, d); - num2 = 42; - BOOST_CHECK_EQUAL(42, toml::find(v, a, b, c, d)); - } } -BOOST_AUTO_TEST_CASE(test_find_for_table) -{ - // the value corresponding to the key is not the expected type - { - toml::table v{{"key", 42}}; - bool thrown = false; - try - { - toml::find(v, "key"); - } - catch(toml::type_error const& te) - { - thrown = true; - } - BOOST_CHECK(thrown); - } - - { - toml::table v{{"num", 42}}; - BOOST_CHECK_EQUAL(42, toml::find(v, "num")); - - // reference that can be used to modify the content - auto& num = toml::find(v, "num"); - num = 54; - BOOST_CHECK_EQUAL(54, toml::find(v, "num")); - } - - // recursive search is not provided for tables. -} - - BOOST_AUTO_TEST_CASE(test_get_or) { // requires conversion int -> uint diff --git a/toml/get.hpp b/toml/get.hpp index fb2f403..b3bc337 100644 --- a/toml/get.hpp +++ b/toml/get.hpp @@ -430,32 +430,6 @@ find(toml::value&& v, const toml::key& ky) return ::toml::get(std::move(tab[ky])); } -// -------------------------------------------------------------------------- -// toml::find(toml::value, toml::key, Ts&& ... keys) -// -// Note: C++ draft N3337 (14.1.11) says that... -// > If a template-parameter of a class template or alias template has a default -// > template-argument, each subsequent template-parameter shall either have a -// > default template-argument supplied or be a template parameter pack. -// So the template parameter pack can appear after a default template argument. -template -decltype(::toml::get(std::declval())) -find(const ::toml::value& v, const ::toml::key& ky, Ts&& ... keys) -{ - return ::toml::find(::toml::find(v, ky), std::forward(keys)...); -} -template -decltype(::toml::get(std::declval<::toml::value&>())) -find(::toml::value& v, const ::toml::key& ky, Ts&& ... keys) -{ - return ::toml::find(::toml::find(v, ky), std::forward(keys)...); -} -template -decltype(::toml::get(std::declval<::toml::value&&>())) -find(::toml::value&& v, const ::toml::key& ky, Ts&& ... keys) -{ - return ::toml::find(::toml::find(std::move(v), ky), std::forward(keys)...); -} // ============================================================================ // get_or(value, fallback) From fd7da057980ae2e267fde582f22dbbc8b5b16faf Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Sun, 16 Jun 2019 20:31:08 +0900 Subject: [PATCH 05/12] doc: update README --- README.md | 67 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 0dce0d1..c6133e5 100644 --- a/README.md +++ b/README.md @@ -451,36 +451,20 @@ const auto value = toml::expect(data.at("number")) ## Finding a value from a table -toml11 provides utility function to find a value from `toml::table`. -Of course, you can do this in your own way with `toml::get` because -it just searches an `unordered_map` and returns a value if it exists. +toml11 provides a utility function to find a value from `toml::value` and `toml::table`. ```cpp -const auto data = toml::parse("example.toml"); -const auto num = toml::find(data, "num", /*for err msg*/"example.toml"); +const toml::value data = /* ... */; + +// find a value named "num" from `data`. +const auto num = toml::find(data, "num"); ``` -If the value does not exist, it throws `std::out_of_range` with an error message. +When you pass a `toml::value`, `toml::find` first casts it to `toml::table`. +If casting failed, `toml::type_error` will be thrown. -```console -terminate called after throwing an instance of 'std::out_of_range' - what(): [error] key "num" not found in example.toml -``` - -You can use this with a `toml::value` that is expected to be a `toml::table`. -It automatically casts the value to table. - -```cpp -const auto data = toml::parse("example.toml"); -const auto num = toml::find(data.at("table"), "num"); -// expecting the following example.toml -// [table] -// num = 42 -``` - -In this case, because the value `data.at("table")` knows the locatoin of itself, -you don't need to pass where you find the value. -`toml::find` will show you an error message including table location. +When the value does not exist, it throws `std::out_of_range` with an error message. +By passing a `toml::value`, it shows an informative error message like the following. ```console terminate called after throwing an instance of 'std::out_of_range' @@ -490,17 +474,36 @@ terminate called after throwing an instance of 'std::out_of_range' | ~~~~~~~ in this table ``` -If it's not a `toml::table`, the same error as "invalid type" would be thrown. - -There is another utility function, `toml::find_or`. -It is almost same as `toml::find`, but returns a default value if the value is -not found or has a different type, like `toml::get_or`. +Contrary, since `toml::table` is just an alias of `std::unordered_map`, +you need to pass a name to the function to show the name in the exception with `toml::table`. ```cpp -const auto data = toml::parse("example.toml"); -const auto num = toml::find_or(data.at("table"), "num", 42); +const toml::table data = /* ... */; + +// you need to pass the name of the table to show it in an error message +const auto num = toml::find(data, "num", "[data]"); ``` +```console +terminate called after throwing an instance of 'std::out_of_range' + what(): [error] key "num" not found in [data] +# table name is needed to show this part ^^^^^^ +``` + +By default (w/o template parameter), `toml::find` returns a `toml::value`. + +```cpp +const toml::value& subtable = toml::find(table, "subtable"); +``` + +__NOTE__: +A new feature, recursive toml::find was planned to be introduced, but it was +found that the change breaks a code that previously worked fine. So the change +was reverted. +The reason is that the overload resolution was ambiguous. To support this, +in the next major update, overloads of `toml::find` for `toml::table` possibly +be removed. + ## Checking value type You can check what type of value does `toml::value` contains by `is_*` function. From 1b19d5f1eb69a91a00ff02bb1f7a6f368d3d4e62 Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Sun, 16 Jun 2019 21:44:59 +0900 Subject: [PATCH 06/12] doc: update README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c6133e5..5072dc9 100644 --- a/README.md +++ b/README.md @@ -498,8 +498,8 @@ const toml::value& subtable = toml::find(table, "subtable"); __NOTE__: A new feature, recursive toml::find was planned to be introduced, but it was -found that the change breaks a code that previously worked fine. So the change -was reverted. +found that the change breaks a code that was previously compiled fine. So the +change was reverted. The reason is that the overload resolution was ambiguous. To support this, in the next major update, overloads of `toml::find` for `toml::table` possibly be removed. From 00d40140acb28c1740d9af4e441fda55f31f7492 Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Mon, 17 Jun 2019 12:59:29 +0900 Subject: [PATCH 07/12] doc: add an example of error message to README --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 5072dc9..6a00be8 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,24 @@ terminate called after throwing an instance of 'toml::syntax_error' | ~~~~~~~ table defined twice ``` +When toml11 encounters a malformed value, it tries to detect what type it is. +Then it shows hints to fix the format. An error message while reading one of +the malformed files in [the language agnostic test suite](https://github.com/BurntSushi/toml-test). +is shown below. + +```console + what(): [error] bad time: should be HH:MM:SS.subsec + --> ./datetime-malformed-no-secs.toml + 1 | no-secs = 1987-07-05T17:45Z + | ^------- HH:MM:SS.subsec + | +Hint: OK: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999 +Hint: NG: 1979-05-27T7:32:00, 1979-05-27 7:32 +``` + +You can find other examples in a job named `output_result` on +[CircleCI](https://circleci.com/gh/ToruNiina/toml11). + Since the error message generation is generally a difficult task, the current status is not ideal. If you encounter a weird error message, please let us know and contribute to improve the quality! From 0357d8fb576abb69f69ae6146bf8040e2cd1d301 Mon Sep 17 00:00:00 2001 From: KerstinKeller Date: Mon, 17 Jun 2019 10:04:39 +0200 Subject: [PATCH 08/12] Add newline to end of CMake files. --- CMakeLists.txt | 2 +- cmake/toml11Config.cmake.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index daf57fe..1a45686 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,4 +102,4 @@ install(EXPORT toml11Targets if (toml11_BUILD_TEST) add_subdirectory(tests) -endif () \ No newline at end of file +endif () diff --git a/cmake/toml11Config.cmake.in b/cmake/toml11Config.cmake.in index 80c76f7..edc73f6 100644 --- a/cmake/toml11Config.cmake.in +++ b/cmake/toml11Config.cmake.in @@ -1,2 +1,2 @@ @PACKAGE_INIT@ -include("@PACKAGE_toml11_install_cmake_dir@/toml11Targets.cmake") \ No newline at end of file +include("@PACKAGE_toml11_install_cmake_dir@/toml11Targets.cmake") From adcd75e0170fcea844f98e230e7045181852aeaa Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Tue, 18 Jun 2019 21:27:16 +0900 Subject: [PATCH 09/12] fix: correctly initialize offset --- toml/datetime.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/toml/datetime.hpp b/toml/datetime.hpp index f0ae405..9429128 100644 --- a/toml/datetime.hpp +++ b/toml/datetime.hpp @@ -450,13 +450,13 @@ struct offset_datetime : date(ld.date), time(ld.time), offset(get_local_offset()) {} explicit offset_datetime(const std::chrono::system_clock::time_point& tp) - : offset_datetime(local_datetime(tp)) + : offset_datetime(local_datetime(tp)), offset(get_local_offset()) {} explicit offset_datetime(const std::time_t& t) - : offset_datetime(local_datetime(t)) + : offset_datetime(local_datetime(t)), offset(get_local_offset()) {} explicit offset_datetime(const std::tm& t) - : offset_datetime(local_datetime(t)) + : offset_datetime(local_datetime(t)), offset(get_local_offset()) {} operator std::chrono::system_clock::time_point() const From 73ac43d70c2ca7bad30ed94dacb0cf12262a3a01 Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Tue, 18 Jun 2019 21:28:50 +0900 Subject: [PATCH 10/12] doc: add contributor --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5072dc9..5475c94 100644 --- a/README.md +++ b/README.md @@ -1115,6 +1115,8 @@ I appreciate the help of the contributors who introduced the great feature to th - Fixed Visual Studio 2019 warnings - @khoitd1997 - Fixed warnings while type conversion +- @KerstinKeller + - Added installation script to CMake ## Licensing terms From 53a185e7a9d371d0b086797f16a5bb8be3350f05 Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Tue, 18 Jun 2019 21:41:30 +0900 Subject: [PATCH 11/12] fix: revert misjudgement as a bug Probably I'm too tired. This reverts commit adcd75e0170fcea844f98e230e7045181852aeaa. --- toml/datetime.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/toml/datetime.hpp b/toml/datetime.hpp index 9429128..f0ae405 100644 --- a/toml/datetime.hpp +++ b/toml/datetime.hpp @@ -450,13 +450,13 @@ struct offset_datetime : date(ld.date), time(ld.time), offset(get_local_offset()) {} explicit offset_datetime(const std::chrono::system_clock::time_point& tp) - : offset_datetime(local_datetime(tp)), offset(get_local_offset()) + : offset_datetime(local_datetime(tp)) {} explicit offset_datetime(const std::time_t& t) - : offset_datetime(local_datetime(t)), offset(get_local_offset()) + : offset_datetime(local_datetime(t)) {} explicit offset_datetime(const std::tm& t) - : offset_datetime(local_datetime(t)), offset(get_local_offset()) + : offset_datetime(local_datetime(t)) {} operator std::chrono::system_clock::time_point() const From dee32e7d5ef79300fd2d8123e454e459976181ff Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Wed, 19 Jun 2019 12:56:21 +0900 Subject: [PATCH 12/12] style: make hint messages clearer --- README.md | 8 ++++---- toml/parser.hpp | 42 +++++++++++++++++++++--------------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 654f845..011ed0c 100644 --- a/README.md +++ b/README.md @@ -150,13 +150,13 @@ the malformed files in [the language agnostic test suite](https://github.com/Bur is shown below. ```console - what(): [error] bad time: should be HH:MM:SS.subsec +what(): [error] bad time: should be HH:MM:SS.subsec --> ./datetime-malformed-no-secs.toml 1 | no-secs = 1987-07-05T17:45Z | ^------- HH:MM:SS.subsec - | -Hint: OK: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999 -Hint: NG: 1979-05-27T7:32:00, 1979-05-27 7:32 + | +Hint: pass: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999 +Hint: fail: 1979-05-27T7:32:00, 1979-05-27 17:32 ``` You can find other examples in a job named `output_result` on diff --git a/toml/parser.hpp b/toml/parser.hpp index 89e8049..d8c974e 100644 --- a/toml/parser.hpp +++ b/toml/parser.hpp @@ -1431,7 +1431,7 @@ result guess_number_type(const location& l) { return err(format_underline("[error] bad offset: should be [+-]HH:MM or Z", {{std::addressof(loc), "[+-]HH:MM or Z"}}, - {"OK: +09:00, -05:30", "NG: +9:00, -5:30"})); + {"pass: +09:00, -05:30", "fail: +9:00, -5:30"})); } return ok(value_t::LocalDatetime); } @@ -1451,15 +1451,15 @@ result guess_number_type(const location& l) { return err(format_underline("[error] bad time: should be HH:MM:SS.subsec", {{std::addressof(loc), "HH:MM:SS.subsec"}}, - {"OK: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999", - "NG: 1979-05-27T7:32:00, 1979-05-27 7:32"})); + {"pass: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999", + "fail: 1979-05-27T7:32:00, 1979-05-27 17:32"})); } if('0' <= c && c <= '9') { return err(format_underline("[error] bad time: missing T", {{std::addressof(loc), "T or space required here"}}, - {"OK: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999", - "NG: 1979-05-27T7:32:00, 1979-05-27 7:32"})); + {"pass: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999", + "fail: 1979-05-27T7:32:00, 1979-05-27 7:32"})); } if(c == ' ' && std::next(loc.iter()) != loc.end() && ('0' <= *std::next(loc.iter()) && *std::next(loc.iter())<= '9')) @@ -1467,8 +1467,8 @@ result guess_number_type(const location& l) loc.advance(); return err(format_underline("[error] bad time: should be HH:MM:SS.subsec", {{std::addressof(loc), "HH:MM:SS.subsec"}}, - {"OK: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999", - "NG: 1979-05-27T7:32:00, 1979-05-27 7:32"})); + {"pass: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999", + "fail: 1979-05-27T7:32:00, 1979-05-27 7:32"})); } } return ok(value_t::LocalDate); @@ -1484,8 +1484,8 @@ result guess_number_type(const location& l) { return err(format_underline("[error] bad float: `_` should be surrounded by digits", {{std::addressof(loc), "here"}}, - {"OK: +1.0, -2e-2, 3.141_592_653_589, inf, nan", - "NG: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0"})); + {"pass: +1.0, -2e-2, 3.141_592_653_589, inf, nan", + "fail: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0"})); } return ok(value_t::Float); } @@ -1500,8 +1500,8 @@ result guess_number_type(const location& l) { return err(format_underline("[error] bad integer: `_` should be surrounded by digits", {{std::addressof(loc), "here"}}, - {"OK: -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755", - "NG: 1__000, 0123"})); + {"pass: -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755", + "fail: 1__000, 0123"})); } if('0' <= c && c <= '9') { @@ -1509,22 +1509,22 @@ result guess_number_type(const location& l) loc.retrace(); return err(format_underline("[error] bad integer: leading zero", {{std::addressof(loc), "here"}}, - {"OK: -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755", - "NG: 1__000, 0123"})); + {"pass: -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755", + "fail: 1__000, 0123"})); } if(c == ':' || c == '-') { return err(format_underline("[error] bad datetime: invalid format", {{std::addressof(loc), "here"}}, - {"OK: 1979-05-27T07:32:00-07:00, 1979-05-27 07:32:00.999999Z", - "NG: 1979-05-27T7:32:00-7:00, 1979-05-27 7:32-00:30"})); + {"pass: 1979-05-27T07:32:00-07:00, 1979-05-27 07:32:00.999999Z", + "fail: 1979-05-27T7:32:00-7:00, 1979-05-27 7:32-00:30"})); } if(c == '.' || c == 'e' || c == 'E') { return err(format_underline("[error] bad float: invalid format", {{std::addressof(loc), "here"}}, - {"OK: +1.0, -2e-2, 3.141_592_653_589, inf, nan", - "NG: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0"})); + {"pass: +1.0, -2e-2, 3.141_592_653_589, inf, nan", + "fail: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0"})); } } return ok(value_t::Integer); @@ -1533,15 +1533,15 @@ result guess_number_type(const location& l) { return err(format_underline("[error] bad float: invalid format", {{std::addressof(loc), "integer part required before this"}}, - {"OK: +1.0, -2e-2, 3.141_592_653_589, inf, nan", - "NG: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0"})); + {"pass: +1.0, -2e-2, 3.141_592_653_589, inf, nan", + "fail: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0"})); } if(loc.iter() != loc.end() && *loc.iter() == '_') { return err(format_underline("[error] bad number: `_` should be surrounded by digits", {{std::addressof(loc), "`_` is not surrounded by digits"}}, - {"OK: -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755", - "NG: 1__000, 0123"})); + {"pass: -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755", + "fail: 1__000, 0123"})); } return err(format_underline("[error] bad format: unknown value appeared", {{std::addressof(loc), "here"}}));