Compare commits

..

46 Commits

Author SHA1 Message Date
ToruNiina
5e3f8f9105 chore: update version values 2021-03-25 22:43:37 +09:00
ToruNiina
17a15d3c18 doc: update contributor list and link in README 2021-03-25 22:33:05 +09:00
ToruNiina
42cc111b05 ci: activate linux/windows
confirmed that macos works.
2021-03-25 15:01:40 +09:00
ToruNiina
5e0ee32854 ci: trying to add macos to github actions [skip travis] [skip appveyor]
it is already listed in travis CI, but not in the GH actions
2021-03-25 14:53:28 +09:00
ToruNiina
2c5cc431fe ci: re-activate linux CI 2021-03-25 14:33:55 +09:00
ToruNiina
970f7cb36a ci: trying to update boost installation settings [skip travis] [skip appveyor] 2021-03-25 14:03:26 +09:00
ToruNiina
b924e70e3c feat: add a simple way to disable <filesystem>
As jwillikers pointed out in #150, there is a case where compiler
defines the corresponding feature test macro of <filesystem> but is
actually not available. The macro is a way to disable the feature
regardless of the status of feature test macro.
2021-03-25 11:44:11 +09:00
Toru Niina
7782258e68 Merge pull request #148 from sneakypete81/patch-1
Fix typo in error message
2021-01-31 14:26:02 +09:00
sneakypete81
08859c36d0 Fix typo in error message 2021-01-30 20:04:00 +00:00
ToruNiina
d3de136562 doc: simplity example code a bit 2021-01-25 17:25:29 +09:00
ToruNiina
43183e2ad1 Merge branch 'master' of github.com:ToruNiina/toml11 2020-12-29 18:54:58 +09:00
ToruNiina
e9144b41fb test: returning toml::value directly from into<T> 2020-12-29 18:53:10 +09:00
ToruNiina
2fb8793f1a doc: add document about basic_value and toml::into
related to #146.
2020-12-29 18:52:07 +09:00
Toru Niina
6c8a53915a Merge pull request #144 from amerry/sstream-include-fix
Add missing standard includes
2020-12-10 01:53:31 +09:00
Alex Merry
db2d33ca4b Add missing header for std::out_of_range exception
Failure seen on GCC 4.8.5 when including "toml/value.hpp".
2020-12-09 10:39:10 +00:00
Alex Merry
935da51769 Add missing include for ostringstream
Since region.hpp no longer includes <iostream> (but only <iomanip>),
source_location.hpp no longer includes a header that provides
std::ostringstream. Including <sstream> fixes this.
2020-12-09 10:19:07 +00:00
ToruNiina
be0d4bd0a9 fix: fix #141; Merge branch 'issue-141' 2020-11-05 00:01:41 +09:00
ToruNiina
9b472a6c72 fix: check it is empty before calling back 2020-11-04 23:24:59 +09:00
ToruNiina
1ead14589e fix: check if it is empty before calling back() 2020-11-04 23:24:02 +09:00
ToruNiina
b13065b1b5 fix: #142 Merge branch 'issue-142' 2020-11-03 21:05:03 +09:00
ToruNiina
a6581ee66b fix: an empty array is not an array of table 2020-11-03 20:34:01 +09:00
ToruNiina
0dafa7ee42 test: add case where a table should be inlined
array-of-table implicitly defines an array. If the array itself has a
comment, we need to format it explicitly.
2020-10-18 20:45:12 +09:00
ToruNiina
908b91079b fix: distinguish the comments and try to keep it
If a value has a comment, we need to try to write it explicitly.
2020-10-18 20:43:33 +09:00
ToruNiina
fce6ff317e refactor: distinguish the reason of failure 2020-10-18 18:36:05 +09:00
ToruNiina
fd50b11523 refactor: add write_comments() 2020-10-18 18:35:56 +09:00
ToruNiina
9090b8273c refactor: move array-of-table stuff to a function 2020-10-18 17:20:06 +09:00
ToruNiina
382e3dc3ab refactor: use serializer::is_array_of_tables 2020-10-14 22:27:29 +09:00
ToruNiina
f7bfcdd7aa fix: check all the elements in an array
while checking if the array is array-of-tables or not (heterogeneous
arrays are allowed, so there might be an array that has a table and
an integer at the same time)
2020-10-14 18:00:04 +09:00
ToruNiina
2e41a26785 Merge branch 'master' of github.com:ToruNiina/toml11 into master 2020-10-14 15:35:18 +09:00
ToruNiina
f3378f0ac1 fix: #131 distinguish implicitly declared array 2020-10-14 15:32:08 +09:00
ToruNiina
12ee73d6a9 ci: suppress some of the combinations in CI
clang-7 with C++20 fails with the same reason, 'undefined reference to
std::allocator<char>::(de)allocate'.
2020-10-14 00:38:46 +09:00
ToruNiina
503baf52ed ci: suppress clang 6 + cxx20
Since the main branch that passed the same check 9 days ago also fails
with clang-6 and C++20 because of the same error, "undefined reference
to allocator_traits<char>::allocate". It could be a change in upstream
and since others (e.g. gcc) works well, I suppress the setting at this
moment.
2020-10-14 00:05:55 +09:00
ToruNiina
2deb75052c ci: use the same version of clang
I don't think it resolves the problem, undefined reference to
'std::allocator<char>::deallocate(char*, unsigned long)', though
2020-10-13 23:37:52 +09:00
ToruNiina
290dca3d67 test: add test for comment duplication 2020-10-13 22:04:28 +09:00
ToruNiina
f283a257d2 Revert "quick temporary patch for comment dup"
This reverts commit a6d38c1ec0.
Since the problem is solved, we don't need this patch any more.
2020-10-13 22:02:32 +09:00
ToruNiina
3d86f3a4e1 fix: avoid comment duplication in array of tables 2020-10-13 21:59:46 +09:00
ToruNiina
dc5a8069a9 refactor: require comments while construction
Note: at this commit, the code would not compile.
2020-10-13 21:58:08 +09:00
Toru Niina
4f31b90665 Merge pull request #136 from chronoxor/master
Fixed: Compile toml11 with MinGW cause error in <filesystem> #135
2020-10-04 18:53:34 +09:00
Ivan Shynkarenka
5d8c573357 Fixed: Compile toml11 with MinGW cause error in <filesystem> #136 2020-10-03 23:16:58 +03:00
Ivan Shynkarenka
6e1e5ccd84 Fixed: Compile toml11 with MinGW cause error in <filesystem> #136 2020-10-03 23:06:47 +03:00
Ivan Shynkarenka
f2d9fd1d1f Fixed: Compile toml11 with MinGW cause error in <filesystem> #136 2020-10-03 22:36:59 +03:00
Ivan Shynkarenka
97c8cbdaf5 Fixed: Compile toml11 with MinGW cause error in <filesystem> #135 2020-10-02 19:10:04 +03:00
ToruNiina
05ceb5ae79 fix: workaround for error around SFINAE in MSVC
avoid lambda with template argument
2020-09-29 02:26:16 +09:00
ToruNiina
96cfdb260a fix: update version in macro and cmake 2020-09-29 01:41:38 +09:00
ToruNiina
0fec125688 feat: remove default value from internal src 2020-09-29 01:40:49 +09:00
ToruNiina
a6d38c1ec0 fix: add a quick temporary patch for comment dup
first aid for #131
2020-09-22 17:36:24 +09:00
15 changed files with 460 additions and 184 deletions

View File

@@ -58,6 +58,8 @@ jobs:
- {compiler: '3.9', standard: '20'}
- {compiler: '4.0', standard: '20'}
- {compiler: '5.0', standard: '20'}
- {compiler: '6.0', standard: '20'}
- {compiler: '7', standard: '20'}
steps:
- name: Checkout
uses: actions/checkout@v2
@@ -76,13 +78,38 @@ jobs:
- name: Configure
run: |
mkdir build && cd build
cmake .. -Dtoml11_BUILD_TEST=ON -DCMAKE_CXX_COMPILER=clang++-${{ matrix.compiler }} -DCMAKE_CXX_STANDARD=${{ matrix.standard }}
cmake .. -Dtoml11_BUILD_TEST=ON -DCMAKE_C_COMPILER=clang-${{ matrix.compiler }} -DCMAKE_CXX_COMPILER=clang++-${{ matrix.compiler }} -DCMAKE_CXX_STANDARD=${{ matrix.standard }}
- name: Build
run: |
cd build && cmake --build .
- name: Test
run: |
cd build && ctest --output-on-failure
build-osx:
runs-on: macos-10.15
strategy:
matrix:
standard: ['11', '14', '17', '20']
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
- name: Install
run: |
brew install boost
- name: Configure
run: |
mkdir build && cd build
cmake .. -Dtoml11_BUILD_TEST=ON -DCMAKE_CXX_STANDARD=${{ matrix.standard }}
- name: Build
run: |
cd build && cmake --build .
- name: Test
run: |
cd build && ctest --output-on-failure
build-windows-msvc:
runs-on: windows-2019
strategy:
@@ -94,6 +121,13 @@ jobs:
uses: actions/checkout@v2
with:
submodules: true
- name: Install
run: |
(New-Object System.Net.WebClient).DownloadFile("https://github.com/actions/boost-versions/releases/download/1.72.0-20200608.4/boost-1.72.0-win32-msvc14.2-x86_64.tar.gz", "$env:TEMP\\boost.tar.gz")
7z.exe x "$env:TEMP\\boost.tar.gz" -o"$env:TEMP\\boostArchive" -y | Out-Null
7z.exe x "$env:TEMP\\boostArchive" -o"$env:TEMP\\boost" -y | Out-Null
Push-Location -Path "$env:TEMP\\boost"
Invoke-Expression .\\setup.ps1
- uses: ilammy/msvc-dev-cmd@v1
- name: Configure
shell: cmd
@@ -101,7 +135,7 @@ jobs:
file --mime-encoding tests/test_literals.cpp
mkdir build
cd build
cmake ../ -G "NMake Makefiles" -Dtoml11_BUILD_TEST=ON -DCMAKE_CXX_STANDARD=${{ matrix.standard }} -DBoost_ADDITIONAL_VERSIONS=1.72.0 -DBoost_USE_MULTITHREADED=ON -DBoost_ARCHITECTURE=-x64 -DBoost_NO_BOOST_CMAKE=ON -DBOOST_ROOT=%BOOST_ROOT_1_72_0%
cmake ../ -G "NMake Makefiles" -Dtoml11_BUILD_TEST=ON -DCMAKE_CXX_STANDARD=${{ matrix.standard }} -DBoost_NO_BOOST_CMAKE=ON -DBOOST_ROOT="C:\\hostedtoolcache\\windows\\Boost\\1.72.0\\x86_64"
- name: Build
working-directory: ./build
run: |

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.1)
enable_testing()
project(toml11 VERSION 3.5.0)
project(toml11 VERSION 3.6.1)
option(toml11_BUILD_TEST "Build toml tests" OFF)
option(toml11_TEST_WITH_ASAN "use LLVM address sanitizer" OFF)

View File

@@ -11,7 +11,7 @@ toml11
toml11 is a C++11 (or later) header-only toml parser/encoder depending only on C++ standard library.
- It is compatible to the latest version of [TOML v1.0.0-rc.2](https://toml.io/en/v1.0.0-rc.2).
- It is compatible to the latest version of [TOML v1.0.0](https://toml.io/en/v1.0.0).
- It is one of the most TOML standard compliant libraries, tested with [the language agnostic test suite for TOML parsers by BurntSushi](https://github.com/BurntSushi/toml-test).
- It shows highly informative error messages. You can see the error messages about invalid files at [CircleCI](https://circleci.com/gh/ToruNiina/toml11).
- It has configurable container. You can use any random-access containers and key-value maps as backend containers.
@@ -28,6 +28,10 @@ toml11 is a C++11 (or later) header-only toml parser/encoder depending only on C
int main()
{
// ```toml
// title = "an example toml file"
// nums = [3, 1, 4, 1, 5]
// ```
auto data = toml::parse("example.toml");
// find a value with the specified type from a table
@@ -37,9 +41,9 @@ int main()
std::vector<int> nums = toml::find<std::vector<int>>(data, "nums");
// access with STL-like manner
if(not data.at("a").contains("b"))
if(not data.contains("foo"))
{
data["a"]["b"] = "c";
data["foo"] = "bar";
}
// pass a fallback
@@ -1303,9 +1307,9 @@ struct foo
double b;
std::string c;
toml::table into_toml() const // you need to mark it const.
toml::value into_toml() const // you need to mark it const.
{
return toml::table{{"a", this->a}, {"b", this->b}, {"c", this->c}};
return toml::value{{"a", this->a}, {"b", this->b}, {"c", this->c}};
}
};
} // ext
@@ -1332,9 +1336,9 @@ namespace toml
template<>
struct into<ext::foo>
{
static toml::table into_toml(const ext::foo& f)
static toml::value into_toml(const ext::foo& f)
{
return toml::table{{"a", f.a}, {"b", f.b}, {"c", f.c}};
return toml::value{{"a", f.a}, {"b", f.b}, {"c", f.c}};
}
};
} // toml
@@ -1346,6 +1350,27 @@ toml::value v(f);
Any type that can be converted to `toml::value`, e.g. `int`, `toml::table` and
`toml::array` are okay to return from `into_toml`.
You can also return a custom `toml::basic_value` from `toml::into`.
```cpp
namespace toml
{
template<>
struct into<ext::foo>
{
static toml::basic_value<toml::preserve_comments> into_toml(const ext::foo& f)
{
toml::basic_value<toml::preserve_comments> v{{"a", f.a}, {"b", f.b}, {"c", f.c}};
v.comments().push_back(" comment");
return v;
}
};
} // toml
```
But note that, if this `basic_value` would be assigned into other `toml::value`
that discards `comments`, the comments would be dropped.
## Formatting user-defined error messages
When you encounter an error after you read the toml value, you may want to
@@ -1848,6 +1873,12 @@ I appreciate the help of the contributors who introduced the great feature to th
- Fix include path in README
- Mohammed Alyousef (@MoAlyousef)
- Made testing optional in CMake
- Ivan Shynkarenka (@chronoxor)
- Fix compilation error in `<filesystem>` with MinGW
- Alex Merry (@amerry)
- Add missing include files
- sneakypete81 (@sneakypete81)
- Fix typo in error message
## Licensing terms

View File

@@ -138,6 +138,43 @@ BOOST_AUTO_TEST_CASE(test_comment_both)
}
}
BOOST_AUTO_TEST_CASE(test_comments_on_implicit_values)
{
{
const std::string file = R"(
# comment for the first element of array-of-tables.
[[array-of-tables]]
foo = "bar"
)";
std::istringstream iss(file);
const auto v = toml::parse<toml::preserve_comments>(iss);
const auto aot = toml::find(v, "array-of-tables");
const auto elm = aot.at(0);
BOOST_TEST(aot.comments().empty());
BOOST_TEST(elm.comments().size() == 1);
BOOST_TEST(elm.comments().front() == " comment for the first element of array-of-tables.");
}
{
const std::string file = R"(
# comment for the array itself
array-of-tables = [
# comment for the first element of array-of-tables.
{foo = "bar"}
]
)";
std::istringstream iss(file);
const auto v = toml::parse<toml::preserve_comments>(iss);
const auto aot = toml::find(v, "array-of-tables");
const auto elm = aot.at(0);
BOOST_TEST(aot.comments().size() == 1);
BOOST_TEST(aot.comments().front() == " comment for the array itself");
BOOST_TEST(elm.comments().size() == 1);
BOOST_TEST(elm.comments().front() == " comment for the first element of array-of-tables.");
}
}
BOOST_AUTO_TEST_CASE(test_discard_comment)
{
const std::string file = R"(

View File

@@ -70,9 +70,9 @@ struct from<extlib::foo>
template<>
struct into<extlib::foo>
{
static toml::table into_toml(const extlib::foo& f)
static toml::value into_toml(const extlib::foo& f)
{
return toml::table{{"a", f.a}, {"b", f.b}};
return toml::value{{"a", f.a}, {"b", f.b}};
}
};

View File

@@ -10,6 +10,7 @@
#include <map>
#include <iostream>
#include <fstream>
#include <sstream>
template<typename Comment,
template<typename ...> class Table,
@@ -303,3 +304,60 @@ BOOST_AUTO_TEST_CASE(test_format_key)
BOOST_TEST("\"special-chars-\\\\-\\\"-\\b-\\f-\\r-\\n-\\t\"" == toml::format_key(key));
}
}
// In toml11, an implicitly-defined value does not have any comments.
// So, in the following file,
// ```toml
// # comment
// [[array-of-tables]]
// foo = "bar"
// ```
// The array named "array-of-tables" does not have the comment, but the first
// element of the array has. That means that, the above file is equivalent to
// the following.
// ```toml
// array-of-tables = [
// # comment
// {foo = "bar"},
// ]
// ```
// If the array itself has a comment (value_has_comment_ == true), we should try
// to make it inline.
// ```toml
// # comment about array
// array-of-tables = [
// # comment about table element
// {foo = "bar"}
// ]
// ```
// If it is formatted as a multiline table, the two comments becomes
// indistinguishable.
// ```toml
// # comment about array
// # comment about table element
// [[array-of-tables]]
// foo = "bar"
// ```
// So we need to try to make it inline, and it force-inlines regardless
// of the line width limit.
// It may fail if the element of a table has comment. In that case,
// the array-of-tables will be formatted as a multiline table.
BOOST_AUTO_TEST_CASE(test_distinguish_comment)
{
const std::string str = R"(# comment about array itself
array_of_table = [
# comment about the first element (table)
{key = "value"},
])";
std::istringstream iss(str);
const auto data = toml::parse<toml::preserve_comments>(iss);
const auto serialized = toml::format(data, /*width = */ 0);
std::istringstream reparse(serialized);
const auto parsed = toml::parse<toml::preserve_comments>(reparse);
BOOST_TEST(parsed.at("array_of_table").comments().size() == 1u);
BOOST_TEST(parsed.at("array_of_table").comments().front() == " comment about array itself");
BOOST_TEST(parsed.at("array_of_table").at(0).comments().size() == 1u);
BOOST_TEST(parsed.at("array_of_table").at(0).comments().front() == " comment about the first element (table)");
}

View File

@@ -34,8 +34,8 @@
#endif
#define TOML11_VERSION_MAJOR 3
#define TOML11_VERSION_MINOR 5
#define TOML11_VERSION_PATCH 0
#define TOML11_VERSION_MINOR 6
#define TOML11_VERSION_PATCH 1
#include "toml/parser.hpp"
#include "toml/literal.hpp"

View File

@@ -4,6 +4,7 @@
#define TOML11_COMMENTS_HPP
#include <initializer_list>
#include <iterator>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>

View File

@@ -338,8 +338,10 @@ get(const basic_value<C, M, V>& v)
{v.location(), "here"}
}));
}
std::transform(ar.cbegin(), ar.cend(), container.begin(),
[](const value& x){return ::toml::get<value_type>(x);});
for(std::size_t i=0; i<ar.size(); ++i)
{
container[i] = ::toml::get<value_type>(ar[i]);
}
return container;
}

View File

@@ -12,8 +12,11 @@ inline namespace toml_literals
{
// implementation
inline ::toml::value literal_internal_impl(::toml::detail::location loc)
inline ::toml::basic_value<::toml::discard_comments, std::unordered_map, std::vector>
literal_internal_impl(::toml::detail::location loc)
{
using value_type = ::toml::basic_value<
::toml::discard_comments, std::unordered_map, std::vector>;
// if there are some comments or empty lines, skip them.
using skip_line = ::toml::detail::repeat<toml::detail::sequence<
::toml::detail::maybe<::toml::detail::lex_ws>,
@@ -50,7 +53,7 @@ inline ::toml::value literal_internal_impl(::toml::detail::location loc)
// If it is neither a table-key or a array-of-table-key, it may be a value.
if(!is_table_key && !is_aots_key)
{
if(auto data = ::toml::detail::parse_value<::toml::value>(loc))
if(auto data = ::toml::detail::parse_value<value_type>(loc))
{
return data.unwrap();
}
@@ -67,7 +70,7 @@ inline ::toml::value literal_internal_impl(::toml::detail::location loc)
// It is a valid toml file.
// It should be parsed as if we parse a file with this content.
if(auto data = ::toml::detail::parse_toml_file<::toml::value>(loc))
if(auto data = ::toml::detail::parse_toml_file<value_type>(loc))
{
return data.unwrap();
}
@@ -78,7 +81,8 @@ inline ::toml::value literal_internal_impl(::toml::detail::location loc)
}
inline ::toml::value operator"" _toml(const char* str, std::size_t len)
inline ::toml::basic_value<::toml::discard_comments, std::unordered_map, std::vector>
operator"" _toml(const char* str, std::size_t len)
{
::toml::detail::location loc(
std::string("TOML literal encoded in a C++ code"),
@@ -91,7 +95,8 @@ inline ::toml::value operator"" _toml(const char* str, std::size_t len)
#if defined(__cpp_char8_t) && __cpp_char8_t >= 201811L
// value of u8"" literal has been changed from char to char8_t and char8_t is
// NOT compatible to char
inline ::toml::value operator"" _toml(const char8_t* str, std::size_t len)
inline ::toml::basic_value<::toml::discard_comments, std::unordered_map, std::vector>
operator"" _toml(const char8_t* str, std::size_t len)
{
::toml::detail::location loc(
std::string("TOML literal encoded in a C++ code"),

View File

@@ -13,12 +13,14 @@
#include "types.hpp"
#include "value.hpp"
#if __cplusplus >= 201703L
#ifndef TOML11_DISABLE_STD_FILESYSTEM
#ifdef __cpp_lib_filesystem
#if __has_include(<filesystem>)
#define TOML11_HAS_STD_FILESYSTEM
#include <filesystem>
#endif // has_include(<string_view>)
#endif // cplusplus >= C++17
#endif // __cpp_lib_filesystem
#endif // TOML11_DISABLE_STD_FILESYSTEM
namespace toml
{
@@ -930,7 +932,7 @@ parse_key(location& loc)
return ok(std::make_pair(std::vector<key>(1, smpl.unwrap().first),
smpl.unwrap().second));
}
return err(format_underline("toml::parse_key: an invalid key appeaed.",
return err(format_underline("toml::parse_key: an invalid key appeared.",
{{source_location(loc), "is not a valid key"}}, {
"bare keys : non-empty strings composed only of [A-Za-z0-9_-].",
"quoted keys: same as \"basic strings\" or 'literal strings'.",
@@ -1315,7 +1317,41 @@ insert_nested_key(typename Value::table_type& root, const Value& v,
}
else // if not, we need to create the array of table
{
value_type aot(array_type(1, v), key_reg);
// XXX: Consider the following array of tables.
// ```toml
// # This is a comment.
// [[aot]]
// foo = "bar"
// ```
// Here, the comment is for `aot`. But here, actually two
// values are defined. An array that contains tables, named
// `aot`, and the 0th element of the `aot`, `{foo = "bar"}`.
// Those two are different from each other. But both of them
// points to the same portion of the TOML file, `[[aot]]`,
// so `key_reg.comments()` returns `# This is a comment`.
// If it is assigned as a comment of `aot` defined here, the
// comment will be duplicated. Both the `aot` itself and
// the 0-th element will have the same comment. This causes
// "duplication of the same comments" bug when the data is
// serialized.
// Next, consider the following.
// ```toml
// # comment 1
// aot = [
// # coment 2
// {foo = "bar"},
// ]
// ```
// In this case, we can distinguish those two comments. So
// here we need to add "comment 1" to the `aot` and
// "comment 2" to the 0th element of that.
// To distinguish those two, we check the key region.
std::vector<std::string> comments{/* empty by default */};
if(key_reg.str().substr(0, 2) != "[[")
{
comments = key_reg.comments();
}
value_type aot(array_type(1, v), key_reg, std::move(comments));
tab->insert(std::make_pair(k, aot));
return ok(true);
}
@@ -1384,7 +1420,8 @@ insert_nested_key(typename Value::table_type& root, const Value& v,
// [x.y.z]
if(tab->count(k) == 0)
{
(*tab)[k] = value_type(table_type{}, key_reg);
// a table that is defined implicitly doesn't have any comments.
(*tab)[k] = value_type(table_type{}, key_reg, {/*no comment*/});
}
// type checking...
@@ -1674,11 +1711,24 @@ inline result<value_t, std::string> guess_value_type(const location& loc)
}
}
template<typename Value, typename T>
result<Value, std::string>
parse_value_helper(result<std::pair<T, region>, std::string> rslt)
{
if(rslt.is_ok())
{
auto comments = rslt.as_ok().second.comments();
return ok(Value(std::move(rslt.as_ok()), std::move(comments)));
}
else
{
return err(std::move(rslt.as_err()));
}
}
template<typename Value>
result<Value, std::string> parse_value(location& loc)
{
using value_type = Value;
const auto first = loc.iter();
if(first == loc.end())
{
@@ -1691,18 +1741,19 @@ result<Value, std::string> parse_value(location& loc)
{
return err(type.unwrap_err());
}
switch(type.unwrap())
{
case value_t::boolean : {return parse_boolean(loc); }
case value_t::integer : {return parse_integer(loc); }
case value_t::floating : {return parse_floating(loc); }
case value_t::string : {return parse_string(loc); }
case value_t::offset_datetime: {return parse_offset_datetime(loc);}
case value_t::local_datetime : {return parse_local_datetime(loc); }
case value_t::local_date : {return parse_local_date(loc); }
case value_t::local_time : {return parse_local_time(loc); }
case value_t::array : {return parse_array<value_type>(loc); }
case value_t::table : {return parse_inline_table<value_type>(loc);}
case value_t::boolean : {return parse_value_helper<Value>(parse_boolean(loc) );}
case value_t::integer : {return parse_value_helper<Value>(parse_integer(loc) );}
case value_t::floating : {return parse_value_helper<Value>(parse_floating(loc) );}
case value_t::string : {return parse_value_helper<Value>(parse_string(loc) );}
case value_t::offset_datetime: {return parse_value_helper<Value>(parse_offset_datetime(loc) );}
case value_t::local_datetime : {return parse_value_helper<Value>(parse_local_datetime(loc) );}
case value_t::local_date : {return parse_value_helper<Value>(parse_local_date(loc) );}
case value_t::local_time : {return parse_value_helper<Value>(parse_local_time(loc) );}
case value_t::array : {return parse_value_helper<Value>(parse_array<Value>(loc) );}
case value_t::table : {return parse_value_helper<Value>(parse_inline_table<Value>(loc));}
default:
{
const auto msg = format_underline("toml::parse_value: "
@@ -1993,7 +2044,7 @@ result<Value, std::string> parse_toml_file(location& loc)
const auto& reg = tk.second;
const auto inserted = insert_nested_key(data,
value_type(tab.unwrap(), reg),
value_type(tab.unwrap(), reg, reg.comments()),
keys.begin(), keys.end(), reg,
/*is_array_of_table=*/ true);
if(!inserted) {return err(inserted.unwrap_err());}
@@ -2010,7 +2061,8 @@ result<Value, std::string> parse_toml_file(location& loc)
const auto& reg = tk.second;
const auto inserted = insert_nested_key(data,
value_type(tab.unwrap(), reg), keys.begin(), keys.end(), reg);
value_type(tab.unwrap(), reg, reg.comments()),
keys.begin(), keys.end(), reg);
if(!inserted) {return err(inserted.unwrap_err());}
continue;
@@ -2019,10 +2071,7 @@ result<Value, std::string> parse_toml_file(location& loc)
"unknown line appeared", {{source_location(loc), "unknown format"}}));
}
Value v(std::move(data), file);
v.comments() = comments;
return ok(std::move(v));
return ok(Value(std::move(data), file, comments));
}
} // detail

View File

@@ -344,7 +344,7 @@ struct region final : public region_base
{
// unwrap the first '#' by std::next.
auto str = make_string(std::next(comment_found), iter);
if(str.back() == '\r') {str.pop_back();}
if(!str.empty() && str.back() == '\r') {str.pop_back();}
com.push_back(std::move(str));
}
else
@@ -397,7 +397,7 @@ struct region final : public region_base
{
// unwrap the first '#' by std::next.
auto str = make_string(std::next(comment_found), this->line_end());
if(str.back() == '\r') {str.pop_back();}
if(!str.empty() && str.back() == '\r') {str.pop_back();}
com.push_back(std::move(str));
}
}

View File

@@ -97,8 +97,10 @@ struct serializer
const int float_prec = std::numeric_limits<toml::floating>::max_digits10,
const bool can_be_inlined = false,
const bool no_comment = false,
std::vector<toml::key> ks = {})
std::vector<toml::key> ks = {},
const bool value_has_comment = false)
: can_be_inlined_(can_be_inlined), no_comment_(no_comment),
value_has_comment_(value_has_comment && !no_comment),
float_prec_(float_prec), width_(w), keys_(std::move(ks))
{}
~serializer() = default;
@@ -120,7 +122,7 @@ struct serializer
std::snprintf(buf.data(), buf.size(), fmt, this->float_prec_, f);
std::string token(buf.begin(), std::prev(buf.end()));
if(token.back() == '.') // 1. => 1.0
if(!token.empty() && token.back() == '.') // 1. => 1.0
{
token += '0';
}
@@ -244,92 +246,18 @@ struct serializer
std::string operator()(const array_type& v) const
{
if(!v.empty() && v.front().is_table())// v is an array of tables
{
// if it's not inlined, we need to add `[[table.key]]`.
// but if it can be inlined,
// ```
// table.key = [
// {...},
// # comment
// {...},
// ]
// ```
if(this->can_be_inlined_)
{
std::string token;
if(!keys_.empty())
{
token += format_key(keys_.back());
token += " = ";
}
bool failed = false;
token += "[\n";
for(const auto& item : v)
{
// if an element of the table has a comment, the table
// cannot be inlined.
if(this->has_comment_inside(item.as_table()))
{
failed = true;
break;
}
if(!no_comment_)
{
for(const auto& c : item.comments())
{
token += '#';
token += c;
token += '\n';
}
}
const auto t = this->make_inline_table(item.as_table());
if(t.size() + 1 > width_ || // +1 for the last comma {...},
std::find(t.cbegin(), t.cend(), '\n') != t.cend())
{
failed = true;
break;
}
token += t;
token += ",\n";
}
if(!failed)
{
token += "]\n";
return token;
}
// if failed, serialize them as [[array.of.tables]].
}
std::string token;
for(const auto& item : v)
{
if(!no_comment_)
{
for(const auto& c : item.comments())
{
token += '#';
token += c;
token += '\n';
}
}
token += "[[";
token += format_keys(keys_);
token += "]]\n";
token += this->make_multiline_table(item.as_table());
}
return token;
}
if(v.empty())
{
return std::string("[]");
}
if(this->is_array_of_tables(v))
{
return make_array_of_tables(v);
}
// not an array of tables. normal array.
// first, try to make it inline if none of the elements have a comment.
if(!this->has_comment_inside(v))
if( ! this->has_comment_inside(v))
{
const auto inl = this->make_inline_array(v);
if(inl.size() < this->width_ &&
@@ -350,7 +278,7 @@ struct serializer
token += "[\n";
for(const auto& item : v)
{
if(!item.comments().empty() && !no_comment_)
if( ! item.comments().empty() && !no_comment_)
{
// if comment exists, the element must be the only element in the line.
// e.g. the following is not allowed.
@@ -376,7 +304,7 @@ struct serializer
token += '\n';
}
token += toml::visit(*this, item);
if(token.back() == '\n') {token.pop_back();}
if(!token.empty() && token.back() == '\n') {token.pop_back();}
token += ",\n";
continue;
}
@@ -384,7 +312,7 @@ struct serializer
next_elem += toml::visit(*this, item);
// comma before newline.
if(next_elem.back() == '\n') {next_elem.pop_back();}
if(!next_elem.empty() && next_elem.back() == '\n') {next_elem.pop_back();}
// if current line does not exceeds the width limit, continue.
if(current_line.size() + next_elem.size() + 1 < this->width_)
@@ -411,7 +339,10 @@ struct serializer
}
if(!current_line.empty())
{
if(current_line.back() != '\n') {current_line += '\n';}
if(!current_line.empty() && current_line.back() != '\n')
{
current_line += '\n';
}
token += current_line;
}
token += "]\n";
@@ -557,8 +488,10 @@ struct serializer
for(const auto& item : v)
{
if(is_first) {is_first = false;} else {token += ',';}
token += visit(serializer((std::numeric_limits<std::size_t>::max)(),
this->float_prec_, true), item);
token += visit(serializer(
(std::numeric_limits<std::size_t>::max)(), this->float_prec_,
/* inlined */ true, /*no comment*/ false, /*keys*/ {},
/*has_comment*/ !item.comments().empty()), item);
}
token += ']';
return token;
@@ -577,8 +510,10 @@ struct serializer
if(is_first) {is_first = false;} else {token += ',';}
token += format_key(kv.first);
token += '=';
token += visit(serializer((std::numeric_limits<std::size_t>::max)(),
this->float_prec_, true), kv.second);
token += visit(serializer(
(std::numeric_limits<std::size_t>::max)(), this->float_prec_,
/* inlined */ true, /*no comment*/ false, /*keys*/ {},
/*has_comment*/ !kv.second.comments().empty()), kv.second);
}
token += '}';
return token;
@@ -588,8 +523,16 @@ struct serializer
{
std::string token;
// print non-table stuff first. because after printing [foo.bar], the
// remaining non-table values will be assigned into [foo.bar], not [foo]
// print non-table elements first.
// ```toml
// [foo] # a table we're writing now here
// key = "value" # <- non-table element, "key"
// # ...
// [foo.bar] # <- table element, "bar"
// ```
// because after printing [foo.bar], the remaining non-table values will
// be assigned into [foo.bar], not [foo]. Those values should be printed
// earlier.
for(const auto& kv : v)
{
if(kv.second.is_table() || is_array_of_tables(kv.second))
@@ -597,21 +540,16 @@ struct serializer
continue;
}
if(!kv.second.comments().empty() && !no_comment_)
{
for(const auto& c : kv.second.comments())
{
token += '#';
token += c;
token += '\n';
}
}
token += write_comments(kv.second);
const auto key_and_sep = format_key(kv.first) + " = ";
const auto residual_width = (this->width_ > key_and_sep.size()) ?
this->width_ - key_and_sep.size() : 0;
token += key_and_sep;
token += visit(serializer(residual_width, this->float_prec_, true),
kv.second);
token += visit(serializer(residual_width, this->float_prec_,
/*can be inlined*/ true, /*no comment*/ false, /*keys*/ {},
/*has_comment*/ !kv.second.comments().empty()), kv.second);
if(token.back() != '\n')
{
token += '\n';
@@ -637,8 +575,8 @@ struct serializer
ks.push_back(kv.first);
auto tmp = visit(serializer(this->width_, this->float_prec_,
!multiline_table_printed, this->no_comment_, ks),
kv.second);
!multiline_table_printed, this->no_comment_, ks,
/*has_comment*/ !kv.second.comments().empty()), kv.second);
if((!multiline_table_printed) &&
std::find(tmp.cbegin(), tmp.cend(), '\n') != tmp.cend())
@@ -651,31 +589,142 @@ struct serializer
tmp += '\n';
}
if(!kv.second.comments().empty() && !no_comment_)
{
for(const auto& c : kv.second.comments())
{
token += '#';
token += c;
token += '\n';
}
}
token += write_comments(kv.second);
token += tmp;
}
return token;
}
std::string make_array_of_tables(const array_type& v) const
{
// if it's not inlined, we need to add `[[table.key]]`.
// but if it can be inlined, we can format it as the following.
// ```
// table.key = [
// {...},
// # comment
// {...},
// ]
// ```
// This function checks if inlinization is possible or not, and then
// format the array-of-tables in a proper way.
//
// Note about comments:
//
// If the array itself has a comment (value_has_comment_ == true), we
// should try to make it inline.
// ```toml
// # comment about array
// array = [
// # comment about table element
// {of = "table"}
// ]
// ```
// If it is formatted as a multiline table, the two comments becomes
// indistinguishable.
// ```toml
// # comment about array
// # comment about table element
// [[array]]
// of = "table"
// ```
// So we need to try to make it inline, and it force-inlines regardless
// of the line width limit.
// It may fail if the element of a table has comment. In that case,
// the array-of-tables will be formatted as a multiline table.
if(this->can_be_inlined_ || this->value_has_comment_)
{
std::string token;
if(!keys_.empty())
{
token += format_key(keys_.back());
token += " = ";
}
bool failed = false;
token += "[\n";
for(const auto& item : v)
{
// if an element of the table has a comment, the table
// cannot be inlined.
if(this->has_comment_inside(item.as_table()))
{
failed = true;
break;
}
// write comments for the table itself
token += write_comments(item);
const auto t = this->make_inline_table(item.as_table());
if(t.size() + 1 > width_ || // +1 for the last comma {...},
std::find(t.cbegin(), t.cend(), '\n') != t.cend())
{
// if the value itself has a comment, ignore the line width limit
if( ! this->value_has_comment_)
{
failed = true;
break;
}
}
token += t;
token += ",\n";
}
if( ! failed)
{
token += "]\n";
return token;
}
// if failed, serialize them as [[array.of.tables]].
}
std::string token;
for(const auto& item : v)
{
token += write_comments(item);
token += "[[";
token += format_keys(keys_);
token += "]]\n";
token += this->make_multiline_table(item.as_table());
}
return token;
}
std::string write_comments(const value_type& v) const
{
std::string retval;
if(this->no_comment_) {return retval;}
for(const auto& c : v.comments())
{
retval += '#';
retval += c;
retval += '\n';
}
return retval;
}
bool is_array_of_tables(const value_type& v) const
{
if(!v.is_array()) {return false;}
const auto& a = v.as_array();
return !a.empty() && a.front().is_table();
if(!v.is_array() || v.as_array().empty()) {return false;}
return is_array_of_tables(v.as_array());
}
bool is_array_of_tables(const array_type& v) const
{
// Since TOML v0.5.0, heterogeneous arrays are allowed. So we need to
// check all the element in an array to check if the array is an array
// of tables.
return std::all_of(v.begin(), v.end(), [](const value_type& elem) {
return elem.is_table();
});
}
private:
bool can_be_inlined_;
bool no_comment_;
bool value_has_comment_;
int float_prec_;
std::size_t width_;
std::vector<toml::key> keys_;

View File

@@ -3,6 +3,7 @@
#ifndef TOML11_SOURCE_LOCATION_HPP
#define TOML11_SOURCE_LOCATION_HPP
#include <cstdint>
#include <sstream>
#include "region.hpp"

View File

@@ -1047,10 +1047,10 @@ class basic_value
//
// Those constructors take detail::region that contains parse result.
basic_value(boolean b, detail::region reg)
basic_value(boolean b, detail::region reg, std::vector<std::string> cm)
: type_(value_t::boolean),
region_info_(std::make_shared<detail::region>(std::move(reg))),
comments_(region_info_->comments())
comments_(std::move(cm))
{
assigner(this->boolean_, b);
}
@@ -1058,68 +1058,75 @@ class basic_value
detail::conjunction<
std::is_integral<T>, detail::negation<std::is_same<T, boolean>>
>::value, std::nullptr_t>::type = nullptr>
basic_value(T i, detail::region reg)
basic_value(T i, detail::region reg, std::vector<std::string> cm)
: type_(value_t::integer),
region_info_(std::make_shared<detail::region>(std::move(reg))),
comments_(region_info_->comments())
comments_(std::move(cm))
{
assigner(this->integer_, static_cast<integer>(i));
}
template<typename T, typename std::enable_if<
std::is_floating_point<T>::value, std::nullptr_t>::type = nullptr>
basic_value(T f, detail::region reg)
basic_value(T f, detail::region reg, std::vector<std::string> cm)
: type_(value_t::floating),
region_info_(std::make_shared<detail::region>(std::move(reg))),
comments_(region_info_->comments())
comments_(std::move(cm))
{
assigner(this->floating_, static_cast<floating>(f));
}
basic_value(toml::string s, detail::region reg)
basic_value(toml::string s, detail::region reg,
std::vector<std::string> cm)
: type_(value_t::string),
region_info_(std::make_shared<detail::region>(std::move(reg))),
comments_(region_info_->comments())
comments_(std::move(cm))
{
assigner(this->string_, std::move(s));
}
basic_value(const local_date& ld, detail::region reg)
basic_value(const local_date& ld, detail::region reg,
std::vector<std::string> cm)
: type_(value_t::local_date),
region_info_(std::make_shared<detail::region>(std::move(reg))),
comments_(region_info_->comments())
comments_(std::move(cm))
{
assigner(this->local_date_, ld);
}
basic_value(const local_time& lt, detail::region reg)
basic_value(const local_time& lt, detail::region reg,
std::vector<std::string> cm)
: type_(value_t::local_time),
region_info_(std::make_shared<detail::region>(std::move(reg))),
comments_(region_info_->comments())
comments_(std::move(cm))
{
assigner(this->local_time_, lt);
}
basic_value(const local_datetime& ldt, detail::region reg)
basic_value(const local_datetime& ldt, detail::region reg,
std::vector<std::string> cm)
: type_(value_t::local_datetime),
region_info_(std::make_shared<detail::region>(std::move(reg))),
comments_(region_info_->comments())
comments_(std::move(cm))
{
assigner(this->local_datetime_, ldt);
}
basic_value(const offset_datetime& odt, detail::region reg)
basic_value(const offset_datetime& odt, detail::region reg,
std::vector<std::string> cm)
: type_(value_t::offset_datetime),
region_info_(std::make_shared<detail::region>(std::move(reg))),
comments_(region_info_->comments())
comments_(std::move(cm))
{
assigner(this->offset_datetime_, odt);
}
basic_value(const array_type& ary, detail::region reg)
basic_value(const array_type& ary, detail::region reg,
std::vector<std::string> cm)
: type_(value_t::array),
region_info_(std::make_shared<detail::region>(std::move(reg))),
comments_(region_info_->comments())
comments_(std::move(cm))
{
assigner(this->array_, ary);
}
basic_value(const table_type& tab, detail::region reg)
basic_value(const table_type& tab, detail::region reg,
std::vector<std::string> cm)
: type_(value_t::table),
region_info_(std::make_shared<detail::region>(std::move(reg))),
comments_(region_info_->comments())
comments_(std::move(cm))
{
assigner(this->table_, tab);
}
@@ -1127,8 +1134,10 @@ class basic_value
template<typename T, typename std::enable_if<
detail::is_exact_toml_type<T, value_type>::value,
std::nullptr_t>::type = nullptr>
basic_value(std::pair<T, detail::region> parse_result)
: basic_value(std::move(parse_result.first), std::move(parse_result.second))
basic_value(std::pair<T, detail::region> parse_result, std::vector<std::string> comments)
: basic_value(std::move(parse_result.first),
std::move(parse_result.second),
std::move(comments))
{}
// type checking and casting ============================================