diff --git a/README.md b/README.md index ee982db..ddb93eb 100644 --- a/README.md +++ b/README.md @@ -451,36 +451,58 @@ 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 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"); +// 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. +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 ``` -You can use this with a `toml::value` that is expected to be a `toml::table`. -It automatically casts the value to 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"); -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"); +if(data.count("num") == 1) +{ + const auto num = toml::get(data.at("num")); + // more stuff ... +} ``` -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. +---- + +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 +// [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. ```console terminate called after throwing an instance of 'std::out_of_range' @@ -490,7 +512,45 @@ 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. +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). + +---- There is another utility function, `toml::find_or`. It is almost same as `toml::find`, but returns a default value if the value is @@ -516,20 +576,27 @@ if(v.is_integer()) The complete list of the functions is below. ```cpp -const toml::value v(/*...*/); -v.is_boolean(); -v.is_integer(); -v.is_float(); -v.is_string(); -v.is_offset_datetime(); -v.is_local_datetime(); -v.is_local_date(); -v.is_local_time(); -v.is_array(); -v.is_table(); -v.is_uninitialized(); +namespace toml { +class value { + // ... + bool is_boolean() const noexcept; + bool is_integer() const noexcept; + bool is_floating() const noexcept; + bool is_string() const noexcept; + bool is_offset_datetime() const noexcept; + bool is_local_datetime() const noexcept; + bool is_local_date() const noexcept; + bool is_local_time() const noexcept; + bool is_array() const noexcept; + bool is_table() const noexcept; + bool is_uninitialized() const noexcept; + // ... +}; +} // toml ``` +__NOTE__: `is_float` is marked as deprecated since v2.4.0 to make the function names consistent with snake case typenames. Please use `is_floating` instead. + Also, you can get `enum class` value from `toml::value`. ```cpp @@ -569,20 +636,33 @@ if(v.is_integer() && v.as_integer() == 42) The complete list of the functions is below. ```cpp -const toml::value v(/*...*/); -v.as_boolean(); -v.as_integer(); -v.as_float(); -v.as_string(); -v.as_offset_datetime(); -v.as_local_datetime(); -v.as_local_date(); -v.as_local_time(); -v.as_array(); -v.as_table(); -v.as_uninitialized(); +namespace toml { +class value { + // ... + const boolean& as_boolean() const& noexcept; + const integer& as_integer() const& noexcept; + const floating& as_floating() const& noexcept; + const string& as_string() const& noexcept; + const offset_datetime& as_offset_datetime() const& noexcept; + const local_datetime& as_local_datetime() const& noexcept; + const local_date& as_local_date() const& noexcept; + const local_time& as_local_time() const& noexcept; + const array& as_array() const& noexcept; + const table& as_table() const& noexcept; + // -------------------------------------------------------- + // non-const version + boolean& as_boolean() & noexcept; + // ditto... + // -------------------------------------------------------- + // rvalue version + boolean&& as_boolean() && noexcept; + // ditto... +}; +} // toml ``` +__NOTE__: `as_float` is marked as deprecated since v2.4.0 to make the function names consistent with snake case typenames. Please use `as_floating` instead. + ## Visiting a toml::value toml11 provides `toml::visit` to apply a function to `toml::value` in the diff --git a/tests/test_get_related_func.cpp b/tests/test_get_related_func.cpp index 30eb362..e6e448c 100644 --- a/tests/test_get_related_func.cpp +++ b/tests/test_get_related_func.cpp @@ -12,8 +12,9 @@ #include #include -BOOST_AUTO_TEST_CASE(test_find) +BOOST_AUTO_TEST_CASE(test_find_for_value) { + // value itself is not a table { toml::value v(true); bool thrown = false; @@ -27,22 +28,87 @@ BOOST_AUTO_TEST_CASE(test_find) } BOOST_CHECK(thrown); } - + // the value corresponding to the key is not the expected type { - 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{{"key", 42}}; + bool thrown = false; + try + { + toml::find(v, "key"); + } + catch(toml::type_error const& te) + { + thrown = true; + } + BOOST_CHECK(thrown); } { toml::value v = toml::table{{"num", 42}}; BOOST_CHECK_EQUAL(42, toml::find(v, "num")); - toml::find(v, "num") = 54; + + // 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")); } + + // 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/tests/test_value.cpp b/tests/test_value.cpp index 2cd3315..8b1afe9 100644 --- a/tests/test_value.cpp +++ b/tests/test_value.cpp @@ -810,7 +810,6 @@ BOOST_AUTO_TEST_CASE(test_value_table) BOOST_CHECK_EQUAL(v1.as_table().at("bar").as_integer(), 54); BOOST_CHECK_EQUAL(v1.as_table().at("baz").as_string().str, "quux"); - toml::value v3(v1); BOOST_CHECK(v3 == v1); diff --git a/toml/get.hpp b/toml/get.hpp index 0ca5435..86158ee 100644 --- a/toml/get.hpp +++ b/toml/get.hpp @@ -559,6 +559,55 @@ find(basic_value&& v, const key& ky) return ::toml::get(std::move(tab.at(ky))); } +// -------------------------------------------------------------------------- +// toml::find(toml::value, toml::key, Ts&& ... keys) + +template class M, template class V, + typename ... Ts> +const basic_value& +find(const basic_value& v, const ::toml::key& ky, Ts&& ... keys) +{ + return ::toml::find(::toml::find(v, ky), std::forward(keys)...); +} +template class M, template class V> +basic_value& +find(basic_value& v, const ::toml::key& ky, Ts&& ... keys) +{ + return ::toml::find(::toml::find(v, ky), std::forward(keys)...); +} +template class M, template class V> +basic_value&& +find(basic_value&& v, const ::toml::key& ky, Ts&& ... keys) +{ + return ::toml::find(::toml::find(std::move(v), ky), std::forward(keys)...); +} + +template class M, template class V, + typename ... Ts> +decltype(::toml::get(std::declval&>())) +find(const basic_value& v, const ::toml::key& ky, Ts&& ... keys) +{ + return ::toml::find(::toml::find(v, ky), std::forward(keys)...); +} +template class M, template class V> +decltype(::toml::get(std::declval&>())) +find(basic_value& v, const ::toml::key& ky, Ts&& ... keys) +{ + return ::toml::find(::toml::find(v, ky), std::forward(keys)...); +} +template class M, template class V> +decltype(::toml::get(std::declval&&>())) +find(basic_value&& v, const ::toml::key& ky, Ts&& ... keys) +{ + return ::toml::find(::toml::find(std::move(v), ky), std::forward(keys)...); +} + // ============================================================================ // get_or(value, fallback) diff --git a/toml/literal.hpp b/toml/literal.hpp index 5158495..bc4733b 100644 --- a/toml/literal.hpp +++ b/toml/literal.hpp @@ -11,7 +11,7 @@ inline namespace literals inline namespace toml_literals { -inline ::toml::value operator""_toml(const char* str, std::size_t len) +inline ::toml::value operator"" _toml(const char* str, std::size_t len) { ::toml::detail::location> loc(/* filename = */ std::string("TOML literal encoded in a C++ code"),