mirror of
https://github.com/ToruNiina/toml11.git
synced 2025-12-16 03:08:52 +08:00
Merge pull request #59 from ToruNiina/preserve-comments
Preserve comments; related to #48
This commit is contained in:
48
README.md
48
README.md
@@ -63,6 +63,7 @@ int main()
|
|||||||
- [Conversion between toml value and arbitrary types](#conversion-between-toml-value-and-arbitrary-types)
|
- [Conversion between toml value and arbitrary types](#conversion-between-toml-value-and-arbitrary-types)
|
||||||
- [Invalid UTF-8 Codepoints](#invalid-utf-8-codepoints)
|
- [Invalid UTF-8 Codepoints](#invalid-utf-8-codepoints)
|
||||||
- [Formatting user-defined error messages](#formatting-user-defined-error-messages)
|
- [Formatting user-defined error messages](#formatting-user-defined-error-messages)
|
||||||
|
- [Getting comments related to a value](#getting-comments)
|
||||||
- [Serializing TOML data](#serializing-toml-data)
|
- [Serializing TOML data](#serializing-toml-data)
|
||||||
- [Underlying types](#underlying-types)
|
- [Underlying types](#underlying-types)
|
||||||
- [Running Tests](#running-tests)
|
- [Running Tests](#running-tests)
|
||||||
@@ -831,6 +832,53 @@ you will get an error message like this.
|
|||||||
| ~~ maximum number here
|
| ~~ 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<toml::array>(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
|
## Serializing TOML data
|
||||||
|
|
||||||
toml11 (after v2.1.0) enables you to serialize data into toml format.
|
toml11 (after v2.1.0) enables you to serialize data into toml format.
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ set(TEST_NAMES
|
|||||||
test_parse_key
|
test_parse_key
|
||||||
test_parse_table_key
|
test_parse_table_key
|
||||||
test_literals
|
test_literals
|
||||||
|
test_comments
|
||||||
test_get
|
test_get
|
||||||
test_get_related_func
|
test_get_related_func
|
||||||
test_from_toml
|
test_from_toml
|
||||||
|
|||||||
125
tests/test_comments.cpp
Normal file
125
tests/test_comments.cpp
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
#define BOOST_TEST_MODULE "test_comments"
|
||||||
|
#ifdef UNITTEST_FRAMEWORK_LIBRARY_EXIST
|
||||||
|
#include <boost/test/unit_test.hpp>
|
||||||
|
#else
|
||||||
|
#define BOOST_TEST_NO_LIB
|
||||||
|
#include <boost/test/included/unit_test.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <toml.hpp>
|
||||||
|
|
||||||
|
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<toml::array>(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.)");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,6 +52,14 @@ struct region_base
|
|||||||
virtual std::size_t before() const noexcept {return 0;}
|
virtual std::size_t before() const noexcept {return 0;}
|
||||||
// number of characters in the line after the region
|
// number of characters in the line after the region
|
||||||
virtual std::size_t after() const noexcept {return 0;}
|
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.
|
// 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 name() const override {return source_name_;}
|
||||||
|
|
||||||
|
std::string comment_before() const override
|
||||||
|
{
|
||||||
|
auto iter = this->line_begin(); // points the first element
|
||||||
|
std::vector<std::pair<decltype(iter), decltype(iter)>> comments;
|
||||||
|
while(iter != this->begin())
|
||||||
|
{
|
||||||
|
iter = std::prev(iter);
|
||||||
|
using rev_iter = std::reverse_iterator<decltype(iter)>;
|
||||||
|
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:
|
private:
|
||||||
|
|
||||||
source_ptr source_;
|
source_ptr source_;
|
||||||
|
|||||||
@@ -607,6 +607,19 @@ class value
|
|||||||
template<value_t T>
|
template<value_t T>
|
||||||
typename detail::toml_default_type<T>::type&& cast() &&;
|
typename detail::toml_default_type<T>::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:
|
private:
|
||||||
|
|
||||||
void cleanup() noexcept
|
void cleanup() noexcept
|
||||||
|
|||||||
Reference in New Issue
Block a user