Merge pull request #59 from ToruNiina/preserve-comments

Preserve comments; related to #48
This commit is contained in:
Toru Niina 2019-04-27 17:40:26 +09:00 committed by GitHub
commit 90f84000ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 281 additions and 0 deletions

View File

@ -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<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
toml11 (after v2.1.0) enables you to serialize data into toml format.

View File

@ -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

125
tests/test_comments.cpp Normal file
View 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.)");
}
}

View File

@ -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<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:
source_ptr source_;

View File

@ -607,6 +607,19 @@ class value
template<value_t T>
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:
void cleanup() noexcept