diff --git a/README.md b/README.md index e8ea0a6..685446d 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ int main() - [Conversion between toml value and arbitrary types](#conversion-between-toml-value-and-arbitrary-types) - [Invalid UTF-8 Codepoints](#invalid-utf-8-codepoints) - [Formatting user-defined error messages](#formatting-user-defined-error-messages) +- [Getting comments related to a value](#getting-comments) - [Serializing TOML data](#serializing-toml-data) - [Underlying types](#underlying-types) - [Running Tests](#running-tests) @@ -831,6 +832,53 @@ you will get an error message like this. | ~~ maximum number here ``` +## Getting comments + +Since toml11 keeps a file data until all the values are destructed, you can +also extract a comment related to a value by calling `toml::value::comment()`. + +If there is a comment just after a value (within the same line), you can get +the specific comment by `toml::value::comment_inline()`. + +If there are comments just before a value (without any newline between them), +you can get the comments by `toml::value::comment_before()`. + +`toml::value::comment()` returns the results of both functions after +concatenating them. + +```toml +a = 42 # comment for a. + +# comment for b. +# this is also a comment for b. +b = "foo" + +c = [ # comment for c. + 3.14, # this is not a comment for c, but for 3.14. +] # this is also a comment for c. +``` + +```cpp +// "# comment for a." +const std::string com1 = toml::find(data, "a").comment(); + +// "# comment for b." +const std::string com2 = toml::find(data, "b").comment(); + +// "# comment for c.\n# this is also a comment for c." +const std::string com3 = toml::find(data, "c").comment(); + +// "# this is not a comment for c, but for 3.14." +const std::string com3 = toml::find(data, "c").front().comment(); +``` + +Note that once a data in a value is modified, the related file region +information would be deleted. Thus after modifying a data, you cannot find any +comments. + +Also note that currently it does not support any way to set a comment to a value. +And currently, serializer does not take comments into account. + ## Serializing TOML data toml11 (after v2.1.0) enables you to serialize data into toml format. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3de9785..04ab6c4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -21,6 +21,7 @@ set(TEST_NAMES test_parse_key test_parse_table_key test_literals + test_comments test_get test_get_related_func test_from_toml diff --git a/tests/test_comments.cpp b/tests/test_comments.cpp new file mode 100644 index 0000000..a6325e4 --- /dev/null +++ b/tests/test_comments.cpp @@ -0,0 +1,125 @@ +#define BOOST_TEST_MODULE "test_comments" +#ifdef UNITTEST_FRAMEWORK_LIBRARY_EXIST +#include +#else +#define BOOST_TEST_NO_LIB +#include +#endif + +#include + +BOOST_AUTO_TEST_CASE(test_comment_before) +{ + using namespace toml::literals::toml_literals; + { + const toml::value v = u8R"( + # comment for a. + a = 42 + # comment for b. + b = "baz" + )"_toml; + + BOOST_CHECK_EQUAL(toml::find(v, "a").comment_before(), u8"# comment for a."); + BOOST_CHECK_EQUAL(toml::find(v, "b").comment_before(), u8"# comment for b."); + BOOST_CHECK_EQUAL(toml::find(v, "a").comment_inline(), ""); + BOOST_CHECK_EQUAL(toml::find(v, "b").comment_inline(), ""); + + BOOST_CHECK_EQUAL(toml::find(v, "a").comment(), u8"# comment for a."); + BOOST_CHECK_EQUAL(toml::find(v, "b").comment(), u8"# comment for b."); + } + + { + const toml::value v = u8R"( + # comment for a. + # another comment for a. + a = 42 + # comment for b. + # also comment for b. + b = "baz" + )"_toml; + + BOOST_CHECK_EQUAL(toml::find(v, "a").comment_before(), u8R"(# comment for a. +# another comment for a.)"); + BOOST_CHECK_EQUAL(toml::find(v, "b").comment_before(), u8R"(# comment for b. +# also comment for b.)"); + BOOST_CHECK_EQUAL(toml::find(v, "a").comment_inline(), u8""); + BOOST_CHECK_EQUAL(toml::find(v, "b").comment_inline(), u8""); + + BOOST_CHECK_EQUAL(toml::find(v, "a").comment(), u8R"(# comment for a. +# another comment for a.)"); + BOOST_CHECK_EQUAL(toml::find(v, "b").comment(), u8R"(# comment for b. +# also comment for b.)"); + } +} + +BOOST_AUTO_TEST_CASE(test_comment_inline) +{ + using namespace toml::literals::toml_literals; + { + const toml::value v = u8R"( + a = 42 # comment for a. + b = "baz" # comment for b. + )"_toml; + + BOOST_CHECK_EQUAL(toml::find(v, "a").comment_before(), ""); + BOOST_CHECK_EQUAL(toml::find(v, "b").comment_before(), ""); + BOOST_CHECK_EQUAL(toml::find(v, "a").comment_inline(), u8"# comment for a."); + BOOST_CHECK_EQUAL(toml::find(v, "b").comment_inline(), u8"# comment for b."); + + BOOST_CHECK_EQUAL(toml::find(v, "a").comment(), u8"# comment for a."); + BOOST_CHECK_EQUAL(toml::find(v, "b").comment(), u8"# comment for b."); + } + { + const toml::value v = u8R"( + a = [ # comment for a. + 42, + ] # this also. + b = [ # comment for b. + "bar", + ] + c = [ + 3.14, # this is not a comment for c, but 3.14. + ] # comment for c. + )"_toml; + + BOOST_CHECK_EQUAL(toml::find(v, "a").comment_before(), ""); + BOOST_CHECK_EQUAL(toml::find(v, "b").comment_before(), ""); + BOOST_CHECK_EQUAL(toml::find(v, "c").comment_before(), ""); + + BOOST_CHECK_EQUAL(toml::find(v, "a").comment_inline(), u8R"(# comment for a. +# this also.)"); + BOOST_CHECK_EQUAL(toml::find(v, "b").comment_inline(), u8"# comment for b."); + BOOST_CHECK_EQUAL(toml::find(v, "c").comment_inline(), u8"# comment for c."); + + BOOST_CHECK_EQUAL(toml::find(v, "a").comment(), u8R"(# comment for a. +# this also.)"); + BOOST_CHECK_EQUAL(toml::find(v, "b").comment(), u8"# comment for b."); + BOOST_CHECK_EQUAL(toml::find(v, "c").comment(), u8"# comment for c."); + + const auto& c0 = toml::find(v, "c").at(0); + BOOST_CHECK_EQUAL(c0.comment(), u8"# this is not a comment for c, but 3.14."); + } +} + +BOOST_AUTO_TEST_CASE(test_comment_both) +{ + using namespace toml::literals::toml_literals; + { + const toml::value v = u8R"( + # comment for a. + a = 42 # inline comment for a. + # comment for b. + b = "baz" # inline comment for b. + )"_toml; + + BOOST_CHECK_EQUAL(toml::find(v, "a").comment_before(), "# comment for a."); + BOOST_CHECK_EQUAL(toml::find(v, "b").comment_before(), "# comment for b."); + BOOST_CHECK_EQUAL(toml::find(v, "a").comment_inline(), "# inline comment for a."); + BOOST_CHECK_EQUAL(toml::find(v, "b").comment_inline(), "# inline comment for b."); + + BOOST_CHECK_EQUAL(toml::find(v, "a").comment(), u8R"(# comment for a. +# inline comment for a.)"); + BOOST_CHECK_EQUAL(toml::find(v, "b").comment(), u8R"(# comment for b. +# inline comment for b.)"); + } +} diff --git a/toml/region.hpp b/toml/region.hpp index 6ef6f9b..7b4c006 100644 --- a/toml/region.hpp +++ b/toml/region.hpp @@ -52,6 +52,14 @@ struct region_base virtual std::size_t before() const noexcept {return 0;} // number of characters in the line after the region virtual std::size_t after() const noexcept {return 0;} + + virtual std::string comment_before() const {return "";} // just before + virtual std::string comment_inline() const {return "";} // in the same line + virtual std::string comment() const {return "";} // concatenate + // ```toml + // # comment_before + // key = "value" # comment_inline + // ``` }; // location represents a position in a container, which contains a file content. @@ -280,6 +288,92 @@ struct region final : public region_base std::string name() const override {return source_name_;} + std::string comment_before() const override + { + auto iter = this->line_begin(); // points the first element + std::vector> comments; + while(iter != this->begin()) + { + iter = std::prev(iter); + using rev_iter = std::reverse_iterator; + auto line_before = std::find(rev_iter(iter), rev_iter(this->begin()), + '\n').base(); + // range [line_before, iter) represents the previous line + + auto comment_found = std::find(line_before, iter, '#'); + if(iter != comment_found && std::all_of(line_before, comment_found, + [](const char c) noexcept -> bool { + return c == ' ' || c == '\t'; + })) + { + // the line before this range contains only a comment. + comments.push_back(std::make_pair(comment_found, iter)); + } + else + { + break; + } + iter = line_before; + } + + std::string com; + for(auto i = comments.crbegin(), e = comments.crend(); i!=e; ++i) + { + if(i != comments.crbegin()) {com += '\n';} + com += std::string(i->first, i->second); + } + return com; + } + + std::string comment_inline() const override + { + if(this->contain_newline()) + { + std::string com; + // check both the first and the last line. + const auto first_line_end = + std::find(this->line_begin(), this->last(), '\n'); + const auto first_comment_found = + std::find(this->line_begin(), first_line_end, '#'); + + if(first_comment_found != first_line_end) + { + com += std::string(first_comment_found, first_line_end); + } + + const auto last_comment_found = + std::find(this->last(), this->line_end(), '#'); + if(last_comment_found != this->line_end()) + { + if(!com.empty()){com += '\n';} + com += std::string(last_comment_found, this->line_end()); + } + return com; + } + const auto comment_found = + std::find(this->line_begin(), this->line_end(), '#'); + return std::string(comment_found, this->line_end()); + } + + std::string comment() const override + { + std::string com_bef = this->comment_before(); + std::string com_inl = this->comment_inline(); + if(!com_bef.empty() && !com_inl.empty()) + { + com_bef += '\n'; + return com_bef + com_inl; + } + else if(com_bef.empty()) + { + return com_inl; + } + else + { + return com_bef; + } + } + private: source_ptr source_; diff --git a/toml/value.hpp b/toml/value.hpp index ef4bd15..9af4040 100644 --- a/toml/value.hpp +++ b/toml/value.hpp @@ -607,6 +607,19 @@ class value template typename detail::toml_default_type::type&& cast() &&; + std::string comment() const + { + return this->region_info_->comment(); + } + std::string comment_before() const + { + return this->region_info_->comment_before(); + } + std::string comment_inline() const + { + return this->region_info_->comment_inline(); + } + private: void cleanup() noexcept