diff --git a/CHANGELOG.md b/CHANGELOG.md index f0c2d62..9f58364 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ template: #### Additions: - Added value type deduction to `emplace()` methods +- Added toml::path type to parse and manipulate a path to a node in a toml::table +- Added at_path overloads to accept toml::path

diff --git a/README.md b/README.md index c6fa6e3..2e1ac4a 100644 --- a/README.md +++ b/README.md @@ -257,6 +257,7 @@ UTF-8 decoding is performed using a state machine based on Bjoern Hoehrmann's '[ - **[@bjadamson](https://github.com/bjadamson)** - Reported some bugs and helped design a new feature - **[@bobfang1992](https://github.com/bobfang1992)** - Reported a bug and created a [wrapper in python](https://github.com/bobfang1992/pytomlpp) - **[@GiulioRomualdi](https://github.com/GiulioRomualdi)** - Added cmake+meson support +- **[@jonestristand](https://github.com/jonestristand)** - Designed and implemented the `toml::path`s feature - **[@levicki](https://github.com/levicki)** - Helped design some new features - **[@moorereason](https://github.com/moorereason)** - Reported a whole bunch of bugs - **[@mosra](https://github.com/mosra)** - Created the awesome [m.css] used to generate the API docs diff --git a/include/toml++/impl/at_path.h b/include/toml++/impl/at_path.h index 0449cac..1c2ee0f 100644 --- a/include/toml++/impl/at_path.h +++ b/include/toml++/impl/at_path.h @@ -57,6 +57,57 @@ TOML_NAMESPACE_START TOML_EXPORTED_FREE_FUNCTION node_view at_path(const node& root, std::string_view path) noexcept; + /// \brief Returns a view of the node matching a fully-qualified "TOML path". + /// + /// \detail \cpp + /// auto config = toml::parse(R"( + /// + /// [foo] + /// bar = [ 0, 1, 2, [ 3 ], { kek = 4 } ] + /// + /// )"sv); + /// + /// toml::path path1("foo.bar[2]"); + /// toml::path path2("foo.bar[4].kek"); + /// std::cout << toml::at_path(config, path1) << "\n"; + /// std::cout << toml::at_path(config, path1.parent_path()) << "\n"; + /// std::cout << toml::at_path(config, path2) << "\n"; + /// std::cout << toml::at_path(config, path2.parent_path()) << "\n"; + /// \ecpp + /// + /// \out + /// 2 + /// [ 0, 1, 2, [ 3 ], { kek = 4 } ] + /// 4 + /// { kek = 4 } + /// \eout + /// + /// + /// \note Keys in paths are interpreted literally, so whitespace (or lack thereof) matters: + /// \cpp + /// toml::at_path(config, toml::path("foo.bar")) // same as config["foo"]["bar"] + /// toml::at_path(config, toml::path("foo. bar")) // same as config["foo"][" bar"] + /// toml::at_path(config, toml::path("foo..bar")) // same as config["foo"][""]["bar"] + /// toml::at_path(config, toml::path(".foo.bar")) // same as config[""]["foo"]["bar"] + /// \ecpp + ///
+ /// Additionally, TOML allows '.' (period) characters to appear in keys if they are quoted strings. + /// This function makes no allowance for this, instead treating all period characters as sub-table delimiters. + /// + /// \param root The root node from which the path will be traversed. + /// \param path The "TOML path" to traverse. + TOML_NODISCARD + TOML_EXPORTED_FREE_FUNCTION + node_view at_path(node & root, const toml::path& path) noexcept; + + /// \brief Returns a const view of the node matching a fully-qualified "TOML path". + /// + /// \see #toml::at_path(node&, const toml::path& path) + TOML_NODISCARD + TOML_EXPORTED_FREE_FUNCTION + node_view at_path(const node& root, const toml::path& path) noexcept; + + #if TOML_ENABLE_WINDOWS_COMPAT /// \brief Returns a view of the node matching a fully-qualified "TOML path". diff --git a/include/toml++/impl/at_path.inl b/include/toml++/impl/at_path.inl index 253c3a5..8a18c26 100644 --- a/include/toml++/impl/at_path.inl +++ b/include/toml++/impl/at_path.inl @@ -70,7 +70,9 @@ TOML_ANON_NAMESPACE_START #if TOML_INT_CHARCONV auto fc_result = std::from_chars(index_str.data(), index_str.data() + index_str.length(), index); - if (fc_result.ec != std::errc{}) + + // If not able to parse, or entire index not parseable, then fail (otherwise would allow a[1bc] == a[1] + if (fc_result.ec != std::errc{} || fc_result.ptr != index_str.data() + index_str.length()) return nullptr; #else @@ -174,6 +176,48 @@ TOML_ANON_NAMESPACE_START return current; } + TOML_INTERNAL_LINKAGE + node* get_at_path(node & root, const toml::path& path) + { + if (root.is_value()) // values don't have child nodes + return nullptr; + + // special check if table has a key that is an empty string, and the path is empty, + // return the node at that empty key. + if (path.size() == 0 && root.is_table() && root.as_table()->contains("")) + return root.as_table()->get(""); + + node* current = &root; + + for (const auto& component: path) + { + auto type = component.type; + if (type == path_component_type::array_index && std::holds_alternative(component.value)) + { + const auto current_array = current->as(); + if (!current_array) + return nullptr; // not an array, using array index doesn't work + + current = current_array->get(std::get(component.value)); + } + else if (type == path_component_type::key && std::holds_alternative(component.value)) + { + const auto current_table = current->as(); + if (!current_table) + return nullptr; + + current = current_table->get(std::get(component.value)); + } + else + { + // Error: invalid component + return nullptr; + } + } + + return current; + } + #if TOML_ENABLE_WINDOWS_COMPAT TOML_INTERNAL_LINKAGE @@ -206,6 +250,19 @@ TOML_NAMESPACE_START return node_view{ TOML_ANON_NAMESPACE::get_at_path(const_cast(root), path) }; } + TOML_EXTERNAL_LINKAGE + node_view at_path(node & root, const toml::path& path) noexcept + { + return node_view{ TOML_ANON_NAMESPACE::get_at_path(root, path) }; + } + + TOML_EXTERNAL_LINKAGE + node_view at_path(const node& root, const toml::path& path) noexcept + { + return node_view{ TOML_ANON_NAMESPACE::get_at_path(const_cast(root), path) }; + } + + #if TOML_ENABLE_WINDOWS_COMPAT TOML_EXTERNAL_LINKAGE diff --git a/include/toml++/impl/forward_declarations.h b/include/toml++/impl/forward_declarations.h index d89ea40..f1d59f2 100644 --- a/include/toml++/impl/forward_declarations.h +++ b/include/toml++/impl/forward_declarations.h @@ -95,6 +95,8 @@ TOML_NAMESPACE_START template class value; + class path; + class toml_formatter; class json_formatter; class yaml_formatter; diff --git a/include/toml++/impl/node.h b/include/toml++/impl/node.h index 5634a13..3c9c1cf 100644 --- a/include/toml++/impl/node.h +++ b/include/toml++/impl/node.h @@ -1005,6 +1005,54 @@ TOML_NAMESPACE_START TOML_EXPORTED_MEMBER_FUNCTION node_view at_path(std::string_view path) const noexcept; + /// \brief Returns a view of the subnode matching a fully-qualified "TOML path". + /// + /// \detail \cpp + /// auto config = toml::parse(R"( + /// + /// [foo] + /// bar = [ 0, 1, 2, [ 3 ], { kek = 4 } ] + /// + /// )"sv); + /// + /// toml::path path1("foo.bar[2]"); + /// toml::path path2("foo.bar[4].kek"); + /// std::cout << config.at_path(path1) << "\n"; + /// std::cout << config.at_path(path1.parent_path()) << "\n"; + /// std::cout << config.at_path(path2) << "\n"; + /// std::cout << config.at_path(path2.parent_path()) << "\n"; + /// + /// \out + /// 2 + /// [ 0, 1, 2, [ 3 ], { kek = 4 } ] + /// 4 + /// { kek = 4 } + /// \eout + /// + /// + /// \note Keys in paths are interpreted literally, so whitespace (or lack thereof) matters: + /// \cpp + /// config.at_path(toml::path("foo.bar")) // same as node_view{ config }["foo"]["bar"] + /// config.at_path(toml::path("foo. bar")) // same as node_view{ config }["foo"][" bar"] + /// config.at_path(toml::path("foo..bar")) // same as node_view{ config }["foo"][""]["bar"] + /// config.at_path(toml::path(".foo.bar")) // same as node_view{ config }[""]["foo"]["bar"] + /// \ecpp + ///
+ /// Additionally, TOML allows '.' (period) characters to appear in keys if they are quoted strings. + /// This function makes no allowance for this, instead treating all period characters as sub-table delimiters. + /// + /// \param path The "TOML path" to traverse. + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + node_view at_path(const toml::path& path) noexcept; + + /// \brief Returns a const view of the subnode matching a fully-qualified "TOML path". + /// + /// \see #at_path(const toml::path& path) + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + node_view at_path(const toml::path& path) const noexcept; + #if TOML_ENABLE_WINDOWS_COMPAT /// \brief Returns a view of the subnode matching a fully-qualified "TOML path". diff --git a/include/toml++/impl/node.inl b/include/toml++/impl/node.inl index 1a37c46..3d5497b 100644 --- a/include/toml++/impl/node.inl +++ b/include/toml++/impl/node.inl @@ -71,6 +71,18 @@ TOML_NAMESPACE_START return toml::at_path(*this, path); } + TOML_EXTERNAL_LINKAGE + node_view node::at_path(const toml::path& path) noexcept + { + return toml::at_path(*this, path); + } + + TOML_EXTERNAL_LINKAGE + node_view node::at_path(const toml::path& path) const noexcept + { + return toml::at_path(*this, path); + } + #if TOML_ENABLE_WINDOWS_COMPAT TOML_EXTERNAL_LINKAGE diff --git a/include/toml++/impl/node_view.h b/include/toml++/impl/node_view.h index 4f3994d..8e07001 100644 --- a/include/toml++/impl/node_view.h +++ b/include/toml++/impl/node_view.h @@ -710,6 +710,18 @@ TOML_NAMESPACE_START return {}; } + /// \brief Returns a view of the selected subnode. + /// + /// \param path A "TOML path" to the desired subnode + /// + /// \returns A view of the selected node if this node represented a table and it contained a + /// value at the given key, or an empty view. + TOML_NODISCARD + node_view operator[](const toml::path& path) const noexcept + { + return node_ ? node_->at_path(path) : node_view{}; + } + /// \brief Returns a view of the subnode matching a fully-qualified "TOML path". /// /// \see #toml::node::at_path(std::string_view) @@ -719,6 +731,15 @@ TOML_NAMESPACE_START return node_ ? node_->at_path(path) : node_view{}; } + /// \brief Returns a view of the subnode matching a fully-qualified "TOML path". + /// + /// \see #toml::node::at_path(const toml::path&) + TOML_NODISCARD + node_view at_path(const toml::path& path) const noexcept + { + return node_ ? node_->at_path(path) : node_view{}; + } + #if TOML_ENABLE_WINDOWS_COMPAT /// \brief Returns a view of the selected subnode. diff --git a/include/toml++/impl/path.h b/include/toml++/impl/path.h new file mode 100644 index 0000000..dbf0aaf --- /dev/null +++ b/include/toml++/impl/path.h @@ -0,0 +1,491 @@ +//# This file is a part of toml++ and is subject to the the terms of the MIT license. +//# Copyright (c) Mark Gillard +//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text. +// SPDX-License-Identifier: MIT +#pragma once + +#include "forward_declarations.h" +#include "std_vector.h" +#include "std_string.h" +#include "std_variant.h" + +#include "header_start.h" + + +TOML_NAMESPACE_START +{ + + /// \brief Indicates type of path component, either a key, an index in an array, or invalid + enum class TOML_CLOSED_ENUM path_component_type : uint8_t + { + invalid = 0x0, + key = 0x1, + array_index = 0x2 + }; + + /// \brief Holds the value of a path component, either the name of the key in a string_view, or the index of an array as a size_t + using path_component_value = std::variant; + + /// \brief Represents a single component of a complete 'TOML-path': either a key or an array index + struct path_component + { + path_component_value value; + path_component_type type; + }; + + /// \brief A TOML path. + /// + /// \detail This type parses and represents a path to a TOML node. It validates + /// the syntax of the path but does not ensure that the path refers to + /// a valid node in any particular TOML document. If parsing fails, + /// the object will evaluate as 'falsy', and will be empty. + /// + /// \cpp + /// toml::path the_path("animals.cats[1]"); + /// + /// // can use with tbl.at_path or operator[] + /// std::cout << "second cat: " << tbl[the_path] << "\n"; + /// std::cout << "cats: " << tbl.at_path(the_path.parent_path()) << "\n"; + /// \ecpp + /// + /// \out + /// second cat: lion + /// cats: ['tiger', 'lion', 'puma'] + /// \eout + class TOML_EXPORTED_CLASS path + { + private: + /// \cond + + bool parse_error_ = false; + + std::vector components_; + + std::vector parse_(std::string_view, bool& parse_success); + + /// \endcond + + public: + /// \brief Default constructor. + TOML_NODISCARD_CTOR + path() noexcept = default; + + /// \brief Construct from string + TOML_NODISCARD_CTOR + TOML_EXPORTED_MEMBER_FUNCTION + explicit path(std::string_view); + + /// \brief Default destructor. + ~path() noexcept = default; + + /// \brief Copy constructor. + TOML_NODISCARD_CTOR + path(const path& other) = default; + + /// \brief Move constructor. + TOML_NODISCARD_CTOR + path(path&& other) noexcept = default; + + /// \brief Copy-assignment operator. + path& operator=(const path&) = default; + + /// \brief Move-assignment operator. + path& operator=(path&&) = default; + + /// \brief Append operator. + TOML_EXPORTED_MEMBER_FUNCTION + path& operator/=(path&&) noexcept; + + /// \brief Append operator. + TOML_EXPORTED_MEMBER_FUNCTION + path& operator/=(std::string_view); + + /// \brief Append operator. + TOML_EXPORTED_MEMBER_FUNCTION + path& operator+=(path&&) noexcept; + + /// \brief Append operator. + TOML_EXPORTED_MEMBER_FUNCTION + path& operator+=(std::string_view); + + TOML_NODISCARD + /// \brief Append a path to the current path + inline path operator+(const toml::path& rhs) const + { + toml::path result = { *this }; + result.append(rhs); + + return result; + } + + TOML_NODISCARD + /// \brief Append a path to the current path + inline path operator/(const toml::path& rhs) const + { + return *this + rhs; + } + + /// \brief Evaluate whether path parsing succeeded + TOML_NODISCARD + explicit inline operator bool() const noexcept + { + return !parse_error_; + }; + + /// \brief Implicitly cast to a std::string + TOML_NODISCARD + inline operator std::string() const { return string(); } + + /// \brief Evaluate whether two paths are the same + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + bool operator==(const path& compare) const noexcept; + + /// \brief Evaluate whether two paths are the same + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + bool operator==(std::string_view compare) const noexcept; + + /// \brief Evaluate whether two paths are the same + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + bool operator==(const char* compare) const noexcept; + + /// \brief Evaluate whether two paths are different + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + bool operator!=(const path& compare) const noexcept; + + /// \brief Evaluate whether two paths are different + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + bool operator!=(std::string_view compare) const noexcept; + + /// \brief Evaluate whether two paths are different + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + bool operator!=(const char* compare) const noexcept; + + /// \brief Fetch a path component by index for modification + TOML_NODISCARD + inline path_component& operator[](size_t index) noexcept + { + return components_[index]; + }; + + /// \brief Fetch a path component by index + TOML_NODISCARD + inline const path_component& operator[](size_t index) const noexcept + { + return components_[index]; + }; + + /// \brief Number of components in the path + TOML_NODISCARD + inline size_t size() const noexcept + { + return components_.size(); + }; + + /// \brief Whether (true) or not (false) the path is empty + TOML_NODISCARD + inline bool empty() const { return size() <= 0; } + + /// \brief Erases the contents of the path + TOML_EXPORTED_MEMBER_FUNCTION + void clear() noexcept; + + TOML_NODISCARD + /// \brief Iterator at the start of the vector of path components (see #path_component) + inline auto begin() const noexcept { return components_.begin(); } + + TOML_NODISCARD + /// \brief Iterator at the end of the vector of path components (see #path_component) + inline auto end() const noexcept { return components_.end(); } + + /// \brief Removes the number of terminal path components specified by n + TOML_EXPORTED_MEMBER_FUNCTION + path& truncate(size_t n); + + /// \brief Returns a toml::path object which has had n terminal path components removed + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + path truncated(size_t n) const; + + /// \brief Returns a toml::path object representing the path of the parent node + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + path parent_path() const; + + /// \brief Returns a toml::path object representing terminal n-parts of a TOML path + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + path leaf(size_t n = 1) const; + + /// \brief Returns a toml::path object that is a specified subpath of the current path, representing the + /// range of path components from [start, end). + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + path subpath(std::vector::const_iterator start, + std::vector::const_iterator end) const; + + /// \brief Returns a toml::path object that is a specified subpath of the current path, representing the + /// range of path components with indexes from [start, start + length]. + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + path subpath(size_t start, size_t length) const; + + /// \brief Appends elements to the end of the TOML path + TOML_EXPORTED_MEMBER_FUNCTION + path& append(const toml::path&); + + /// \brief Appends elements to the end of the TOML path + TOML_EXPORTED_MEMBER_FUNCTION + path& append(toml::path&&); + + /// \brief Appends elements to the end of the TOML path + TOML_EXPORTED_MEMBER_FUNCTION + path& append(std::string_view); + + /// \brief Prepends elements to the beginning of the TOML path + TOML_EXPORTED_MEMBER_FUNCTION + path& prepend(const toml::path&); + + /// \brief Prepends elements to the beginning of the TOML path + TOML_EXPORTED_MEMBER_FUNCTION + path& prepend(toml::path&&); + + /// \brief Prepends elements to the beginning of the TOML path + TOML_EXPORTED_MEMBER_FUNCTION + path& prepend(std::string_view); + + /// \brief Replaces the contents of the path object by a new path + TOML_EXPORTED_MEMBER_FUNCTION + path& assign(std::string_view); + + /// \brief Replaces the contents of the path object by a new path + TOML_EXPORTED_MEMBER_FUNCTION + path& assign(const path&); + + /// \brief Returns a string representing the path + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + std::string string() const; + +#if TOML_ENABLE_WINDOWS_COMPAT + + /// \brief Construct from wstring + TOML_NODISCARD_CTOR + TOML_EXPORTED_MEMBER_FUNCTION + explicit path(std::wstring_view); + + /// \brief Append operator + TOML_EXPORTED_MEMBER_FUNCTION + path& operator/=(std::wstring_view); + + /// \brief Append operator. + TOML_EXPORTED_MEMBER_FUNCTION + path& operator+=(std::wstring_view); + + /// \brief Implicitly cast to a std::wstring + TOML_NODISCARD + inline operator std::wstring() const + { + return wstring(); + } + + /// \brief Evaluate whether two paths are the same + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + bool operator==(std::wstring_view compare) const noexcept; + + /// \brief Evaluate whether two paths are the same + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + bool operator!=(std::wstring_view compare) const noexcept; + + /// \brief Appends elements to the end of the TOML path + TOML_EXPORTED_MEMBER_FUNCTION + path& append(std::wstring_view); + + /// \brief Prepends elements to the beginning of the TOML path + TOML_EXPORTED_MEMBER_FUNCTION + path& prepend(std::wstring_view); + + /// \brief Replaces the contents of the path object by a new path + TOML_EXPORTED_MEMBER_FUNCTION + path& assign(std::wstring_view); + + /// \brief Returns a wstring representing the path + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + std::wstring wstring() const; + +#endif // TOML_ENABLE_WINDOWS_COMPAT + + // === Hidden friend operators ======================================== + /// \brief Writes the #toml::path out to the output stream in a human-readbale format + friend std::ostream& operator<<(std::ostream& os, const toml::path& rhs); + + /// \brief Reads and parses a #toml::path from the input stream + friend std::istream& operator>>(std::istream& is, toml::path& rhs); + + /// \brief Appends a string-formatted toml path to a #toml::path object + TOML_NODISCARD + friend path operator+(const toml::path& lhs, std::string_view rhs) + { + return lhs + toml::path(rhs); + } + + /// \brief Appends a c-string-formatted toml path to a #toml::path object + TOML_NODISCARD + friend path operator+(const toml::path& lhs, const char* rhs) + { + return lhs + toml::path(rhs); + } + + /// \brief Appends a #toml::path object to a string-formatted toml path + TOML_NODISCARD + friend path operator+(std::string_view lhs, const toml::path& rhs) + { + toml::path result = { rhs }; + result.prepend(lhs); + + return result; + } + + /// \brief Appends a #toml::path object to a c-string-formatted toml path + TOML_NODISCARD + friend path operator+(const char* lhs, const toml::path& rhs) + { + toml::path result = { rhs }; + result.prepend(lhs); + + return result; + } + + /// \brief Appends a string-formatted toml path to a #toml::path object + TOML_NODISCARD + friend path operator/(const toml::path& lhs, std::string_view rhs) + { + return lhs + rhs; + } + + /// \brief Appends a c-string-formatted toml path to a #toml::path object + TOML_NODISCARD + friend path operator/(const toml::path& lhs, const char* rhs) + { + return lhs + toml::path(rhs); + } + + /// \brief Appends a #toml::path object to a string-formatted toml path + TOML_NODISCARD + friend path operator/(std::string_view lhs, const toml::path& rhs) + { + return lhs + rhs; + } + + /// \brief Appends a #toml::path object to a c-string-formatted toml path + TOML_NODISCARD + friend path operator/(const char* lhs, const toml::path& rhs) + { + return lhs + rhs; + } + +#if TOML_ENABLE_WINDOWS_COMPAT + + /// \brief Appends a wide-string-formatted toml path to a #toml::path object + TOML_NODISCARD + friend path operator+(const toml::path& lhs, std::wstring_view rhs) + { + return lhs + toml::path(rhs); + } + + /// \brief Appends a wchar_t-string-formatted toml path to a #toml::path object + TOML_NODISCARD + friend path operator+(const toml::path& lhs, const wchar_t* rhs) + { + return lhs + toml::path(rhs); + } + + /// \brief Appends a #toml::path object to a wide-string-formatted toml path + TOML_NODISCARD + friend path operator+(std::wstring_view lhs, const toml::path& rhs) + { + toml::path result = { rhs }; + result.prepend(lhs); + + return result; + } + + /// \brief Appends a #toml::path object to a wchar_t-string-formatted toml path + TOML_NODISCARD + friend path operator+(const wchar_t* lhs, const toml::path& rhs) + { + toml::path result = { rhs }; + result.prepend(lhs); + + return result; + } + + /// \brief Appends a wide-string-formatted toml path to a #toml::path object + TOML_NODISCARD + friend path operator/(const toml::path& lhs, std::wstring_view rhs) + { + return lhs + rhs; + } + + /// \brief Appends a wchar_t-string-formatted toml path to a #toml::path object + TOML_NODISCARD + friend path operator/(const toml::path& lhs, const wchar_t* rhs) + { + return lhs + toml::path(rhs); + } + + /// \brief Appends a #toml::path object to a wide-string-formatted toml path + TOML_NODISCARD + friend path operator/(std::wstring_view lhs, const toml::path& rhs) + { + return lhs + rhs; + } + + /// \brief Appends a #toml::path object to a wchar_t-string-formatted toml path + TOML_NODISCARD + friend path operator/(const wchar_t* lhs, const toml::path& rhs) + { + return lhs + rhs; + } + +#endif // TOML_ENABLE_WINDOWS_COMPAT + + }; + + inline namespace literals + { + /// \brief Parses a TOML path from a string literal. + /// + /// \detail \cpp + /// using namespace toml::literals; + /// + /// auto path = "main.settings.devices[2]"_tpath; + /// std::cout << path.parent_path() << "\n"; + /// \ecpp + /// + /// \out + /// main.settings.devices + /// \eout + /// + /// \param str The string data. + /// \param len The string length. + /// + /// \returns A #toml::path generated from the string literal. + TOML_NODISCARD + inline toml::path operator"" _tpath(const char* str, size_t len) + { + return toml::path(std::string_view{ str, len }); + } + } +} +TOML_NAMESPACE_END; + +#include "header_end.h" diff --git a/include/toml++/impl/path.inl b/include/toml++/impl/path.inl new file mode 100644 index 0000000..a2feeef --- /dev/null +++ b/include/toml++/impl/path.inl @@ -0,0 +1,541 @@ +//# This file is a part of toml++ and is subject to the the terms of the MIT license. +//# Copyright (c) Mark Gillard +//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text. +// SPDX-License-Identifier: MIT +#pragma once +//# {{ +#include "preprocessor.h" +#if !TOML_IMPLEMENTATION +#error This is an implementation-only header. +#endif +//# }} + +#include "path.h" + +TOML_DISABLE_WARNINGS; +#include +#include +#include +#if TOML_INT_CHARCONV +#include +#endif +TOML_ENABLE_WARNINGS; +#include "header_start.h" + +TOML_NAMESPACE_START +{ + TOML_EXTERNAL_LINKAGE + path::path(std::string_view path_str) + { + components_ = parse_(path_str, parse_error_); + } + + TOML_EXTERNAL_LINKAGE + path& path::operator/=(path&& rhs) noexcept + { + return append(std::move(rhs)); + } + + TOML_EXTERNAL_LINKAGE + path& path::operator/=(std::string_view source) + { + return append(source); + } + + TOML_EXTERNAL_LINKAGE + path& path::operator+=(path&& rhs) noexcept + { + return append(std::move(rhs)); + } + + TOML_EXTERNAL_LINKAGE + path& path::operator+=(std::string_view source) + { + return append(source); + } + + TOML_EXTERNAL_LINKAGE + bool path::operator==(const path& compare) const noexcept + { + if (components_.size() != compare.components_.size()) + return false; + + for (size_t i = 0; i < components_.size(); ++i) + { + if (components_[i].type != compare.components_[i].type + || components_[i].value != compare.components_[i].value) + return false; + } + + return true; + } + + TOML_EXTERNAL_LINKAGE + bool path::operator==(std::string_view compare) const noexcept + { + return string() == compare; + } + + TOML_EXTERNAL_LINKAGE + bool path::operator==(const char* compare) const noexcept + { + return string() == std::string_view(compare); + } + + TOML_EXTERNAL_LINKAGE + bool path::operator!=(const path& compare) const noexcept + { + return !(*this == compare); + } + + TOML_EXTERNAL_LINKAGE + bool path::operator!=(std::string_view compare) const noexcept + { + return !(*this == compare); + } + + TOML_EXTERNAL_LINKAGE + bool path::operator!=(const char* compare) const noexcept + { + return !(*this == std::string_view(compare)); + } + + TOML_EXTERNAL_LINKAGE + std::vector path::parse_(std::string_view path_str, bool& parse_error) + { + std::vector parsed_components; + parse_error = false; // success by default, set to true if fails to parse. + + if (path_str == "") // empty path + return parsed_components; + + auto str_start = path_str.data(); + size_t pos = 0; + const auto end = path_str.length(); + bool prev_was_array_indexer = false; + bool prev_was_dot = true; // invisible root 'dot' + + while (pos < end) + { + // start of an array indexer + if (path_str[pos] == '[') + { + // get array index substring + const auto index_start = pos + 1u; // first position after '[' + const auto index_end = path_str.find(']', index_start); // position of ']' + if (index_end == std::string_view::npos || index_end == index_start) // nothing in brackets, error + { + parsed_components.clear(); // empty object in case of error + parse_error = true; + return parsed_components; + } + auto index_str = std::string_view(&path_str[index_start], index_end - index_start); + + // trim whitespace from either side of the index + const auto first_non_ws = index_str.find_first_not_of(" \t"sv); + const auto last_non_ws = index_str.find_last_not_of(" \t"sv); + if (first_non_ws == std::string_view::npos) + { + parsed_components.clear(); // empty object in case of error + parse_error = true; + return parsed_components; + } + TOML_ASSERT_ASSUME(last_non_ws != std::string_view::npos); + index_str = index_str.substr(first_non_ws, (last_non_ws - first_non_ws) + 1u); + + // parse the actual array index to an integer type + size_t index; + if (index_str.length() == 1u && index_str[0] >= '0' && index_str[0] <= '9') + index = static_cast(index_str[0] - '0'); + else + { +#if TOML_INT_CHARCONV + + auto fc_result = std::from_chars(index_str.data(), index_str.data() + index_str.length(), index); + + // If not able to parse, or entire index not parseable, then fail (otherwise would allow a[1bc] == a[1] + if (fc_result.ec != std::errc{} || fc_result.ptr != index_str.data() + index_str.length()) + { + parsed_components.clear(); // empty object in case of error + parse_error_ = true; + return parsed_components; + } + +#else + + std::stringstream ss; + ss.imbue(std::locale::classic()); + ss.write(index_str.data(), static_cast(index_str.length())); + if (!(ss >> index)) + { + clear(); // empty object in case of error + parse_error_ = true; + return; + } + +#endif + } + + pos = index_end + 1u; + prev_was_dot = false; + prev_was_array_indexer = true; + + parsed_components.emplace_back(path_component{ { index }, path_component_type::array_index }); + } + + // start of a new table child + else if (path_str[pos] == '.') + { + // a dot immediately following another dot (or at the beginning of the string) is as if we'd asked + // for an empty child in between, e.g. + // + // foo..bar + // + // is equivalent to + // + // "foo".""."bar" + // + if (prev_was_dot) + parsed_components.emplace_back(path_component{ { ""s }, path_component_type::key }); + + pos++; + prev_was_dot = true; + prev_was_array_indexer = false; + } + + // some regular subkey + else + { + const auto subkey_start = pos; + const auto subkey_len = + impl::min(path_str.find_first_of(".["sv, subkey_start + 1u), path_str.length()) - subkey_start; + const auto subkey = path_str.substr(subkey_start, subkey_len); + + // a regular subkey segment immediately after an array indexer is OK if it was all whitespace, e.g.: + // + // "foo[0] .bar" + // ^^ skip this + // + // otherwise its an error (since it would have to be preceeded by a dot) + if (prev_was_array_indexer) + { + auto non_ws = subkey.find_first_not_of(" \t"); + if (non_ws == std::string_view::npos) + { + pos += subkey_len; + prev_was_dot = false; + prev_was_array_indexer = false; + continue; + } + else + { + parsed_components.clear(); // empty object in case of error + parse_error = true; + return parsed_components; + } + } + + pos += subkey_len; + prev_was_dot = false; + prev_was_array_indexer = false; + + parsed_components.emplace_back(path_component{ + std::string(std::string_view{ str_start + subkey_start, subkey_len }), + path_component_type::key + }); + } + } + + if (prev_was_dot) // Last character was a '.', which implies an empty string key at the end of the path + { + parsed_components.emplace_back(path_component{ ""s, path_component_type::key }); + } + + return parsed_components; + } + + TOML_EXTERNAL_LINKAGE + void path::clear() noexcept + { + this->components_.clear(); + } + + TOML_EXTERNAL_LINKAGE + path& path::truncate(size_t n) + { + n = n > components_.size() ? components_.size() : n; + + auto it_end = components_.end(); + components_.erase(it_end - static_cast(n), it_end); + + return *this; + } + + TOML_EXTERNAL_LINKAGE + path path::truncated(size_t n) const + { + path truncated_path {}; + + n = n > components_.size() ? components_.size() : n; + + // Copy all components except one + // Need at least two path components to have a parent, since if there is + // only one path component, the parent is the root/null path "" + truncated_path.components_.insert(truncated_path.components_.begin(), + components_.begin(), + components_.end() - static_cast(n)); + + return truncated_path; + } + + + TOML_EXTERNAL_LINKAGE + path path::parent_path() const + { + return truncated(1); + } + + TOML_EXTERNAL_LINKAGE + path path::leaf(size_t n) const + { + toml::path leaf_path {}; + + n = n > components_.size() ? components_.size() : n; + + if (n > 0) + { + leaf_path.components_.insert(leaf_path.components_.begin(), + components_.end() - static_cast(n), + components_.end()); + } + + return leaf_path; + } + + TOML_EXTERNAL_LINKAGE + path path::subpath(std::vector::const_iterator start, + std::vector::const_iterator end) const + { + toml::path subpath{}; + + if (start > end) + { + return subpath; + } + + subpath.components_.insert(subpath.components_.begin(), start, end); + + return subpath; + } + + TOML_EXTERNAL_LINKAGE + path path::subpath(size_t start, size_t length) const + { + return subpath(begin() + static_cast(start), begin() + static_cast(start + length)); + } + + TOML_EXTERNAL_LINKAGE + path& path::append(const toml::path& source) + { + parse_error_ = false; // This will end up being a valid path when appended (even if previously failed and now + // empty) + + // Copy path parts to this object + for (const auto& component : source.components_) + { + components_.push_back(component); + } + + return *this; + } + + TOML_EXTERNAL_LINKAGE + path& path::append(toml::path&& source) + { + parse_error_ = false; // This will end up being a valid path when appended (even if previously failed and now + // empty) + + // Copy path parts to this object + for (auto& component : source.components_) + { + components_.emplace_back(std::move(component)); + } + + return *this; + } + + TOML_EXTERNAL_LINKAGE + path& path::append(std::string_view source) + { + auto components_to_append = parse_(source, parse_error_); + for (auto& component : components_to_append) + { + components_.emplace_back(std::move(component)); + } + + return *this; + } + + TOML_EXTERNAL_LINKAGE + path& path::prepend(const toml::path& source) + { + parse_error_ = false; // This will end up being a valid path when appended (even if previously failed and now + // empty) + + components_.insert(components_.begin(), source.components_.begin(), source.components_.end()); + + return *this; + } + + TOML_EXTERNAL_LINKAGE + path& path::prepend(toml::path&& source) + { + parse_error_ = false; // This will end up being a valid path when appended (even if previously failed and now + // empty) + + components_.insert(components_.begin(), std::make_move_iterator(source.components_.begin()), std::make_move_iterator(source.components_.end())); + + return *this; + } + + TOML_EXTERNAL_LINKAGE + path& path::prepend(std::string_view source) + { + auto components_to_prepend = parse_(source, parse_error_); + + components_.insert(components_.begin(), components_to_prepend.begin(), components_to_prepend.end()); + + return *this; + } + + TOML_EXTERNAL_LINKAGE + path& path::assign(std::string_view source) + { + components_ = parse_(source, parse_error_); + return *this; + } + + TOML_EXTERNAL_LINKAGE + path& path::assign(const path& source) + { + if (source) + { + components_ = source.components_; + } + else // propagate error of source + { + clear(); + parse_error_ = true; + } + + return *this; + } + + TOML_EXTERNAL_LINKAGE + std::string path::string() const + { + std::stringstream ss; + bool atRoot = true; + + for (const auto& component : components_) + { + if (component.type == path_component_type::key) // key + { + ss << (atRoot ? "" : ".") << std::get(component.value); + } + else if (component.type == path_component_type::array_index) // array + { + ss << "[" << std::get(component.value) << "]"; + } + atRoot = false; + } + + return ss.str(); + } + + TOML_EXTERNAL_LINKAGE + std::ostream& operator<<(std::ostream& os, const toml::path& rhs) + { + os << rhs.string(); + return os; + } + + TOML_EXTERNAL_LINKAGE + std::istream& operator>>(std::istream& is, toml::path& rhs) + { + std::string s; + is >> s; + rhs.assign(s); + + return is; + } + +#if TOML_ENABLE_WINDOWS_COMPAT + + /// \brief Construct from wstring + TOML_EXTERNAL_LINKAGE + path::path(std::wstring_view path_str) : path(impl::narrow(path_str)) + { } + + /// \brief Append operator + TOML_EXTERNAL_LINKAGE + path& path::operator/=(std::wstring_view rhs) + { + return append(rhs); + } + + /// \brief Append operator. + TOML_EXTERNAL_LINKAGE + path& path::operator+=(std::wstring_view rhs) + { + return append(rhs); + } + + /// \brief Evaluate whether two paths are the same + TOML_EXTERNAL_LINKAGE + bool path::operator==(std::wstring_view compare) const noexcept + { + return *this == impl::narrow(compare); + } + + /// \brief Evaluate whether two paths are the same + TOML_EXTERNAL_LINKAGE + bool path::operator!=(std::wstring_view compare) const noexcept + { + return *this != impl::narrow(compare); + } + + /// \brief Appends elements to the end of the TOML path + TOML_EXTERNAL_LINKAGE + path& path::append(std::wstring_view source) + { + return append(impl::narrow(source)); + } + + /// \brief Prepends elements to the beginning of the TOML path + TOML_EXTERNAL_LINKAGE + path& path::prepend(std::wstring_view source) + { + return prepend(impl::narrow(source)); + } + + /// \brief Replaces the contents of the path object by a new path + TOML_EXTERNAL_LINKAGE + path& path::assign(std::wstring_view source) + { + return assign(impl::narrow(source)); + } + + /// \brief Returns a wstring representing the path + TOML_EXTERNAL_LINKAGE + std::wstring path::wstring() const + { + return impl::widen(string()); + } + +#endif // TOML_ENABLE_WINDOWS_COMPAT + +} +TOML_NAMESPACE_END; + +#include "header_end.h" diff --git a/include/toml++/impl/std_variant.h b/include/toml++/impl/std_variant.h new file mode 100644 index 0000000..bffdab5 --- /dev/null +++ b/include/toml++/impl/std_variant.h @@ -0,0 +1,10 @@ +//# This file is a part of toml++ and is subject to the the terms of the MIT license. +//# Copyright (c) Mark Gillard +//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text. +// SPDX-License-Identifier: MIT +#pragma once + +#include "preprocessor.h" +TOML_DISABLE_WARNINGS; +#include +TOML_ENABLE_WARNINGS; diff --git a/include/toml++/impl/table.h b/include/toml++/impl/table.h index d6edca9..5806278 100644 --- a/include/toml++/impl/table.h +++ b/include/toml++/impl/table.h @@ -4,6 +4,7 @@ // SPDX-License-Identifier: MIT #pragma once +#include "forward_declarations.h" #include "std_map.h" #include "std_initializer_list.h" #include "array.h" @@ -1858,6 +1859,40 @@ TOML_NAMESPACE_START return node_view{ get(key) }; } + /// \brief Gets a node_view for the provided path. + /// + /// \param path The "TOML path" to the desired key. + /// + /// \returns A view of the value at the given path if one existed, or an empty node view. + /// + /// \remarks std::map::operator[]'s behaviour of default-constructing a value at a key if it + /// didn't exist is a crazy bug factory so I've deliberately chosen not to emulate it. + /// This is not an error. + /// + /// \see toml::node_view + TOML_NODISCARD + node_view operator[](const toml::path& path) noexcept + { + return node_view{ at_path(path) }; + } + + /// \brief Gets a node_view for the provided path (const overload). + /// + /// \param path The "TOML path" to the desired key. + /// + /// \returns A view of the value at the given path if one existed, or an empty node view. + /// + /// \remarks std::map::operator[]'s behaviour of default-constructing a value at a key if it + /// didn't exist is a crazy bug factory so I've deliberately chosen not to emulate it. + /// This is not an error. + /// + /// \see toml::node_view + TOML_NODISCARD + node_view operator[](const toml::path& path) const noexcept + { + return node_view{ at_path(path) }; + } + #if TOML_ENABLE_WINDOWS_COMPAT /// \brief Gets a node_view for the selected value. diff --git a/include/toml++/toml.h b/include/toml++/toml.h index 2dabdb7..b7e7d81 100644 --- a/include/toml++/toml.h +++ b/include/toml++/toml.h @@ -41,6 +41,7 @@ TOML_DISABLE_SUGGEST_ATTR_WARNINGS; #include "impl/source_region.h" #include "impl/date_time.h" #include "impl/at_path.h" +#include "impl/path.h" #include "impl/node.h" #include "impl/node_view.h" #include "impl/value.h" @@ -64,6 +65,7 @@ TOML_DISABLE_SUGGEST_ATTR_WARNINGS; #include "impl/print_to_stream.inl" #include "impl/node.inl" #include "impl/at_path.inl" +#include "impl/path.inl" #include "impl/array.inl" #include "impl/table.inl" #include "impl/unicode.inl" diff --git a/tests/path.cpp b/tests/path.cpp new file mode 100644 index 0000000..dae26d7 --- /dev/null +++ b/tests/path.cpp @@ -0,0 +1,594 @@ +// This file is a part of toml++ and is subject to the the terms of the MIT license. +// Copyright (c) Mark Gillard +// See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text. +// SPDX-License-Identifier: MIT + +#include "tests.h" +TOML_DISABLE_SPAM_WARNINGS; + +TEST_CASE("path - parsing") +{ + + SECTION("parsing") + { + CHECK(toml::path("").string() == ""); + CHECK(toml::path("[1]").string() == "[1]"); + CHECK(toml::path("[1][2]").string() == "[1][2]"); + CHECK(toml::path(" [1][2]").string() == " [1][2]"); + CHECK(toml::path("a. .b").string() == "a. .b"); + CHECK(toml::path("test[23]").string() == "test[23]"); + CHECK(toml::path("[ 120 ]").string() == "[120]"); + CHECK(toml::path("test.value").string() == "test.value"); + CHECK(toml::path("test[0].value").string() == "test[0].value"); + CHECK(toml::path("test[1][2]\t .value").string() == "test[1][2].value"); + CHECK(toml::path("test[1]\t[2].value").string() == "test[1][2].value"); + CHECK(toml::path(".test[1][2]\t ..value").string() == ".test[1][2]..value"); + +#if TOML_ENABLE_WINDOWS_COMPAT + + CHECK(toml::path(L"").string() == ""); + CHECK(toml::path(L"[1]").string() == "[1]"); + CHECK(toml::path(L"[1][2]").string() == "[1][2]"); + CHECK(toml::path(L" [1][2]").string() == " [1][2]"); + CHECK(toml::path(L"a. .b").string() == "a. .b"); + CHECK(toml::path(L"test[23]").string() == "test[23]"); + CHECK(toml::path(L"[ 120 ]").string() == "[120]"); + CHECK(toml::path(L"test.value").string() == "test.value"); + CHECK(toml::path(L"test[0].value").string() == "test[0].value"); + CHECK(toml::path(L"test[1][2]\t .value").string() == "test[1][2].value"); + CHECK(toml::path(L"test[1]\t[2].value").string() == "test[1][2].value"); + CHECK(toml::path(L".test[1][2]\t ..value").string() == ".test[1][2]..value"); + +#endif // TOML_ENABLE_WINDOWS_COMPAT + + } + + SECTION("parsing - errors") + { + CHECK(!toml::path("test[][2].value")); + CHECK(!toml::path("test[ ")); + CHECK(!toml::path("test[1]a.b")); + CHECK(!toml::path("test[1] a.b")); + CHECK(!toml::path("test[1a]")); + CHECK(!toml::path("test[a1]")); + CHECK(!toml::path("test[1!]")); + CHECK(!toml::path("test[!1]")); + CHECK(!toml::path("test[1 2]")); + CHECK(!toml::path("test[1.2]")); + CHECK(!toml::path("test[0.2]")); + +#if TOML_ENABLE_WINDOWS_COMPAT + + CHECK(!toml::path(L"test[][2].value")); + CHECK(!toml::path(L"test[ ")); + CHECK(!toml::path(L"test[1]a.b")); + CHECK(!toml::path(L"test[1] a.b")); + CHECK(!toml::path(L"test[1a]")); + CHECK(!toml::path(L"test[a1]")); + CHECK(!toml::path(L"test[1!]")); + CHECK(!toml::path(L"test[!1]")); + CHECK(!toml::path(L"test[1 2]")); + CHECK(!toml::path(L"test[1.2]")); + CHECK(!toml::path(L"test[0.2]")); + +#endif // TOML_ENABLE_WINDOWS_COMPAT + + } + + SECTION("parsing from literal") + { + auto p0 = "a.b.c[1][12]"_tpath; + CHECK(p0); + CHECK(p0.string() == "a.b.c[1][12]"); + + CHECK("ab.cd[1]"_tpath == toml::path("ab.cd[1]")); + + CHECK(("an.invalid.path[a1]"_tpath).string() == ""); + } + +} + +TEST_CASE("path - manipulating") +{ + SECTION("parent_node and truncation") + { + toml::path p0(""); + CHECK(p0.parent_path().string() == ""); + + toml::path p1("start.middle.end"); + CHECK(p1.parent_path().string() == "start.middle"); + CHECK(p1.parent_path().parent_path().string() == "start"); + CHECK(p1.parent_path().parent_path().parent_path().string() == ""); + CHECK(p1.parent_path().parent_path().parent_path().parent_path().string() == ""); + + toml::path p2("[1][2][3]"); + CHECK(p2.parent_path().string() == "[1][2]"); + CHECK(p2.parent_path().parent_path().string() == "[1]"); + CHECK(p2.parent_path().parent_path().parent_path().string() == ""); + + toml::path p3(".test"); + CHECK(p3.parent_path().string() == ""); + + toml::path p4("test.."); + CHECK(p4.parent_path().string() == "test."); + CHECK(p4.parent_path().parent_path().string() == "test"); + CHECK(p4.parent_path().parent_path().parent_path().string() == ""); + + toml::path p5("test.key[12].subkey"); + CHECK(p5.parent_path().string() == "test.key[12]"); + CHECK(p5.parent_path().parent_path().string() == "test.key"); + CHECK(p5.parent_path().parent_path().parent_path().string() == "test"); + CHECK(p5.parent_path().parent_path().parent_path().parent_path().string() == ""); + + toml::path p6("test.key1.key2.key3.key4"); + CHECK(p6.truncated(0).string() == "test.key1.key2.key3.key4"); + CHECK(p6.truncated(1).string() == "test.key1.key2.key3"); + CHECK(p6.truncated(4).string() == "test"); + CHECK(p6.truncated(5).string() == ""); + CHECK(p6.truncated(20).string() == ""); + CHECK(p6.string() == "test.key1.key2.key3.key4"); + + p6.truncate(0); + CHECK(p6.string() == "test.key1.key2.key3.key4"); + p6.truncate(2); + CHECK(p6.string() == "test.key1.key2"); + p6.truncate(3); + CHECK(p6.string() == ""); + + } + + SECTION("subpath") + { + toml::path p0("a.simple[1].path[2].object"); + + CHECK(p0.subpath(p0.begin() + 1, p0.begin() + 4).string() == "simple[1].path"); + CHECK(p0.subpath(p0.begin() + 1, p0.end() - 1).string() == "simple[1].path[2]"); + CHECK(p0.subpath(p0.begin(), p0.begin()).string() == ""); + CHECK(p0.subpath(p0.begin(), p0.end() - 5).string() == "a"); + CHECK(p0.subpath(p0.begin() + 2, p0.end() - 1).string() == "[1].path[2]"); + + CHECK(p0.subpath(p0.begin() + 5, p0.end() - 5).string() == ""); + CHECK(p0.subpath(p0.end(), p0.begin()) == ""); + + CHECK(p0.subpath(1, 4).string() == "simple[1].path[2]"); + CHECK(p0.subpath(0, 0).string() == ""); + CHECK(p0.subpath(2, 0).string() == ""); + CHECK(p0.subpath(2, 1).string() == "[1]"); + } + + SECTION("leaf") + { + toml::path p0("one.two.three.four.five"); + CHECK(p0.leaf(0).string() == ""); + CHECK(p0.leaf().string() == "five"); + CHECK(p0.leaf(3).string() == "three.four.five"); + CHECK(p0.leaf(5).string() == "one.two.three.four.five"); + CHECK(p0.leaf(10).string() == "one.two.three.four.five"); + + toml::path p1("[10][2][30][4][50]"); + CHECK(p1.leaf(0).string() == ""); + CHECK(p1.leaf().string() == "[50]"); + CHECK(p1.leaf(3).string() == "[30][4][50]"); + CHECK(p1.leaf(5).string() == "[10][2][30][4][50]"); + CHECK(p1.leaf(10).string() == "[10][2][30][4][50]"); + + toml::path p2("one[1].two.three[3]"); + CHECK(p2.leaf(0).string() == ""); + CHECK(p2.leaf().string() == "[3]"); + CHECK(p2.leaf(3).string() == "two.three[3]"); + CHECK(p2.leaf(4).string() == "[1].two.three[3]"); + CHECK(p2.leaf(10).string() == "one[1].two.three[3]"); + } + + SECTION("append - string") + { + toml::path p0("start"); + CHECK(p0.append("middle.end").string() == "start.middle.end"); + CHECK(p0.append("[12]").string() == "start.middle.end[12]"); + + toml::path p1(""); + CHECK(p1.append("[1].key").string() == "[1].key"); + +#if TOML_ENABLE_WINDOWS_COMPAT + + toml::path p2("start"); + CHECK(p2.append(L"middle.end").string() == "start.middle.end"); + CHECK(p2.append(L"[12]").string() == "start.middle.end[12]"); + + toml::path p3(""); + CHECK(p3.append(L"[1].key").string() == "[1].key"); + +#endif // TOML_ENABLE_WINDOWS_COMPAT + + } + + SECTION("append - toml::path copy") + { + toml::path p0("start"); + toml::path appendee1("middle.end"); + toml::path appendee2("[12]"); + CHECK(p0.append(appendee1).string() == "start.middle.end"); + CHECK(p0.append(appendee2).string() == "start.middle.end[12]"); + + // Ensure copies and not moves + CHECK(appendee1.string() == "middle.end"); + CHECK(appendee2.string() == "[12]"); + + toml::path p1(""); + toml::path appendee3("[1].key"); + CHECK(p1.append(appendee3).string() == "[1].key"); + + // Ensure copies and not moves + CHECK(appendee3.string() == "[1].key"); + } + + SECTION("append - toml::path move") + { + toml::path p0("start"); + CHECK(p0.append(toml::path("middle.end")).string() == "start.middle.end"); + CHECK(p0.append(toml::path("[12]")).string() == "start.middle.end[12]"); + + toml::path p1(""); + CHECK(p1.append(toml::path("[1].key")).string() == "[1].key"); + } + + SECTION("prepend - string") + { + toml::path p0("start"); + CHECK(p0.prepend("middle.end").string() == "middle.end.start"); + CHECK(p0.prepend("[12]").string() == "[12].middle.end.start"); + + toml::path p1(""); + CHECK(p1.prepend("[1].key").string() == "[1].key"); + +#if TOML_ENABLE_WINDOWS_COMPAT + + toml::path p2("start"); + CHECK(p2.prepend(L"middle.end").string() == "middle.end.start"); + CHECK(p2.prepend(L"[12]").string() == "[12].middle.end.start"); + + toml::path p3(""); + CHECK(p3.prepend(L"[1].key").string() == "[1].key"); + +#endif // TOML_ENABLE_WINDOWS_COMPAT + + } + + SECTION("prepend - toml::path copy") + { + toml::path p0("start"); + toml::path prependee1("middle.end"); + toml::path prependee2("[12]"); + CHECK(p0.prepend(prependee1).string() == "middle.end.start"); + CHECK(p0.prepend(prependee2).string() == "[12].middle.end.start"); + + // Ensure copies and not moves + CHECK(prependee1.string() == "middle.end"); + CHECK(prependee2.string() == "[12]"); + + toml::path p1(""); + toml::path prependee3("[1].key"); + CHECK(p1.prepend(prependee3).string() == "[1].key"); + + // Ensure copies and not moves + CHECK(prependee3.string() == "[1].key"); + } + + SECTION("prepend - toml::path move") + { + toml::path p0("start"); + CHECK(p0.prepend(toml::path("middle.end")).string() == "middle.end.start"); + CHECK(p0.prepend(toml::path("[12]")).string() == "[12].middle.end.start"); + + toml::path p1(""); + CHECK(p1.prepend(toml::path("[1].key")).string() == "[1].key"); + } + + SECTION("alter components") + { + toml::path p0("start.mid[1][2].end"); + + p0[3].value = std::size_t{ 13 }; + CHECK(p0.string() == "start.mid[1][13].end"); + + p0[3].type = path_component_type::key; + p0[3].value = "newkey"; + CHECK(p0.string() == "start.mid[1].newkey.end"); + + p0[0].value = std::size_t{ 2 }; + p0[0].type = path_component_type::array_index; + CHECK(p0.string() == "[2].mid[1].newkey.end"); + } + + SECTION("assign") + { + toml::path p0("start.mid[1][2].end"); + p0.assign("test.key[1]"); + CHECK(p0.string() == "test.key[1]"); + p0.assign(""); + CHECK(p0.string() == ""); + + toml::path p1("a.test.path[1]"); + p1.assign("invalid[abc]"); + CHECK(!p1); + CHECK(p1.string() == ""); + + toml::path p2("another[1].test.path"); + p2.assign(toml::path("test")); + CHECK(p2.string() == "test"); + p2.assign(toml::path("")); + CHECK(p2.string() == ""); + + toml::path p3("final.test[1]"); + p3.assign(toml::path("invalid[abc")); + CHECK(!p3); + CHECK(p3.string() == ""); + +#if TOML_ENABLE_WINDOWS_COMPAT + + toml::path p4("start.mid[1][2].end"); + p4.assign(L"test.key[1]"); + CHECK(p4.string() == "test.key[1]"); + p4.assign(""); + CHECK(p4.string() == ""); + + toml::path p5("a.test.path[1]"); + p5.assign("invalid[abc]"); + CHECK(!p5); + CHECK(p5.string() == ""); + +#endif // TOML_ENABLE_WINDOWS_COMPAT + + } + +} + +TEST_CASE("path - operators") +{ + SECTION("object equality") + { + CHECK(toml::path("a.b.c") == toml::path("a.b.c")); + CHECK(toml::path("[1].a") == toml::path("[1].a")); + + CHECK(toml::path("a.b.c") != toml::path("a.b")); + CHECK(toml::path("[1].b") != toml::path("[1].b.c")); + } + + SECTION("string equality") + { + CHECK(toml::path("a.b.c") == "a.b.c"); + CHECK(toml::path("[1].a") == "[1].a"); + + CHECK(toml::path("a.b.c") != "a.b"); + CHECK(toml::path("[1].b") != "[1].b.c"); + +#if TOML_ENABLE_WINDOWS_COMPAT + + CHECK(toml::path("a.b.c") == L"a.b.c"); + CHECK(toml::path("[1].a") == L"[1].a"); + + CHECK(toml::path("a.b.c") != L"a.b"); + CHECK(toml::path("[1].b") != L"[1].b.c"); + +#endif // TOML_ENABLE_WINDOWS_COMPAT + + } + + SECTION("arithmetic") + { + CHECK(toml::path("a.b.c") / "a[1]" == "a.b.c.a[1]"); + CHECK((toml::path("a.b.c") / "a[1]") == "a.b.c.a[1]"); + + CHECK(toml::path("a.b.c") + toml::path("a[1]") == "a.b.c.a[1]"); + CHECK(toml::path("a.b.c") / toml::path("a[1]") == "a.b.c.a[1]"); + + toml::path p1("a.b"); + toml::path p2("c[1]"); + CHECK((p1 + p2) == "a.b.c[1]"); + CHECK(p1 / p2 == "a.b.c[1]"); + + CHECK(p1 + "c[1]" == "a.b.c[1]"); + CHECK(p1 / "c[1]" == "a.b.c[1]"); + + CHECK("a.b" + p2 == "a.b.c[1]"); + CHECK("a.b" / p2 == "a.b.c[1]"); + +#if TOML_ENABLE_WINDOWS_COMPAT + + CHECK(toml::path("a.b.c") / L"a[1]" == "a.b.c.a[1]"); + CHECK((toml::path("a.b.c") / L"a[1]") == "a.b.c.a[1]"); + + CHECK(p1 + L"c[1]" == "a.b.c[1]"); + CHECK(p1 / L"c[1]" == "a.b.c[1]"); + + CHECK(L"a.b" + p2 == "a.b.c[1]"); + CHECK(L"a.b" / p2 == "a.b.c[1]"); + +#endif // TOML_ENABLE_WINDOWS_COMPAT + } +} + +TEST_CASE("path - misc") +{ + + CHECK(toml::path("a.b.c").string() == "a.b.c"); + + CHECK(!toml::path("a").empty()); + CHECK(toml::path("").empty()); + + CHECK(static_cast(toml::path("a.b[1]")) == "a.b[1]"); + CHECK(static_cast(toml::path("a.b[1]"))); + CHECK(!static_cast(toml::path("a.b[a b]"))); + +#if TOML_ENABLE_WINDOWS_COMPAT + + CHECK(static_cast(toml::path("a.b[1]")) == L"a.b[1]"); + +#endif + +} + +TEST_CASE("path - accessing") +{ + + // clang-format off + + const auto tbl = table + { + { ""sv, 0 }, // blank key + { "a"sv, 1 }, + { + "b"sv, + array + { + 2, + array{ 3 }, + table { { "c", 4 } } + }, + }, + { "d", table{ {"e", 5, }, {""sv, -1 } } } + }; + + // clang-format on + + /* + + # equivalent to the following TOML: + + "" = 0 + a = 1 + b = [ + 2, + [ 3 ], + { "c" = 4 } + ] + d = { "e" = 5, "" = -1 } + + */ + + SECTION("table") + { + // this section uses the free function version of at_path + + CHECK(tbl[""]); + CHECK(tbl[""] == at_path(tbl, toml::path(""))); + + CHECK(tbl["a"]); + CHECK(tbl["a"] == at_path(tbl, toml::path("a"))); + CHECK(tbl["a"] != at_path(tbl, toml::path(".a"))); // equivalent to ""."a" + CHECK(!at_path(tbl, toml::path(".a"))); + + CHECK(tbl["b"]); + CHECK(tbl["b"] == at_path(tbl, toml::path("b"))); + + CHECK(tbl["b"][0]); + CHECK(tbl["b"][0] == at_path(tbl, toml::path("b[0]"))); + CHECK(tbl["b"][0] == at_path(tbl, toml::path("b[0] "))); + CHECK(tbl["b"][0] == at_path(tbl, toml::path("b[ 0\t]"))); // whitespace is allowed inside array indexer + + CHECK(tbl["b"][1]); + CHECK(tbl["b"][1] != tbl["b"][0]); + CHECK(tbl["b"][1] == at_path(tbl, toml::path("b[1]"))); + + CHECK(tbl["b"][1][0]); + CHECK(tbl["b"][1][0] == at_path(tbl, toml::path("b[1][0]"))); + CHECK(tbl["b"][1][0] == at_path(tbl, toml::path("b[1] \t [0]"))); // whitespace is allowed after array + // indexers + + CHECK(tbl["b"][2]["c"]); + CHECK(tbl["b"][2]["c"] == at_path(tbl, toml::path("b[2].c"))); + CHECK(tbl["b"][2]["c"] == at_path(tbl, toml::path("b[2] \t.c"))); // whitespace is allowed after array indexers + + CHECK(tbl["d"]); + CHECK(tbl["d"] == at_path(tbl, toml::path("d"))); + + CHECK(tbl["d"]["e"]); + CHECK(tbl["d"]["e"] == at_path(tbl, toml::path("d.e"))); + CHECK(tbl["d"]["e"] != at_path(tbl, toml::path("d. e"))); // equivalent to "d"." e" + CHECK(!at_path(tbl, toml::path("d. e"))); + + CHECK(tbl["d"][""]); + CHECK(tbl["d"][""] == at_path(tbl, toml::path("d."))); + } + + SECTION("array") + { + // this section uses the node_view member function version of at_path + + auto arr = tbl["b"]; + + CHECK(tbl["b"][0]); + CHECK(tbl["b"][0] == arr.at_path(toml::path("[0]"))); + CHECK(tbl["b"][0] == arr.at_path(toml::path("[0] "))); + CHECK(tbl["b"][0] == arr.at_path(toml::path("[ 0\t]"))); // whitespace is allowed inside array indexer + + CHECK(tbl["b"][1]); + CHECK(tbl["b"][1].node() != arr[0].node()); + CHECK(tbl["b"][1] == arr.at_path(toml::path("[1]"))); + + CHECK(tbl["b"][1][0]); + CHECK(tbl["b"][1][0] == arr.at_path(toml::path("[1][0]"))); + CHECK(tbl["b"][1][0] == arr.at_path(toml::path("[1] \t [0]"))); // whitespace is allowed after array + // indexers + + CHECK(tbl["b"][2]["c"]); + CHECK(tbl["b"][2]["c"] == arr.at_path(toml::path("[2].c"))); + CHECK(tbl["b"][2]["c"] == arr.at_path(toml::path("[2] \t.c"))); // whitespace is allowed after array indexers + } + + SECTION("indexing operator") + { + // this section uses the operator[] of table and node_view + CHECK(tbl[""]); + CHECK(tbl[""] == tbl[toml::path("")]); + + CHECK(tbl["a"]); + CHECK(tbl["a"] == tbl[toml::path("a")]); + CHECK(tbl["a"] != tbl[toml::path(".a")]); // equivalent to ""."a" + CHECK(!tbl[toml::path(".a")]); + + CHECK(tbl["b"]); + CHECK(tbl["b"] == tbl[toml::path("b")]); + + CHECK(tbl["b"][0]); + CHECK(tbl["b"][0] == tbl[toml::path("b[0]")]); + CHECK(tbl["b"][0] == tbl[toml::path("b[0] ")]); + CHECK(tbl["b"][0] == tbl[toml::path("b[ 0\t]")]); // whitespace is allowed inside array indexer + + CHECK(tbl["b"][1]); + CHECK(tbl["b"][1] != tbl[toml::path("b")][0]); + CHECK(tbl["b"][1] == tbl[toml::path("b[1]")]); + + CHECK(tbl["b"][1][0]); + CHECK(tbl["b"][1][0] == tbl[toml::path("b[1]")][0]); + CHECK(tbl["b"][1][0] == tbl[toml::path("b[1] \t [0]")]); // whitespace is allowed after array + // indexers + + CHECK(tbl["b"][2]["c"]); + CHECK(tbl["b"][2]["c"] == tbl[toml::path("b")][toml::path("[2].c")]); + CHECK(tbl["b"][2]["c"] == tbl[toml::path("b[2] \t.c")]); // whitespace is allowed after array + // indexers + + CHECK(tbl["d"]); + CHECK(tbl["d"] == tbl[toml::path("d")]); + + CHECK(tbl["d"]["e"]); + CHECK(tbl["d"]["e"] == tbl[toml::path("d.e")]); + CHECK(tbl["d"]["e"] != tbl[toml::path("d. e")]); // equivalent to "d"." e" + CHECK(!tbl[toml::path("d. e")]); + + CHECK(tbl["d"][""]); + CHECK(tbl["d"][""] == tbl[toml::path("d.")]); + } + + SECTION("std::variant mismatches") + { + toml::path p0("b[2].c"); + p0[1].value = "abc"; + CHECK(!at_path(tbl, p0)); + + toml::path p1("b[2].c"); + p1[0].value = std::size_t{ 1 }; + CHECK(!at_path(tbl, p1)); + } +} diff --git a/tests/vs/test_debug_x64.vcxproj b/tests/vs/test_debug_x64.vcxproj index add468c..66562a7 100644 --- a/tests/vs/test_debug_x64.vcxproj +++ b/tests/vs/test_debug_x64.vcxproj @@ -94,6 +94,7 @@ + diff --git a/tests/vs/test_debug_x64_cpplatest.vcxproj b/tests/vs/test_debug_x64_cpplatest.vcxproj index 300d6e7..87db1a9 100644 --- a/tests/vs/test_debug_x64_cpplatest.vcxproj +++ b/tests/vs/test_debug_x64_cpplatest.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_debug_x64_cpplatest_noexcept.vcxproj b/tests/vs/test_debug_x64_cpplatest_noexcept.vcxproj index aa14ce2..877b963 100644 --- a/tests/vs/test_debug_x64_cpplatest_noexcept.vcxproj +++ b/tests/vs/test_debug_x64_cpplatest_noexcept.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_debug_x64_cpplatest_noexcept_unrel.vcxproj b/tests/vs/test_debug_x64_cpplatest_noexcept_unrel.vcxproj index 0df0202..b00f2cb 100644 --- a/tests/vs/test_debug_x64_cpplatest_noexcept_unrel.vcxproj +++ b/tests/vs/test_debug_x64_cpplatest_noexcept_unrel.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_debug_x64_cpplatest_unrel.vcxproj b/tests/vs/test_debug_x64_cpplatest_unrel.vcxproj index 92ded47..357571f 100644 --- a/tests/vs/test_debug_x64_cpplatest_unrel.vcxproj +++ b/tests/vs/test_debug_x64_cpplatest_unrel.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_debug_x64_noexcept.vcxproj b/tests/vs/test_debug_x64_noexcept.vcxproj index 1a729fc..30becad 100644 --- a/tests/vs/test_debug_x64_noexcept.vcxproj +++ b/tests/vs/test_debug_x64_noexcept.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_debug_x64_noexcept_unrel.vcxproj b/tests/vs/test_debug_x64_noexcept_unrel.vcxproj index 732bcac..6077da6 100644 --- a/tests/vs/test_debug_x64_noexcept_unrel.vcxproj +++ b/tests/vs/test_debug_x64_noexcept_unrel.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_debug_x64_unrel.vcxproj b/tests/vs/test_debug_x64_unrel.vcxproj index c707504..436d16f 100644 --- a/tests/vs/test_debug_x64_unrel.vcxproj +++ b/tests/vs/test_debug_x64_unrel.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_debug_x86.vcxproj b/tests/vs/test_debug_x86.vcxproj index c825ceb..ca750d0 100644 --- a/tests/vs/test_debug_x86.vcxproj +++ b/tests/vs/test_debug_x86.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_debug_x86_cpplatest.vcxproj b/tests/vs/test_debug_x86_cpplatest.vcxproj index 34a6d9c..57f96c9 100644 --- a/tests/vs/test_debug_x86_cpplatest.vcxproj +++ b/tests/vs/test_debug_x86_cpplatest.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_debug_x86_cpplatest_noexcept.vcxproj b/tests/vs/test_debug_x86_cpplatest_noexcept.vcxproj index 9e0f3f5..dcd2b31 100644 --- a/tests/vs/test_debug_x86_cpplatest_noexcept.vcxproj +++ b/tests/vs/test_debug_x86_cpplatest_noexcept.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_debug_x86_cpplatest_noexcept_unrel.vcxproj b/tests/vs/test_debug_x86_cpplatest_noexcept_unrel.vcxproj index 2099ef7..e8d869e 100644 --- a/tests/vs/test_debug_x86_cpplatest_noexcept_unrel.vcxproj +++ b/tests/vs/test_debug_x86_cpplatest_noexcept_unrel.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_debug_x86_cpplatest_unrel.vcxproj b/tests/vs/test_debug_x86_cpplatest_unrel.vcxproj index 985609f..543508c 100644 --- a/tests/vs/test_debug_x86_cpplatest_unrel.vcxproj +++ b/tests/vs/test_debug_x86_cpplatest_unrel.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_debug_x86_noexcept.vcxproj b/tests/vs/test_debug_x86_noexcept.vcxproj index 1d20321..56b5971 100644 --- a/tests/vs/test_debug_x86_noexcept.vcxproj +++ b/tests/vs/test_debug_x86_noexcept.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_debug_x86_noexcept_unrel.vcxproj b/tests/vs/test_debug_x86_noexcept_unrel.vcxproj index 5dcc3fd..9c3b0af 100644 --- a/tests/vs/test_debug_x86_noexcept_unrel.vcxproj +++ b/tests/vs/test_debug_x86_noexcept_unrel.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_debug_x86_unrel.vcxproj b/tests/vs/test_debug_x86_unrel.vcxproj index 2a6abc5..116ef9e 100644 --- a/tests/vs/test_debug_x86_unrel.vcxproj +++ b/tests/vs/test_debug_x86_unrel.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_release_x64.vcxproj b/tests/vs/test_release_x64.vcxproj index c14ace3..8811130 100644 --- a/tests/vs/test_release_x64.vcxproj +++ b/tests/vs/test_release_x64.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_release_x64_cpplatest.vcxproj b/tests/vs/test_release_x64_cpplatest.vcxproj index 8793957..da4ee62 100644 --- a/tests/vs/test_release_x64_cpplatest.vcxproj +++ b/tests/vs/test_release_x64_cpplatest.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_release_x64_cpplatest_noexcept.vcxproj b/tests/vs/test_release_x64_cpplatest_noexcept.vcxproj index b4ab9d8..a0c71a8 100644 --- a/tests/vs/test_release_x64_cpplatest_noexcept.vcxproj +++ b/tests/vs/test_release_x64_cpplatest_noexcept.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_release_x64_cpplatest_noexcept_unrel.vcxproj b/tests/vs/test_release_x64_cpplatest_noexcept_unrel.vcxproj index af6f209..647cc47 100644 --- a/tests/vs/test_release_x64_cpplatest_noexcept_unrel.vcxproj +++ b/tests/vs/test_release_x64_cpplatest_noexcept_unrel.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_release_x64_cpplatest_unrel.vcxproj b/tests/vs/test_release_x64_cpplatest_unrel.vcxproj index 6f8619a..57409ac 100644 --- a/tests/vs/test_release_x64_cpplatest_unrel.vcxproj +++ b/tests/vs/test_release_x64_cpplatest_unrel.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_release_x64_noexcept.vcxproj b/tests/vs/test_release_x64_noexcept.vcxproj index 07d44a5..745f3a0 100644 --- a/tests/vs/test_release_x64_noexcept.vcxproj +++ b/tests/vs/test_release_x64_noexcept.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_release_x64_noexcept_unrel.vcxproj b/tests/vs/test_release_x64_noexcept_unrel.vcxproj index 6ddae97..8ead14e 100644 --- a/tests/vs/test_release_x64_noexcept_unrel.vcxproj +++ b/tests/vs/test_release_x64_noexcept_unrel.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_release_x64_unrel.vcxproj b/tests/vs/test_release_x64_unrel.vcxproj index c3c6f6a..1272813 100644 --- a/tests/vs/test_release_x64_unrel.vcxproj +++ b/tests/vs/test_release_x64_unrel.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_release_x86.vcxproj b/tests/vs/test_release_x86.vcxproj index 4f7747f..1723cc2 100644 --- a/tests/vs/test_release_x86.vcxproj +++ b/tests/vs/test_release_x86.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_release_x86_cpplatest.vcxproj b/tests/vs/test_release_x86_cpplatest.vcxproj index 0a6c7a2..21e0762 100644 --- a/tests/vs/test_release_x86_cpplatest.vcxproj +++ b/tests/vs/test_release_x86_cpplatest.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_release_x86_cpplatest_noexcept.vcxproj b/tests/vs/test_release_x86_cpplatest_noexcept.vcxproj index eb3c04e..6ac0205 100644 --- a/tests/vs/test_release_x86_cpplatest_noexcept.vcxproj +++ b/tests/vs/test_release_x86_cpplatest_noexcept.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_release_x86_cpplatest_noexcept_unrel.vcxproj b/tests/vs/test_release_x86_cpplatest_noexcept_unrel.vcxproj index 54c7b29..835c8e9 100644 --- a/tests/vs/test_release_x86_cpplatest_noexcept_unrel.vcxproj +++ b/tests/vs/test_release_x86_cpplatest_noexcept_unrel.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_release_x86_cpplatest_unrel.vcxproj b/tests/vs/test_release_x86_cpplatest_unrel.vcxproj index 817936e..43153cb 100644 --- a/tests/vs/test_release_x86_cpplatest_unrel.vcxproj +++ b/tests/vs/test_release_x86_cpplatest_unrel.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_release_x86_noexcept.vcxproj b/tests/vs/test_release_x86_noexcept.vcxproj index 4985df8..daa2156 100644 --- a/tests/vs/test_release_x86_noexcept.vcxproj +++ b/tests/vs/test_release_x86_noexcept.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_release_x86_noexcept_unrel.vcxproj b/tests/vs/test_release_x86_noexcept_unrel.vcxproj index cd2a582..6d0578e 100644 --- a/tests/vs/test_release_x86_noexcept_unrel.vcxproj +++ b/tests/vs/test_release_x86_noexcept_unrel.vcxproj @@ -74,6 +74,7 @@ + diff --git a/tests/vs/test_release_x86_unrel.vcxproj b/tests/vs/test_release_x86_unrel.vcxproj index e594c3f..cfe32af 100644 --- a/tests/vs/test_release_x86_unrel.vcxproj +++ b/tests/vs/test_release_x86_unrel.vcxproj @@ -74,6 +74,7 @@ + diff --git a/toml++.vcxproj b/toml++.vcxproj index c480938..46b1ba9 100644 --- a/toml++.vcxproj +++ b/toml++.vcxproj @@ -40,8 +40,11 @@ + + + @@ -102,6 +105,7 @@ + diff --git a/toml++.vcxproj.filters b/toml++.vcxproj.filters index 9f6be36..e56dbe7 100644 --- a/toml++.vcxproj.filters +++ b/toml++.vcxproj.filters @@ -133,6 +133,11 @@ include\impl + + include\impl + + + @@ -236,6 +241,9 @@ vendor + + include\impl + diff --git a/toml.hpp b/toml.hpp index 157b166..280123e 100644 --- a/toml.hpp +++ b/toml.hpp @@ -1177,6 +1177,8 @@ TOML_NAMESPACE_START template class value; + class path; + class toml_formatter; class json_formatter; class yaml_formatter; @@ -2598,6 +2600,14 @@ TOML_NAMESPACE_START TOML_EXPORTED_FREE_FUNCTION node_view at_path(const node& root, std::string_view path) noexcept; + TOML_NODISCARD + TOML_EXPORTED_FREE_FUNCTION + node_view at_path(node & root, const toml::path& path) noexcept; + + TOML_NODISCARD + TOML_EXPORTED_FREE_FUNCTION + node_view at_path(const node& root, const toml::path& path) noexcept; + #if TOML_ENABLE_WINDOWS_COMPAT TOML_NODISCARD @@ -2612,6 +2622,398 @@ TOML_NAMESPACE_START } TOML_NAMESPACE_END; +//******** impl/std_vector.h ***************************************************************************************** + +TOML_DISABLE_WARNINGS; +#include +#include +TOML_ENABLE_WARNINGS; + +//******** impl/std_variant.h **************************************************************************************** + +TOML_DISABLE_WARNINGS; +#include +TOML_ENABLE_WARNINGS; + +//******** impl/path.h *********************************************************************************************** + +TOML_PUSH_WARNINGS; +#ifdef _MSC_VER +#pragma push_macro("min") +#pragma push_macro("max") +#undef min +#undef max +#endif + +TOML_NAMESPACE_START +{ + enum class TOML_CLOSED_ENUM path_component_type : uint8_t + { + invalid = 0x0, + key = 0x1, + array_index = 0x2 + }; + + using path_component_value = std::variant; + + struct path_component + { + path_component_value value; + path_component_type type; + }; + + class TOML_EXPORTED_CLASS path + { + private: + + bool parse_error_ = false; + + std::vector components_; + + std::vector parse_(std::string_view, bool& parse_success); + + public: + + TOML_NODISCARD_CTOR + path() noexcept = default; + + TOML_NODISCARD_CTOR + TOML_EXPORTED_MEMBER_FUNCTION + explicit path(std::string_view); + + ~path() noexcept = default; + + TOML_NODISCARD_CTOR + path(const path& other) = default; + + TOML_NODISCARD_CTOR + path(path&& other) noexcept = default; + + path& operator=(const path&) = default; + + path& operator=(path&&) = default; + + TOML_EXPORTED_MEMBER_FUNCTION + path& operator/=(path&&) noexcept; + + TOML_EXPORTED_MEMBER_FUNCTION + path& operator/=(std::string_view); + + TOML_EXPORTED_MEMBER_FUNCTION + path& operator+=(path&&) noexcept; + + TOML_EXPORTED_MEMBER_FUNCTION + path& operator+=(std::string_view); + + TOML_NODISCARD + + inline path operator+(const toml::path& rhs) const + { + toml::path result = { *this }; + result.append(rhs); + + return result; + } + + TOML_NODISCARD + + inline path operator/(const toml::path& rhs) const + { + return *this + rhs; + } + + TOML_NODISCARD + explicit inline operator bool() const noexcept + { + return !parse_error_; + }; + + TOML_NODISCARD + inline operator std::string() const { return string(); } + + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + bool operator==(const path& compare) const noexcept; + + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + bool operator==(std::string_view compare) const noexcept; + + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + bool operator==(const char* compare) const noexcept; + + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + bool operator!=(const path& compare) const noexcept; + + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + bool operator!=(std::string_view compare) const noexcept; + + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + bool operator!=(const char* compare) const noexcept; + + TOML_NODISCARD + inline path_component& operator[](size_t index) noexcept + { + return components_[index]; + }; + + TOML_NODISCARD + inline const path_component& operator[](size_t index) const noexcept + { + return components_[index]; + }; + + TOML_NODISCARD + inline size_t size() const noexcept + { + return components_.size(); + }; + + TOML_NODISCARD + inline bool empty() const { return size() <= 0; } + + TOML_EXPORTED_MEMBER_FUNCTION + void clear() noexcept; + + TOML_NODISCARD + + inline auto begin() const noexcept { return components_.begin(); } + + TOML_NODISCARD + + inline auto end() const noexcept { return components_.end(); } + + TOML_EXPORTED_MEMBER_FUNCTION + path& truncate(size_t n); + + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + path truncated(size_t n) const; + + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + path parent_path() const; + + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + path leaf(size_t n = 1) const; + + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + path subpath(std::vector::const_iterator start, + std::vector::const_iterator end) const; + + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + path subpath(size_t start, size_t length) const; + + TOML_EXPORTED_MEMBER_FUNCTION + path& append(const toml::path&); + + TOML_EXPORTED_MEMBER_FUNCTION + path& append(toml::path&&); + + TOML_EXPORTED_MEMBER_FUNCTION + path& append(std::string_view); + + TOML_EXPORTED_MEMBER_FUNCTION + path& prepend(const toml::path&); + + TOML_EXPORTED_MEMBER_FUNCTION + path& prepend(toml::path&&); + + TOML_EXPORTED_MEMBER_FUNCTION + path& prepend(std::string_view); + + TOML_EXPORTED_MEMBER_FUNCTION + path& assign(std::string_view); + + TOML_EXPORTED_MEMBER_FUNCTION + path& assign(const path&); + + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + std::string string() const; + +#if TOML_ENABLE_WINDOWS_COMPAT + + TOML_NODISCARD_CTOR + TOML_EXPORTED_MEMBER_FUNCTION + explicit path(std::wstring_view); + + TOML_EXPORTED_MEMBER_FUNCTION + path& operator/=(std::wstring_view); + + TOML_EXPORTED_MEMBER_FUNCTION + path& operator+=(std::wstring_view); + + TOML_NODISCARD + inline operator std::wstring() const + { + return wstring(); + } + + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + bool operator==(std::wstring_view compare) const noexcept; + + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + bool operator!=(std::wstring_view compare) const noexcept; + + TOML_EXPORTED_MEMBER_FUNCTION + path& append(std::wstring_view); + + TOML_EXPORTED_MEMBER_FUNCTION + path& prepend(std::wstring_view); + + TOML_EXPORTED_MEMBER_FUNCTION + path& assign(std::wstring_view); + + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + std::wstring wstring() const; + +#endif // TOML_ENABLE_WINDOWS_COMPAT + + friend std::ostream& operator<<(std::ostream& os, const toml::path& rhs); + + friend std::istream& operator>>(std::istream& is, toml::path& rhs); + + TOML_NODISCARD + friend path operator+(const toml::path& lhs, std::string_view rhs) + { + return lhs + toml::path(rhs); + } + + TOML_NODISCARD + friend path operator+(const toml::path& lhs, const char* rhs) + { + return lhs + toml::path(rhs); + } + + TOML_NODISCARD + friend path operator+(std::string_view lhs, const toml::path& rhs) + { + toml::path result = { rhs }; + result.prepend(lhs); + + return result; + } + + TOML_NODISCARD + friend path operator+(const char* lhs, const toml::path& rhs) + { + toml::path result = { rhs }; + result.prepend(lhs); + + return result; + } + + TOML_NODISCARD + friend path operator/(const toml::path& lhs, std::string_view rhs) + { + return lhs + rhs; + } + + TOML_NODISCARD + friend path operator/(const toml::path& lhs, const char* rhs) + { + return lhs + toml::path(rhs); + } + + TOML_NODISCARD + friend path operator/(std::string_view lhs, const toml::path& rhs) + { + return lhs + rhs; + } + + TOML_NODISCARD + friend path operator/(const char* lhs, const toml::path& rhs) + { + return lhs + rhs; + } + +#if TOML_ENABLE_WINDOWS_COMPAT + + TOML_NODISCARD + friend path operator+(const toml::path& lhs, std::wstring_view rhs) + { + return lhs + toml::path(rhs); + } + + TOML_NODISCARD + friend path operator+(const toml::path& lhs, const wchar_t* rhs) + { + return lhs + toml::path(rhs); + } + + TOML_NODISCARD + friend path operator+(std::wstring_view lhs, const toml::path& rhs) + { + toml::path result = { rhs }; + result.prepend(lhs); + + return result; + } + + TOML_NODISCARD + friend path operator+(const wchar_t* lhs, const toml::path& rhs) + { + toml::path result = { rhs }; + result.prepend(lhs); + + return result; + } + + TOML_NODISCARD + friend path operator/(const toml::path& lhs, std::wstring_view rhs) + { + return lhs + rhs; + } + + TOML_NODISCARD + friend path operator/(const toml::path& lhs, const wchar_t* rhs) + { + return lhs + toml::path(rhs); + } + + TOML_NODISCARD + friend path operator/(std::wstring_view lhs, const toml::path& rhs) + { + return lhs + rhs; + } + + TOML_NODISCARD + friend path operator/(const wchar_t* lhs, const toml::path& rhs) + { + return lhs + rhs; + } + +#endif // TOML_ENABLE_WINDOWS_COMPAT + }; + + inline namespace literals + { + TOML_NODISCARD + inline toml::path operator"" _tpath(const char* str, size_t len) + { + return toml::path(std::string_view{ str, len }); + } + } +} +TOML_NAMESPACE_END; + +#ifdef _MSC_VER +#pragma pop_macro("min") +#pragma pop_macro("max") +#endif +TOML_POP_WARNINGS; + //******** impl/std_utility.h **************************************************************************************** TOML_DISABLE_WARNINGS; @@ -3183,6 +3585,14 @@ TOML_NAMESPACE_START TOML_EXPORTED_MEMBER_FUNCTION node_view at_path(std::string_view path) const noexcept; + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + node_view at_path(const toml::path& path) noexcept; + + TOML_NODISCARD + TOML_EXPORTED_MEMBER_FUNCTION + node_view at_path(const toml::path& path) const noexcept; + #if TOML_ENABLE_WINDOWS_COMPAT TOML_NODISCARD @@ -3212,13 +3622,6 @@ TOML_IMPL_NAMESPACE_END; #endif TOML_POP_WARNINGS; -//******** impl/std_vector.h ***************************************************************************************** - -TOML_DISABLE_WARNINGS; -#include -#include -TOML_ENABLE_WARNINGS; - //******** impl/std_initializer_list.h ******************************************************************************* TOML_DISABLE_WARNINGS; @@ -3646,12 +4049,24 @@ TOML_NAMESPACE_START return {}; } + TOML_NODISCARD + node_view operator[](const toml::path& path) const noexcept + { + return node_ ? node_->at_path(path) : node_view{}; + } + TOML_NODISCARD node_view at_path(std::string_view path) const noexcept { return node_ ? node_->at_path(path) : node_view{}; } + TOML_NODISCARD + node_view at_path(const toml::path& path) const noexcept + { + return node_ ? node_->at_path(path) : node_view{}; + } + #if TOML_ENABLE_WINDOWS_COMPAT TOML_NODISCARD @@ -7421,6 +7836,18 @@ TOML_NAMESPACE_START return node_view{ get(key) }; } + TOML_NODISCARD + node_view operator[](const toml::path& path) noexcept + { + return node_view{ at_path(path) }; + } + + TOML_NODISCARD + node_view operator[](const toml::path& path) const noexcept + { + return node_view{ at_path(path) }; + } + #if TOML_ENABLE_WINDOWS_COMPAT TOML_NODISCARD @@ -9502,6 +9929,18 @@ TOML_NAMESPACE_START return toml::at_path(*this, path); } + TOML_EXTERNAL_LINKAGE + node_view node::at_path(const toml::path& path) noexcept + { + return toml::at_path(*this, path); + } + + TOML_EXTERNAL_LINKAGE + node_view node::at_path(const toml::path& path) const noexcept + { + return toml::at_path(*this, path); + } + #if TOML_ENABLE_WINDOWS_COMPAT TOML_EXTERNAL_LINKAGE @@ -9614,7 +10053,9 @@ TOML_ANON_NAMESPACE_START #if TOML_INT_CHARCONV auto fc_result = std::from_chars(index_str.data(), index_str.data() + index_str.length(), index); - if (fc_result.ec != std::errc{}) + + // If not able to parse, or entire index not parseable, then fail (otherwise would allow a[1bc] == a[1] + if (fc_result.ec != std::errc{} || fc_result.ptr != index_str.data() + index_str.length()) return nullptr; #else @@ -9718,6 +10159,48 @@ TOML_ANON_NAMESPACE_START return current; } + TOML_INTERNAL_LINKAGE + node* get_at_path(node & root, const toml::path& path) + { + if (root.is_value()) // values don't have child nodes + return nullptr; + + // special check if table has a key that is an empty string, and the path is empty, + // return the node at that empty key. + if (path.size() == 0 && root.is_table() && root.as_table()->contains("")) + return root.as_table()->get(""); + + node* current = &root; + + for (const auto& component: path) + { + auto type = component.type; + if (type == path_component_type::array_index && std::holds_alternative(component.value)) + { + const auto current_array = current->as(); + if (!current_array) + return nullptr; // not an array, using array index doesn't work + + current = current_array->get(std::get(component.value)); + } + else if (type == path_component_type::key && std::holds_alternative(component.value)) + { + const auto current_table = current->as
(); + if (!current_table) + return nullptr; + + current = current_table->get(std::get(component.value)); + } + else + { + // Error: invalid component + return nullptr; + } + } + + return current; + } + #if TOML_ENABLE_WINDOWS_COMPAT TOML_INTERNAL_LINKAGE @@ -9750,6 +10233,18 @@ TOML_NAMESPACE_START return node_view{ TOML_ANON_NAMESPACE::get_at_path(const_cast(root), path) }; } + TOML_EXTERNAL_LINKAGE + node_view at_path(node & root, const toml::path& path) noexcept + { + return node_view{ TOML_ANON_NAMESPACE::get_at_path(root, path) }; + } + + TOML_EXTERNAL_LINKAGE + node_view at_path(const node& root, const toml::path& path) noexcept + { + return node_view{ TOML_ANON_NAMESPACE::get_at_path(const_cast(root), path) }; + } + #if TOML_ENABLE_WINDOWS_COMPAT TOML_EXTERNAL_LINKAGE @@ -9774,6 +10269,535 @@ TOML_NAMESPACE_END; #endif TOML_POP_WARNINGS; +//******** impl/path.inl ********************************************************************************************* + +TOML_DISABLE_WARNINGS; +#include +#include +#include +#if TOML_INT_CHARCONV +#include +#endif +TOML_ENABLE_WARNINGS; +TOML_PUSH_WARNINGS; +#ifdef _MSC_VER +#pragma push_macro("min") +#pragma push_macro("max") +#undef min +#undef max +#endif + +TOML_NAMESPACE_START +{ + TOML_EXTERNAL_LINKAGE + path::path(std::string_view path_str) + { + components_ = parse_(path_str, parse_error_); + } + + TOML_EXTERNAL_LINKAGE + path& path::operator/=(path&& rhs) noexcept + { + return append(std::move(rhs)); + } + + TOML_EXTERNAL_LINKAGE + path& path::operator/=(std::string_view source) + { + return append(source); + } + + TOML_EXTERNAL_LINKAGE + path& path::operator+=(path&& rhs) noexcept + { + return append(std::move(rhs)); + } + + TOML_EXTERNAL_LINKAGE + path& path::operator+=(std::string_view source) + { + return append(source); + } + + TOML_EXTERNAL_LINKAGE + bool path::operator==(const path& compare) const noexcept + { + if (components_.size() != compare.components_.size()) + return false; + + for (size_t i = 0; i < components_.size(); ++i) + { + if (components_[i].type != compare.components_[i].type + || components_[i].value != compare.components_[i].value) + return false; + } + + return true; + } + + TOML_EXTERNAL_LINKAGE + bool path::operator==(std::string_view compare) const noexcept + { + return string() == compare; + } + + TOML_EXTERNAL_LINKAGE + bool path::operator==(const char* compare) const noexcept + { + return string() == std::string_view(compare); + } + + TOML_EXTERNAL_LINKAGE + bool path::operator!=(const path& compare) const noexcept + { + return !(*this == compare); + } + + TOML_EXTERNAL_LINKAGE + bool path::operator!=(std::string_view compare) const noexcept + { + return !(*this == compare); + } + + TOML_EXTERNAL_LINKAGE + bool path::operator!=(const char* compare) const noexcept + { + return !(*this == std::string_view(compare)); + } + + TOML_EXTERNAL_LINKAGE + std::vector path::parse_(std::string_view path_str, bool& parse_error) + { + std::vector parsed_components; + parse_error = false; // success by default, set to true if fails to parse. + + if (path_str == "") // empty path + return parsed_components; + + auto str_start = path_str.data(); + size_t pos = 0; + const auto end = path_str.length(); + bool prev_was_array_indexer = false; + bool prev_was_dot = true; // invisible root 'dot' + + while (pos < end) + { + // start of an array indexer + if (path_str[pos] == '[') + { + // get array index substring + const auto index_start = pos + 1u; // first position after '[' + const auto index_end = path_str.find(']', index_start); // position of ']' + if (index_end == std::string_view::npos || index_end == index_start) // nothing in brackets, error + { + parsed_components.clear(); // empty object in case of error + parse_error = true; + return parsed_components; + } + auto index_str = std::string_view(&path_str[index_start], index_end - index_start); + + // trim whitespace from either side of the index + const auto first_non_ws = index_str.find_first_not_of(" \t"sv); + const auto last_non_ws = index_str.find_last_not_of(" \t"sv); + if (first_non_ws == std::string_view::npos) + { + parsed_components.clear(); // empty object in case of error + parse_error = true; + return parsed_components; + } + TOML_ASSERT_ASSUME(last_non_ws != std::string_view::npos); + index_str = index_str.substr(first_non_ws, (last_non_ws - first_non_ws) + 1u); + + // parse the actual array index to an integer type + size_t index; + if (index_str.length() == 1u && index_str[0] >= '0' && index_str[0] <= '9') + index = static_cast(index_str[0] - '0'); + else + { +#if TOML_INT_CHARCONV + + auto fc_result = std::from_chars(index_str.data(), index_str.data() + index_str.length(), index); + + // If not able to parse, or entire index not parseable, then fail (otherwise would allow a[1bc] == a[1] + if (fc_result.ec != std::errc{} || fc_result.ptr != index_str.data() + index_str.length()) + { + parsed_components.clear(); // empty object in case of error + parse_error_ = true; + return parsed_components; + } + +#else + + std::stringstream ss; + ss.imbue(std::locale::classic()); + ss.write(index_str.data(), static_cast(index_str.length())); + if (!(ss >> index)) + { + clear(); // empty object in case of error + parse_error_ = true; + return; + } + +#endif + } + + pos = index_end + 1u; + prev_was_dot = false; + prev_was_array_indexer = true; + + parsed_components.emplace_back(path_component{ { index }, path_component_type::array_index }); + } + + // start of a new table child + else if (path_str[pos] == '.') + { + // a dot immediately following another dot (or at the beginning of the string) is as if we'd asked + // for an empty child in between, e.g. + // + // foo..bar + // + // is equivalent to + // + // "foo".""."bar" + // + if (prev_was_dot) + parsed_components.emplace_back(path_component{ { ""s }, path_component_type::key }); + + pos++; + prev_was_dot = true; + prev_was_array_indexer = false; + } + + // some regular subkey + else + { + const auto subkey_start = pos; + const auto subkey_len = + impl::min(path_str.find_first_of(".["sv, subkey_start + 1u), path_str.length()) - subkey_start; + const auto subkey = path_str.substr(subkey_start, subkey_len); + + // a regular subkey segment immediately after an array indexer is OK if it was all whitespace, e.g.: + // + // "foo[0] .bar" + // ^^ skip this + // + // otherwise its an error (since it would have to be preceeded by a dot) + if (prev_was_array_indexer) + { + auto non_ws = subkey.find_first_not_of(" \t"); + if (non_ws == std::string_view::npos) + { + pos += subkey_len; + prev_was_dot = false; + prev_was_array_indexer = false; + continue; + } + else + { + parsed_components.clear(); // empty object in case of error + parse_error = true; + return parsed_components; + } + } + + pos += subkey_len; + prev_was_dot = false; + prev_was_array_indexer = false; + + parsed_components.emplace_back(path_component{ + std::string(std::string_view{ str_start + subkey_start, subkey_len }), + path_component_type::key + }); + } + } + + if (prev_was_dot) // Last character was a '.', which implies an empty string key at the end of the path + { + parsed_components.emplace_back(path_component{ ""s, path_component_type::key }); + } + + return parsed_components; + } + + TOML_EXTERNAL_LINKAGE + void path::clear() noexcept + { + this->components_.clear(); + } + + TOML_EXTERNAL_LINKAGE + path& path::truncate(size_t n) + { + n = n > components_.size() ? components_.size() : n; + + auto it_end = components_.end(); + components_.erase(it_end - static_cast(n), it_end); + + return *this; + } + + TOML_EXTERNAL_LINKAGE + path path::truncated(size_t n) const + { + path truncated_path {}; + + n = n > components_.size() ? components_.size() : n; + + // Copy all components except one + // Need at least two path components to have a parent, since if there is + // only one path component, the parent is the root/null path "" + truncated_path.components_.insert(truncated_path.components_.begin(), + components_.begin(), + components_.end() - static_cast(n)); + + return truncated_path; + } + + TOML_EXTERNAL_LINKAGE + path path::parent_path() const + { + return truncated(1); + } + + TOML_EXTERNAL_LINKAGE + path path::leaf(size_t n) const + { + toml::path leaf_path {}; + + n = n > components_.size() ? components_.size() : n; + + if (n > 0) + { + leaf_path.components_.insert(leaf_path.components_.begin(), + components_.end() - static_cast(n), + components_.end()); + } + + return leaf_path; + } + + TOML_EXTERNAL_LINKAGE + path path::subpath(std::vector::const_iterator start, + std::vector::const_iterator end) const + { + toml::path subpath{}; + + if (start > end) + { + return subpath; + } + + subpath.components_.insert(subpath.components_.begin(), start, end); + + return subpath; + } + + TOML_EXTERNAL_LINKAGE + path path::subpath(size_t start, size_t length) const + { + return subpath(begin() + static_cast(start), begin() + static_cast(start + length)); + } + + TOML_EXTERNAL_LINKAGE + path& path::append(const toml::path& source) + { + parse_error_ = false; // This will end up being a valid path when appended (even if previously failed and now + // empty) + + // Copy path parts to this object + for (const auto& component : source.components_) + { + components_.push_back(component); + } + + return *this; + } + + TOML_EXTERNAL_LINKAGE + path& path::append(toml::path&& source) + { + parse_error_ = false; // This will end up being a valid path when appended (even if previously failed and now + // empty) + + // Copy path parts to this object + for (auto& component : source.components_) + { + components_.emplace_back(std::move(component)); + } + + return *this; + } + + TOML_EXTERNAL_LINKAGE + path& path::append(std::string_view source) + { + auto components_to_append = parse_(source, parse_error_); + for (auto& component : components_to_append) + { + components_.emplace_back(std::move(component)); + } + + return *this; + } + + TOML_EXTERNAL_LINKAGE + path& path::prepend(const toml::path& source) + { + parse_error_ = false; // This will end up being a valid path when appended (even if previously failed and now + // empty) + + components_.insert(components_.begin(), source.components_.begin(), source.components_.end()); + + return *this; + } + + TOML_EXTERNAL_LINKAGE + path& path::prepend(toml::path&& source) + { + parse_error_ = false; // This will end up being a valid path when appended (even if previously failed and now + // empty) + + components_.insert(components_.begin(), std::make_move_iterator(source.components_.begin()), std::make_move_iterator(source.components_.end())); + + return *this; + } + + TOML_EXTERNAL_LINKAGE + path& path::prepend(std::string_view source) + { + auto components_to_prepend = parse_(source, parse_error_); + + components_.insert(components_.begin(), components_to_prepend.begin(), components_to_prepend.end()); + + return *this; + } + + TOML_EXTERNAL_LINKAGE + path& path::assign(std::string_view source) + { + components_ = parse_(source, parse_error_); + return *this; + } + + TOML_EXTERNAL_LINKAGE + path& path::assign(const path& source) + { + if (source) + { + components_ = source.components_; + } + else // propagate error of source + { + clear(); + parse_error_ = true; + } + + return *this; + } + + TOML_EXTERNAL_LINKAGE + std::string path::string() const + { + std::stringstream ss; + bool atRoot = true; + + for (const auto& component : components_) + { + if (component.type == path_component_type::key) // key + { + ss << (atRoot ? "" : ".") << std::get(component.value); + } + else if (component.type == path_component_type::array_index) // array + { + ss << "[" << std::get(component.value) << "]"; + } + atRoot = false; + } + + return ss.str(); + } + + TOML_EXTERNAL_LINKAGE + std::ostream& operator<<(std::ostream& os, const toml::path& rhs) + { + os << rhs.string(); + return os; + } + + TOML_EXTERNAL_LINKAGE + std::istream& operator>>(std::istream& is, toml::path& rhs) + { + std::string s; + is >> s; + rhs.assign(s); + + return is; + } + +#if TOML_ENABLE_WINDOWS_COMPAT + + TOML_EXTERNAL_LINKAGE + path::path(std::wstring_view path_str) : path(impl::narrow(path_str)) + { } + + TOML_EXTERNAL_LINKAGE + path& path::operator/=(std::wstring_view rhs) + { + return append(rhs); + } + + TOML_EXTERNAL_LINKAGE + path& path::operator+=(std::wstring_view rhs) + { + return append(rhs); + } + + TOML_EXTERNAL_LINKAGE + bool path::operator==(std::wstring_view compare) const noexcept + { + return *this == impl::narrow(compare); + } + + TOML_EXTERNAL_LINKAGE + bool path::operator!=(std::wstring_view compare) const noexcept + { + return *this != impl::narrow(compare); + } + + TOML_EXTERNAL_LINKAGE + path& path::append(std::wstring_view source) + { + return append(impl::narrow(source)); + } + + TOML_EXTERNAL_LINKAGE + path& path::prepend(std::wstring_view source) + { + return prepend(impl::narrow(source)); + } + + TOML_EXTERNAL_LINKAGE + path& path::assign(std::wstring_view source) + { + return assign(impl::narrow(source)); + } + + TOML_EXTERNAL_LINKAGE + std::wstring path::wstring() const + { + return impl::widen(string()); + } + +#endif // TOML_ENABLE_WINDOWS_COMPAT +} +TOML_NAMESPACE_END; + +#ifdef _MSC_VER +#pragma pop_macro("min") +#pragma pop_macro("max") +#endif +TOML_POP_WARNINGS; + //******** impl/array.inl ******************************************************************************************** TOML_PUSH_WARNINGS; diff --git a/tools/generate_windows_test_targets.py b/tools/generate_windows_test_targets.py index b3a4e7e..3b9398a 100755 --- a/tools/generate_windows_test_targets.py +++ b/tools/generate_windows_test_targets.py @@ -117,6 +117,7 @@ def main(): +