Compare commits

...

57 Commits

Author SHA1 Message Date
ToruNiina
d8707d5867 chore: fix README 2019-02-17 00:22:19 +09:00
ToruNiina
2dd0a78c52 fix: reset stream width before printing
without this, the first line of the serialized result becomes too wide
2019-02-16 23:55:19 +09:00
Toru Niina
d7b8c3c78f Merge pull request #18 from ToruNiina/threadsafe-localtime
add threadsafe localtime_(s|r)
2019-02-16 23:28:48 +09:00
Toru Niina
2f0148a2df Merge pull request #26 from ToruNiina/serialize
add serializer
2019-02-16 23:27:05 +09:00
ToruNiina
4accc29984 chore: update README 2019-02-14 16:47:15 +09:00
ToruNiina
19b9af2494 Merge branch 'master' into serialize 2019-02-14 16:34:45 +09:00
ToruNiina
0aa50e9439 style: just add newlines to README 2019-02-14 16:26:48 +09:00
ToruNiina
a00a906482 fix: add comma at correct position 2019-02-14 16:17:32 +09:00
ToruNiina
19ad7d7c96 fix: remove needless empty line from serialization 2019-02-14 16:17:04 +09:00
ToruNiina
251e55da42 fix: don't ignore std::setw(0) 2019-02-14 15:49:27 +09:00
ToruNiina
32f1b2060a fix: avoid width overflow 2019-02-14 15:49:13 +09:00
ToruNiina
b1c54532df feat: improve array serialization
- make multiline array more clean
- short-circuit for empty array
2019-02-14 15:48:05 +09:00
ToruNiina
38c67f16e8 fix: initialize float precition correctly 2019-02-14 15:47:00 +09:00
ToruNiina
24aefc52a1 test: set width in test_serialize 2019-02-14 15:46:12 +09:00
ToruNiina
ba8c205253 fix: change CRLF into LF before comparison 2019-02-13 23:48:53 +09:00
ToruNiina
31193d99ba Merge branch 'master' into serialize 2019-02-13 23:16:39 +09:00
ToruNiina
c4aecc8e4b chore: update README badges 2019-02-13 22:36:29 +09:00
Toru Niina
60c81d06a0 Merge pull request #25 from ToruNiina/hotfix
fix: open file as binary-mode #16
2019-02-13 21:14:15 +09:00
ToruNiina
46569da231 fix: avoid auto-conversion while making test case 2019-02-13 19:51:54 +09:00
ToruNiina
5e20a8ff16 fix: add scope to the test case to flush 2019-02-13 19:26:52 +09:00
ToruNiina
dd9319245e fix: open file as binary-mode #16
to avoid inconsistency between file size (obtained by tellg) and the
size of the actual contents that would be read later
2019-02-13 19:18:09 +09:00
ToruNiina
4bbe42d105 test: add test_serialize_file 2019-02-13 13:51:36 +09:00
ToruNiina
5bdc022627 fix: correctly serialize quoted keys 2019-02-13 13:51:08 +09:00
ToruNiina
41e354f1ee supress warnings while skipping switch-cases 2019-02-13 13:50:33 +09:00
ToruNiina
d1c76709b0 add serializer #23 2019-02-13 13:37:58 +09:00
ToruNiina
64774a8db0 add toml::visit to use it in serializer 2019-02-13 13:36:55 +09:00
ToruNiina
53f6b8268b fix: compare offset_datetime correctly 2019-02-13 13:34:26 +09:00
ToruNiina
32dcc35918 move return_type_of_t from result to traits 2019-02-13 13:34:03 +09:00
ToruNiina
8c3854b28b update README 2019-01-31 15:37:25 +09:00
Toru Niina
75af9c79df Merge pull request #22 from xaxousis/master
Fix multiple definition error
2019-01-31 01:34:33 +09:00
Quentin Khan
1dfe32acd8 Fix multiple definition error 2019-01-30 17:06:23 +01:00
Toru Niina
5dfdbe4bff Merge pull request #20 from ToruNiina/format-error
add an extra parameter `hints` to format_error
2018-12-27 20:34:53 +09:00
Toru Niina
4584eeb57a Merge pull request #19 from ToruNiina/find-default-type
add default template arg to toml::find
2018-12-27 20:34:36 +09:00
ToruNiina
aa67069387 move hints to the internal function 2018-12-27 16:32:20 +09:00
ToruNiina
ee3424ad51 add an extra parameter hints to format_error 2018-12-27 16:26:23 +09:00
ToruNiina
17def14ab6 add default template arg to toml::find
in most of the use cases, toml::value is used (to show error message).
2018-12-27 15:58:50 +09:00
ToruNiina
51dd3abcae remove one branch by preprocessor
since localtime in windows is already thread-safe, there are no need to
change the function.
2018-12-26 13:38:01 +09:00
ToruNiina
825b2c30a1 add threadsafe localtime_(s|r) 2018-12-25 22:40:52 +09:00
Toru Niina
b5b8830c29 Merge pull request #17 from ToruNiina/hotfix
fix the error with BOM and end of file w/o newline
2018-12-24 16:37:10 +09:00
ToruNiina
87a5c844c2 add test cases for the end-of-file problems 2018-12-24 16:02:32 +09:00
ToruNiina
11c7ee4501 fix the case of file w/o newline at the end
toml::parse failed with the file that contains whitespace or comment at
the end of file without newline. this commit fixes the error.
2018-12-24 16:00:33 +09:00
ToruNiina
d24a188d4c fix the error while reading BOM.
remove possible UB because of the use-after-move.
2018-12-24 15:06:26 +09:00
Toru Niina
29876221f8 Merge pull request #15 from ToruNiina/performance
speedup by removing needless format_underline
2018-12-23 18:30:19 +09:00
ToruNiina
7c03c446fe speedup by removing needless format_underline
drastical speedup for long toml files
2018-12-23 15:22:12 +09:00
Toru Niina
cfdd4d4a90 Merge pull request #14 from ToruNiina/error-message
improve error message quality
2018-12-22 18:46:00 +09:00
ToruNiina
5546b3389d Merge branch 'master' into error-message 2018-12-22 17:55:59 +09:00
ToruNiina
9c95992dad fix error message for empty value 2018-12-22 17:44:09 +09:00
ToruNiina
edb48b2872 add test_error_detection to check it detects error 2018-12-22 17:43:42 +09:00
ToruNiina
c63ac7e435 detect syntax_error; appending array-of-tables
toml file like the following is explicitly prohibited.
a = [{b = 1}]
[[a]]
b = 2
this commit detects this kind of syntax-error while parsing toml file
2018-12-22 17:07:06 +09:00
ToruNiina
fec49aaaa3 fix error message: add missing spaces 2018-12-22 17:06:36 +09:00
ToruNiina
617187969c fix result::unwrap_or with rvalue ref; merge branch 'hotfix' 2018-12-17 23:54:17 +09:00
ToruNiina
e3217cd572 quit returning rvalue ref from unwrap_or 2018-12-17 23:17:45 +09:00
ToruNiina
4d02f399a2 add temporary to receive rvalue 2018-12-17 23:03:53 +09:00
ToruNiina
24723226f1 remove template argument from result::unwrap_or 2018-12-17 19:18:16 +09:00
ToruNiina
7b3684b54e add and_other and or_other to toml::result
effectively same as Rust's std::Result::and and or.
2018-12-17 18:24:41 +09:00
ToruNiina
13c1f9c259 output filename of the second value2 if different
in format_error.
2018-12-17 18:07:57 +09:00
ToruNiina
6df75ad28e fix README 2018-12-17 16:56:09 +09:00
18 changed files with 1811 additions and 241 deletions

View File

@@ -1,59 +0,0 @@
### encoding user's data
You can encode your data to toml format.
```cpp
const toml::value integer(1);
const toml::value array{3.1, 3.14, 3.141, 3.1415};
const toml::value table{{"answer", 42}, {"pi", 3.14}, {"string", "foobar"}};
std::cout << toml::format("integer", integer) << std::endl;
std::cout << toml::format("array", array) << std::endl;
std::cout << toml::format("table", table) << std::endl;
```
this program will output as below.
```toml
integer = 1
array = [3.1, 3.14, 3.141, 3.1415]
[table]
answer = 42
pi = 3.14
string = "foobar"
```
Without key name, you can make string formatted as toml.
```cpp
const std::string integer_ = toml::format(integer); // "1"
const std::string array_ = toml::format(array); // "[3.1, 3.14, 3.141, 3.1415]"
const std::string table_ = toml::format(table); // "answer = 42\npi=3.14\nstring=foobar"
```
### inlinize
You can make `toml::Table` inline.
```cpp
const toml::value table{{"answer", 42}, {"pi", 3.14}, {"string", "foobar"}};
// if the inline-table format length is less than 80, the table will be inlined
std::cout << toml::format("table", table, toml::make_inline(80)) << std::endl;
// In any case, the table will be inlined.
std::cout << toml::format("table", table, toml::forceinline) << std::endl;
```
```toml
table = {answer = 42, pi = 3.14, string = "foobar"}
```
And there are some stream manipulators for toml format.
```cpp
const toml::value table{{"answer", 42}, {"pi", 3.14}, {"string", "foobar"}};
// if the inline-table format length is less than 80, the table will be inlined
std::cout << toml::make_inline(80) << table << std::endl;
// In any case, the table will be inlined.
std::cout << toml::forceinline << table << std::endl;
```

223
README.md
View File

@@ -3,14 +3,19 @@ toml11
[![Build Status](https://travis-ci.org/ToruNiina/toml11.svg?branch=master)](https://travis-ci.org/ToruNiina/toml11) [![Build Status](https://travis-ci.org/ToruNiina/toml11.svg?branch=master)](https://travis-ci.org/ToruNiina/toml11)
[![Build status](https://ci.appveyor.com/api/projects/status/m2n08a926asvg5mg?svg=true)](https://ci.appveyor.com/project/ToruNiina/toml11) [![Build status](https://ci.appveyor.com/api/projects/status/m2n08a926asvg5mg?svg=true)](https://ci.appveyor.com/project/ToruNiina/toml11)
[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) [![Version](https://img.shields.io/github/release/ToruNiina/toml11.svg?style=flat)](https://github.com/ToruNiina/toml11/releases)
[![License](https://img.shields.io/github/license/ToruNiina/toml11.svg?style=flat)](LICENSE)
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.1209136.svg)](https://doi.org/10.5281/zenodo.1209136) [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.1209136.svg)](https://doi.org/10.5281/zenodo.1209136)
c++11 header-only toml parser depending only on c++ standard library. C++11 header-only toml parser depending only on C++ standard library.
compatible to the latest version of [TOML v0.5.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.5.0.md) after version 2.0.0. compatible to the latest version of
[TOML v0.5.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.5.0.md)
after version 2.0.0.
Are you looking for pre-C++11 compatible toml parser? Try [Boost.toml](https://github.com/ToruNiina/Boost.toml)! It has almost the same functionality as this library and works with C++98 & Boost. Are you looking for pre-C++11 compatible toml parser?
Try [Boost.toml](https://github.com/ToruNiina/Boost.toml)!
It has almost the same functionality as this library and works with C++98 & Boost.
## How to use ## How to use
@@ -42,19 +47,28 @@ In the case of file open error, it will throw `std::runtime_error`.
You can also pass a `stream` to the `toml::parse` function after checking the status. You can also pass a `stream` to the `toml::parse` function after checking the status.
Note that on __Windows OS__, stream that is opened as text-mode automatically converts
CRLF ("\r\n") into LF ("\n") and this leads inconsistency between file size and
the contents that would be read. This causes weird error. To use a file stream
with `toml::parse`, don't forget to pass binary mode flag when you open the
stream.
```cpp ```cpp
std::ifstream ifs("sample.toml"); std::ifstream ifs("sample.toml", std::ios_base::binary);
assert(ifs.good()); assert(ifs.good());
const auto data = toml::parse(ifs /*, "filename" (optional)*/); const auto data = toml::parse(ifs /*, "filename" (optional)*/);
``` ```
To show a better error message, it is recommended to pass a filename with `istream`. See also [in the case of syntax error](#in-the-case-of-syntax-error) and [passing invalid type to toml::get](#passing-invalid-type-to-tomlget). To show a better error message, it is recommended to pass a filename with `istream`.
See also [in the case of syntax error](#in-the-case-of-syntax-error)
and [passing invalid type to toml::get](#passing-invalid-type-to-tomlget).
### In the case of syntax error ### In the case of syntax error
If there is a syntax error in a toml file, `toml::parse` will throw `toml::syntax_error`. If there is a syntax error in a toml file, `toml::parse` will throw `toml::syntax_error`.
toml11 now has clean and informative error messages inspired by Rust and it looks like the following (comment after hash sign are actually not shown). toml11 now has clean and informative error messages inspired by Rust and
it looks like the following (comment after hash sign are actually not shown).
```console ```console
terminate called after throwing an instance of 'toml::syntax_error' terminate called after throwing an instance of 'toml::syntax_error'
@@ -64,7 +78,8 @@ terminate called after throwing an instance of 'toml::syntax_error'
| ^------ expected newline, but got '='. # error reason | ^------ expected newline, but got '='. # error reason
``` ```
If you (mistakenly) duplicate tables and got an error, you may want to see where the other is. toml11 shows both at the same time. If you (mistakenly) duplicate tables and got an error, you may want to see
where the other is. toml11 shows both at the same time.
```console ```console
terminate called after throwing an instance of 'toml::syntax_error' terminate called after throwing an instance of 'toml::syntax_error'
@@ -77,11 +92,14 @@ terminate called after throwing an instance of 'toml::syntax_error'
| ~~~~~~~ table defined twice | ~~~~~~~ table defined twice
``` ```
Since the error message generation is generally a difficult task, the current status is not ideal. toml11 needs your help. If you encounter a weird error message, please let us know and contribute to improve the quality! Since the error message generation is generally a difficult task, the current
status is not ideal. toml11 needs your help. If you encounter a weird error message,
please let us know and contribute to improve the quality!
### Getting a toml value ### Getting a toml value
After parsing successfully, you can obtain the values from the result of `toml::parse` (here, `data`) using `toml::get` function. After parsing successfully, you can obtain the values from the result of
`toml::parse` (here, `data`) using `toml::get` function.
```toml ```toml
answer = 42 answer = 42
@@ -101,18 +119,22 @@ const auto tab = toml::get<toml::Table>(data.at("tab"));
const auto key = toml::get<std::string>( tab.at("key")); const auto key = toml::get<std::string>( tab.at("key"));
``` ```
When you pass an exact TOML type that does not require type conversion, `toml::get` returns also a reference through which you can modify the content. When you pass an exact TOML type that does not require type conversion,
`toml::get` returns also a reference through which you can modify the content.
```cpp ```cpp
toml::get<toml::integer>(data["answer"]) = 6 * 9; toml::get<toml::integer>(data["answer"]) = 6 * 9;
std::cout << toml::get<int>(data.at("answer")) << std::endl; // 54 std::cout << toml::get<int>(data.at("answer")) << std::endl; // 54
``` ```
If the specified type requires conversion, you can't take a reference to the value. See also [underlying types](#underlying-types). If the specified type requires conversion, you can't take a reference to the value.
See also [underlying types](#underlying-types).
#### Passing invalid type to toml::get #### Passing invalid type to toml::get
If you choose the invalid type, `toml::type_error` will be thrown. Similar to the `syntax_error`, toml11 also displays informative error messages. The error message when you choose `int` to get `string` value would be like this. If you choose the invalid type, `toml::type_error` will be thrown.
Similar to the `syntax_error`, toml11 also displays informative error messages.
The error message when you choose `int` to get `string` value would be like this.
```console ```console
terminate called after throwing an instance of 'toml::type_error' terminate called after throwing an instance of 'toml::type_error'
@@ -122,7 +144,9 @@ terminate called after throwing an instance of 'toml::type_error'
| ~~~~~~~~~~~~~~ the actual type is string | ~~~~~~~~~~~~~~ the actual type is string
``` ```
NOTE: In order to show this kind of error message, all the toml values have 1 shared_ptr that points the corresponding byte sequence and 2 iterators that point the range. It is recommended to destruct all the `toml::value` classes after configuring your application to save memory resources. NOTE: In order to show this kind of error message, all the toml values have 1
`shared_ptr` that points the corresponding byte sequence and 2 iterators that point the range.
It is recommended to destruct all the `toml::value` classes after configuring your application to save memory resources.
### Getting arrays ### Getting arrays
@@ -157,20 +181,25 @@ const auto aofa = toml::get<
>(data.at("aofa")); >(data.at("aofa"));
``` ```
If you don't know what the type is inside the array, you can use `toml::array`, which is a `std::vector` of `toml::value`, instead. If you don't know what the type is inside the array, you can use `toml::array`,
which is a `std::vector` of `toml::value`, instead.
```cpp ```cpp
const auto aofa = toml::get<toml::array>(data.at("aofa")); const auto aofa = toml::get<toml::array>(data.at("aofa"));
const auto first = toml::get<toml::array>(aofa.at(0)); const auto first = toml::get<toml::array>(aofa.at(0));
``` ```
See also [expecting conversion](#expecting-conversion) and [checking-value-type](#checking-value-type). See also [expecting conversion](#expecting-conversion)
and [checking-value-type](#checking-value-type).
### Getting tables ### Getting tables
`toml::table` is a key component of this library, which is an alias of a `std::unordered_map` from `toml::key (a.k.a. std::string)` to `toml::value`. `toml::parse` returns this as a result. `toml::table` is a key component of this library, which is an alias of
a `std::unordered_map` from `toml::key (a.k.a. std::string)` to `toml::value`.
`toml::parse` returns this.
Since it is just an alias of `std::unordered_map`, it has all the functionalities that `std::unordered_map` has, e.g. `operator[]`, `count`, and `find`. Since it is just an alias of `std::unordered_map`, it has all the functionalities
that `std::unordered_map` has, e.g. `operator[]`, `count`, and `find`.
```cpp ```cpp
toml::table data = toml::parse("example.toml"); toml::table data = toml::parse("example.toml");
@@ -180,7 +209,8 @@ if(data.count("title") != 0)
} }
``` ```
When all the values of the table have the same type, toml11 allows you to convert a `toml::table` to a `map` that contains the convertible type. When all the values of the table have the same type, toml11 allows you to
convert a `toml::table` to a `map` that contains the convertible type.
```toml ```toml
[tab] [tab]
@@ -196,7 +226,8 @@ std::cout << tab["key2"] << std::endl; // bar
### Dotted keys ### Dotted keys
TOML v0.5.0 has a new feature named "dotted keys". You can chain keys to represent the structure of the data. TOML v0.5.0 has a new feature named "dotted keys".
You can chain keys to represent the structure of the data.
```toml ```toml
physical.color = "orange" physical.color = "orange"
@@ -220,7 +251,8 @@ const auto color = toml::get<std::string>(physical.at("color"));
### An array of tables ### An array of tables
An array of tables is just an array of tables. You can get it completely in the same way as the other arrays and tables. An array of tables is just an array of tables.
You can get it completely in the same way as the other arrays and tables.
```toml ```toml
array_of_inline_table = [{key = "value1"}, {key = "value2"}, {key = "value3"}] array_of_inline_table = [{key = "value1"}, {key = "value2"}, {key = "value3"}]
@@ -240,7 +272,9 @@ const auto aot2 = toml::get<std::vector<toml::table>>(data.at("array_of_table"))
### Cost of conversion ### Cost of conversion
Although `toml::get` is convenient, it has additional copy-cost because it copies data contained in `toml::value` to the user-specified type. Of course in some case this overhead is not ignorable. Although `toml::get` is convenient, it has additional copy-cost because
it copies data contained in `toml::value` to the user-specified type.
Of course in some case this overhead is not ignorable.
By passing the exact types, `toml::get` returns reference that has nealy zero overhead. By passing the exact types, `toml::get` returns reference that has nealy zero overhead.
@@ -249,7 +283,8 @@ const auto& tab = toml::get<toml::array>(data.at("tab"));
const auto& numbers = toml::get<toml::table>(data.at("numbers")); const auto& numbers = toml::get<toml::table>(data.at("numbers"));
``` ```
Unfortunately, in this case you need to call `toml::get` each time you access to the element of `toml::array` because `toml::array` is an array of `toml::value`. Unfortunately, in this case you need to call `toml::get` each time you access to
the element of `toml::array` because `toml::array` is an array of `toml::value`.
```cpp ```cpp
const auto& num0 = toml::get<toml::integer>(numbers.at(0)); const auto& num0 = toml::get<toml::integer>(numbers.at(0));
@@ -259,7 +294,9 @@ const auto& num2 = toml::get<toml::integer>(numbers.at(2));
### Datetime and its variants ### Datetime and its variants
TOML v0.5.0 has 4 different datetime objects, `local_date`, `local_time`, `local_datetime`, and `offset_datetime`. With toml11, you can convert `local_time` to your favorite `std::chrono::duration` and others to `std::chrono::system_clock::time_point`. TOML v0.5.0 has 4 different datetime objects, `local_date`, `local_time`,
`local_datetime`, and `offset_datetime`. With toml11, you can convert `local_time`
to your favorite `std::chrono::duration` and others to `std::chrono::system_clock::time_point`.
```toml ```toml
time = 12:30:00 time = 12:30:00
@@ -284,7 +321,8 @@ const auto value = toml::get_or(data, "key", 42); // value => int 42.
### Expecting conversion ### Expecting conversion
By using `toml::expect`, you will get your expected value or an error message without throwing `toml::type_error`. By using `toml::expect`, you will get your expected value or an error message
without throwing `toml::type_error`.
```cpp ```cpp
const auto value = toml::expect<std::string>(data.at("title")); const auto value = toml::expect<std::string>(data.at("title"));
@@ -307,7 +345,9 @@ const auto value = toml::expect<int>(data.at("number"))
### Finding value from table ### Finding value from table
toml11 provides utility function to find a value from `toml::table`. Of course, you can do this in your own way with `toml::get` because it just searches an `unordered_map` and returns a value if it exists. toml11 provides utility function to find a value from `toml::table`.
Of course, you can do this in your own way with `toml::get` because
it just searches an `unordered_map` and returns a value if it exists.
```cpp ```cpp
const auto data = toml::parse("example.toml"); const auto data = toml::parse("example.toml");
@@ -321,7 +361,8 @@ terminate called after throwing an instance of 'std::out_of_range'
what(): [error] key "num" not found in example.toml what(): [error] key "num" not found in example.toml
``` ```
You can use this with a `toml::value` that is expected to be a `toml::table`. It automatically casts the value to table. You can use this with a `toml::value` that is expected to be a `toml::table`.
It automatically casts the value to table.
```cpp ```cpp
const auto data = toml::parse("example.toml"); const auto data = toml::parse("example.toml");
@@ -331,7 +372,8 @@ const auto num = toml::find<int>(data.at("table"), "num");
// num = 42 // num = 42
``` ```
In this case, because the value `data.at("table")` knows the locatoin of itself, you don't need to pass where you find the value. `toml::find` will show you a great error message. In this case, because the value `data.at("table")` knows the locatoin of itself,
you don't need to pass where you find the value. `toml::find` will show you a great error message.
```console ```console
terminate called after throwing an instance of 'std::out_of_range' terminate called after throwing an instance of 'std::out_of_range'
@@ -381,9 +423,26 @@ int i = 0;
toml::from_toml(i, data.at("something")); toml::from_toml(i, data.at("something"));
``` ```
### visiting toml::value
TOML v2.1.0+ provides `toml::visit` to apply a function to `toml::value` in the
same way as `std::variant`.
```cpp
const toml::value v(3.14);
toml::visit([](const auto& val) -> void {
std::cout << val << std::endl;
}, v);
```
The function object that would be passed to `toml::visit` must be able to
recieve all the possible TOML types. Also, the result types should be the same
each other.
### Sanitizing UTF-8 codepoints ### Sanitizing UTF-8 codepoints
toml11 shows warning if a value of an escape sequence used to represent unicode character exceeds the unicode range. toml11 shows warning if a value of an escape sequence used
to represent unicode character exceeds the unicode range.
```console ```console
[warning] input codepoint (0011FFFF) is too large to decode as a unicode character. The result may not be able to render to your screen. [warning] input codepoint (0011FFFF) is too large to decode as a unicode character. The result may not be able to render to your screen.
@@ -446,7 +505,7 @@ if(max < min)
you will get an error message like this. you will get an error message like this.
```console ```console
[error] value should be positive [error] max should be larger than min
--> example.toml --> example.toml
3 | min = 54 3 | min = 54
| ~~ minimum number here | ~~ minimum number here
@@ -455,6 +514,97 @@ you will get an error message like this.
| ~~ maximum number here | ~~ maximum number here
``` ```
### Serializing TOML data
toml11 v2.1.0 enables you to serialize data into toml format.
```cpp
const auto data = toml::table{{"foo", 42}, {"bar", "baz"}};
const std::string serial = toml::format(data);
// serial == "{bar=\"baz\",foo=42}"
std::cout << data << std::endl;
// bar = "baz"
// foo = 42
```
toml11 automatically makes a tiny table and array inline.
You can specify the width to make them inline by `std::setw` for streams.
```cpp
const auto data = toml::table{
{"qux", toml::table{{"foo", 42}, {"bar", "baz"}}},
{"quux", toml::array{"small", "array", "of", "strings"}},
{"foobar", toml::array{"this", "array", "of", "strings", "is", "too", "long",
"to", "print", "into", "single", "line", "isn't", "it?"}},
};
// the threshold becomes 80.
std::cout << std::setw(80) << data << std::endl;
// foobar = [
// "this","array","of","strings","is","too","long","to","print","into",
// "single","line","isn't","it?",
// ]
// quux = ["small","array","of","strings"]
// qux = {bar="baz",foo=42}
// the width is 0. nothing become inline.
std::cout << std::setw(0) << data << std::endl;
// foobar = [
// "this",
// ... (snip)
// "it?",
// ]
// quux = [
// "small",
// "array",
// "of",
// "strings",
// ]
// [qux]
// bar = "baz"
// foo = 42
```
It is recommended to set width before printing data. Some I/O functions changes
width to 0, and it makes all the stuff (including `toml::array`) multiline.
The resulting files becomes too long.
`toml::format` receives optional second argument to set the width.
By default, it is 80.
```cpp
const auto data = toml::table{
{"qux", toml::table{{"foo", 42}, {"bar", "baz"}}}
};
const std::string serial = toml::format(data, /*width = */ 0);
// [qux]
// bar = "baz"
// foo = 42
```
To control the precision of floating point numbers, you need to pass
`std::setprecision` to stream or pass `int` to the optional third argument of
`toml::format` (by default, it is `std::numeric_limits<double>::max_digit10`).
```cpp
const auto data = toml::table{
{"pi", 3.141592653589793},
{"e", 2.718281828459045}
};
std::cout << std::setprecision(17) << data << std::endl;
// e = 2.7182818284590451
// pi = 3.1415926535897931
std::cout << std::setprecision( 7) << data << std::endl;
// e = 2.718282
// pi = 3.141593
const std::string serial = toml::format(data, /*width = */ 0, /*prec = */ 17);
```
## Underlying types ## Underlying types
The toml types (can be used as `toml::*` in this library) and corresponding `enum` names are listed in the table below. The toml types (can be used as `toml::*` in this library) and corresponding `enum` names are listed in the table below.
@@ -470,11 +620,16 @@ The toml types (can be used as `toml::*` in this library) and corresponding `enu
| LocalDatetime | `toml::local_datetime` | `toml::value_t::LocalDatetime` | | LocalDatetime | `toml::local_datetime` | `toml::value_t::LocalDatetime` |
| OffsetDatetime | `toml::offset_datetime` | `toml::value_t::offsetDatetime` | | OffsetDatetime | `toml::offset_datetime` | `toml::value_t::offsetDatetime` |
| Array | `std::vector<toml::value>` | `toml::value_t::Array` | | Array | `std::vector<toml::value>` | `toml::value_t::Array` |
| Table | `std::unordered_map<std::string, toml::key>` | `toml::value_t::Table` | | Table | `std::unordered_map<toml::key, toml::value>` | `toml::value_t::Table` |
`toml::string` is effectively the same as `std::string` but has an additional flag that represents a kind of a string, `string_t::basic` and `string_t::literal`. Although `std::string` is not an exact toml type, still you can get a reference that points to internal `std::string` by using `toml::get<std::string>()` for convenience. `toml::string` is effectively the same as `std::string` but has an additional
flag that represents a kind of a string, `string_t::basic` and `string_t::literal`.
Although `std::string` is not an exact toml type, still you can get a reference
that points to internal `std::string` by using `toml::get<std::string>()` for convenience.
`Datetime` variants are `struct` that are defined in this library. Because `std::chrono::system_clock::time_point` is a __time point__, not capable of representing a Local Time independent from a specific day. `Datetime` variants are `struct` that are defined in this library.
Because `std::chrono::system_clock::time_point` is a __time point__,
not capable of representing a Local Time independent from a specific day.
It is recommended to get `Datetime`s as `std::chrono` classes through `toml::get`. It is recommended to get `Datetime`s as `std::chrono` classes through `toml::get`.
@@ -485,6 +640,8 @@ I thank the contributor for providing great feature to this repository.
- Guillaume Fraux (@Luthaf) - Guillaume Fraux (@Luthaf)
- Windows support and CI on Appvayor - Windows support and CI on Appvayor
- Intel Compiler support - Intel Compiler support
- Quentin Khan (@xaxousis)
- Found & Fixed a bug around ODR
## Licensing terms ## Licensing terms

View File

@@ -25,8 +25,10 @@ set(TEST_NAMES
test_to_toml test_to_toml
test_from_toml test_from_toml
test_parse_file test_parse_file
test_serialize_file
test_parse_unicode test_parse_unicode
) test_error_detection
)
CHECK_CXX_COMPILER_FLAG("-Wall" COMPILER_SUPPORTS_WALL) CHECK_CXX_COMPILER_FLAG("-Wall" COMPILER_SUPPORTS_WALL)
CHECK_CXX_COMPILER_FLAG("-Wpedantic" COMPILER_SUPPORTS_WPEDANTIC) CHECK_CXX_COMPILER_FLAG("-Wpedantic" COMPILER_SUPPORTS_WPEDANTIC)

View File

@@ -0,0 +1,199 @@
#define BOOST_TEST_MODULE "test_error_detection"
#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>
#include <iostream>
#include <fstream>
BOOST_AUTO_TEST_CASE(test_detect_empty_key)
{
std::istringstream stream(std::string("= \"value\""));
bool exception_thrown = false;
try
{
toml::parse(stream, "test_detect_empty_key");
}
catch(const toml::syntax_error& syn)
{
// to see the error message
std::cerr << syn.what() << std::endl;
exception_thrown = true;
}
BOOST_CHECK(exception_thrown);
}
BOOST_AUTO_TEST_CASE(test_detect_missing_value)
{
std::istringstream stream(std::string("a ="));
bool exception_thrown = false;
try
{
toml::parse(stream, "test_detect_missing_value");
}
catch(const toml::syntax_error& syn)
{
std::cerr << syn.what() << std::endl;
exception_thrown = true;
}
BOOST_CHECK(exception_thrown);
}
BOOST_AUTO_TEST_CASE(test_detect_too_many_value)
{
std::istringstream stream(std::string("a = 1 = \"value\""));
bool exception_thrown = false;
try
{
toml::parse(stream, "test_detect_too_many_value");
}
catch(const toml::syntax_error& syn)
{
std::cerr << syn.what() << std::endl;
exception_thrown = true;
}
BOOST_CHECK(exception_thrown);
}
BOOST_AUTO_TEST_CASE(test_detect_duplicate_table)
{
std::istringstream stream(std::string(
"[table]\n"
"a = 42\n"
"[table]\n"
"b = 42\n"
));
bool exception_thrown = false;
try
{
toml::parse(stream, "test_detect_duplicate_table");
}
catch(const toml::syntax_error& syn)
{
std::cerr << syn.what() << std::endl;
exception_thrown = true;
}
BOOST_CHECK(exception_thrown);
}
BOOST_AUTO_TEST_CASE(test_detect_conflict_array_table)
{
std::istringstream stream(std::string(
"[[table]]\n"
"a = 42\n"
"[table]\n"
"b = 42\n"
));
bool exception_thrown = false;
try
{
toml::parse(stream, "test_detect_conflict_array_table");
}
catch(const toml::syntax_error& syn)
{
std::cerr << syn.what() << std::endl;
exception_thrown = true;
}
BOOST_CHECK(exception_thrown);
}
BOOST_AUTO_TEST_CASE(test_detect_conflict_table_array)
{
std::istringstream stream(std::string(
"[table]\n"
"a = 42\n"
"[[table]]\n"
"b = 42\n"
));
bool exception_thrown = false;
try
{
toml::parse(stream, "test_detect_conflict_table_array");
}
catch(const toml::syntax_error& syn)
{
std::cerr << syn.what() << std::endl;
exception_thrown = true;
}
BOOST_CHECK(exception_thrown);
}
BOOST_AUTO_TEST_CASE(test_detect_duplicate_value)
{
std::istringstream stream(std::string(
"a = 1\n"
"a = 2\n"
));
bool exception_thrown = false;
try
{
toml::parse(stream, "test_detect_duplicate_value");
}
catch(const toml::syntax_error& syn)
{
std::cerr << syn.what() << std::endl;
exception_thrown = true;
}
BOOST_CHECK(exception_thrown);
}
BOOST_AUTO_TEST_CASE(test_detect_conflicting_value)
{
std::istringstream stream(std::string(
"a.b = 1\n"
"a.b.c = 2\n"
));
bool exception_thrown = false;
try
{
toml::parse(stream, "test_detect_conflicting_value");
}
catch(const toml::syntax_error& syn)
{
std::cerr << syn.what() << std::endl;
exception_thrown = true;
}
BOOST_CHECK(exception_thrown);
}
BOOST_AUTO_TEST_CASE(test_detect_inhomogeneous_array)
{
std::istringstream stream(std::string(
"a = [1, 1.0]\n"
));
bool exception_thrown = false;
try
{
toml::parse(stream, "test_detect_inhomogeneous_array");
}
catch(const toml::syntax_error& syn)
{
std::cerr << syn.what() << std::endl;
exception_thrown = true;
}
BOOST_CHECK(exception_thrown);
}
BOOST_AUTO_TEST_CASE(test_detect_appending_array_of_table)
{
std::istringstream stream(std::string(
"a = [{b = 1}]\n"
"[[a]]\n"
"b = 2\n"
));
bool exception_thrown = false;
try
{
toml::parse(stream, "test_detect_appending_array_of_table");
}
catch(const toml::syntax_error& syn)
{
std::cerr << syn.what() << std::endl;
exception_thrown = true;
}
BOOST_CHECK(exception_thrown);
}

View File

@@ -68,9 +68,14 @@ BOOST_AUTO_TEST_CASE(test_expect)
{ {
toml::value v1(42); toml::value v1(42);
toml::value v2(3.14); toml::value v2(3.14);
BOOST_CHECK_EQUAL(42, toml::expect<int>(v1).unwrap_or(0)); const auto v1_or_0 = toml::expect<int>(v1).unwrap_or(0);
BOOST_CHECK_EQUAL( 0, toml::expect<int>(v2).unwrap_or(0)); const auto v2_or_0 = toml::expect<int>(v2).unwrap_or(0);
BOOST_CHECK_EQUAL("42", toml::expect<int>(v1).map([](int i){return std::to_string(i);}).unwrap_or(std::string("none"))); BOOST_CHECK_EQUAL(42, v1_or_0);
BOOST_CHECK_EQUAL("none", toml::expect<int>(v2).map([](int i){return std::to_string(i);}).unwrap_or(std::string("none"))); BOOST_CHECK_EQUAL( 0, v2_or_0);
const auto v1_or_none = toml::expect<int>(v1).map([](int i){return std::to_string(i);}).unwrap_or(std::string("none"));
const auto v2_or_none = toml::expect<int>(v2).map([](int i){return std::to_string(i);}).unwrap_or(std::string("none"));
BOOST_CHECK_EQUAL("42", v1_or_none);
BOOST_CHECK_EQUAL("none", v2_or_none);
} }
} }

View File

@@ -194,3 +194,504 @@ BOOST_AUTO_TEST_CASE(test_hard_example)
BOOST_CHECK(toml::get<std::vector<std::string>>(bit.at("multi_line_array")) == BOOST_CHECK(toml::get<std::vector<std::string>>(bit.at("multi_line_array")) ==
expected_multi_line_array); expected_multi_line_array);
} }
// ---------------------------------------------------------------------------
// after here, the test codes generate the content of a file.
BOOST_AUTO_TEST_CASE(test_file_with_BOM)
{
{
const std::string table(
"\xEF\xBB\xBF" // BOM
"key = \"value\"\n"
"[table]\n"
"key = \"value\"\n"
);
std::istringstream iss(table);
const auto data = toml::parse(iss, "test_file_with_BOM.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"\xEF\xBB\xBF" // BOM
"key = \"value\"\n"
"[table]\n"
"key = \"value\"\n"
);
{
std::ofstream ofs("tmp.toml");
ofs << table;
}
const auto data = toml::parse("tmp.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"\xEF\xBB\xBF" // BOM
"key = \"value\"\r\n"
"[table]\r\n"
"key = \"value\"\r\n"
);
std::istringstream iss(table);
const auto data = toml::parse(iss, "test_file_with_BOM_CRLF.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"\xEF\xBB\xBF" // BOM
"key = \"value\"\r\n"
"[table]\r\n"
"key = \"value\"\r\n"
);
{
// with text-mode, "\n" is converted to "\r\n" and the resulting
// value will be "\r\r\n". To avoid the additional "\r", use binary
// mode.
std::ofstream ofs("tmp.toml", std::ios_base::binary);
ofs.write(table.data(), table.size());
}
const auto data = toml::parse("tmp.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
}
BOOST_AUTO_TEST_CASE(test_file_without_newline_at_the_end_of_file)
{
{
const std::string table(
"key = \"value\"\n"
"[table]\n"
"key = \"value\""
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_file_without_newline_at_the_end_of_file.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"key = \"value\"\r\n"
"[table]\r\n"
"key = \"value\""
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_file_without_newline_at_the_end_of_file_CRLF.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"key = \"value\"\n"
"[table]\n"
"key = \"value\" # comment"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_file_without_newline_at_the_end_of_file_comment.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"key = \"value\"\r\n"
"[table]\r\n"
"key = \"value\" # comment"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_file_without_newline_at_the_end_of_file_comment.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"key = \"value\"\n"
"[table]\n"
"key = \"value\" \t"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_file_without_newline_at_the_end_of_file_ws.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"key = \"value\"\r\n"
"[table]\r\n"
"key = \"value\" \t"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_file_without_newline_at_the_end_of_file_ws.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
}
BOOST_AUTO_TEST_CASE(test_files_end_with_comment)
{
// comment w/o newline
{
const std::string table(
"key = \"value\"\n"
"[table]\n"
"key = \"value\"\n"
"# comment"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_comment.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"key = \"value\"\n"
"[table]\n"
"key = \"value\"\n"
"# comment\n"
"# one more comment"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_comment.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
// comment w/ newline
{
const std::string table(
"key = \"value\"\n"
"[table]\n"
"key = \"value\"\n"
"# comment\n"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_comment.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"key = \"value\"\n"
"[table]\n"
"key = \"value\"\n"
"# comment\n"
"# one more comment\n"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_comment.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
// CRLF version
{
const std::string table(
"key = \"value\"\r\n"
"[table]\r\n"
"key = \"value\"\r\n"
"# comment"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_comment.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"key = \"value\"\r\n"
"[table]\r\n"
"key = \"value\"\r\n"
"# comment\r\n"
"# one more comment"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_comment.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"key = \"value\"\r\n"
"[table]\r\n"
"key = \"value\"\r\n"
"# comment\r\n"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_comment.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"key = \"value\"\r\n"
"[table]\r\n"
"key = \"value\"\r\n"
"# comment\r\n"
"# one more comment\r\n"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_comment.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
}
BOOST_AUTO_TEST_CASE(test_files_end_with_empty_lines)
{
{
const std::string table(
"key = \"value\"\n"
"[table]\n"
"key = \"value\"\n"
"\n"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_newline.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"key = \"value\"\n"
"[table]\n"
"key = \"value\"\n"
"\n"
"\n"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_newline.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
// with whitespaces
{
const std::string table(
"key = \"value\"\n"
"[table]\n"
"key = \"value\"\n"
" \n"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_newline.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"key = \"value\"\n"
"[table]\n"
"key = \"value\"\n"
" \n"
" \n"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_newline.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"key = \"value\"\n"
"[table]\n"
"key = \"value\"\n"
"\n"
" \n"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_newline.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"key = \"value\"\n"
"[table]\n"
"key = \"value\"\n"
" \n"
"\n"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_newline.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
// with whitespaces but no newline
{
const std::string table(
"key = \"value\"\n"
"[table]\n"
"key = \"value\"\n"
" "
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_newline.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
// CRLF
{
const std::string table(
"key = \"value\"\r\n"
"[table]\r\n"
"key = \"value\"\r\n"
"\r\n"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_newline.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"key = \"value\"\r\n"
"[table]\r\n"
"key = \"value\"\r\n"
"\r\n"
"\r\n"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_newline.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
// with whitespaces
{
const std::string table(
"key = \"value\"\r\n"
"[table]\r\n"
"key = \"value\"\r\n"
" \r\n"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_newline.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"key = \"value\"\r\n"
"[table]\r\n"
"key = \"value\"\r\n"
"\r\n"
" \r\n"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_newline.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"key = \"value\"\r\n"
"[table]\r\n"
"key = \"value\"\r\n"
" \r\n"
"\r\n"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_newline.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"key = \"value\"\r\n"
"[table]\r\n"
"key = \"value\"\r\n"
" \r\n"
" \r\n"
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_newline.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
{
const std::string table(
"key = \"value\"\r\n"
"[table]\r\n"
"key = \"value\"\r\n"
" "
);
std::istringstream iss(table);
const auto data = toml::parse(iss,
"test_files_end_with_newline.toml");
BOOST_CHECK_EQUAL(toml::get <std::string>(data.at("key")), "value");
BOOST_CHECK_EQUAL(toml::find<std::string>(data.at("table"), "key"), "value");
}
}

View File

@@ -409,3 +409,33 @@ BOOST_AUTO_TEST_CASE(test_or_else)
BOOST_CHECK_EQUAL(mapped.unwrap_err(), "hogehoge"); BOOST_CHECK_EQUAL(mapped.unwrap_err(), "hogehoge");
} }
} }
BOOST_AUTO_TEST_CASE(test_and_or_other)
{
{
const toml::result<int, std::string> r1(toml::ok(42));
const toml::result<int, std::string> r2(toml::err<std::string>("foo"));
BOOST_CHECK_EQUAL(r1, r1.or_other(r2));
BOOST_CHECK_EQUAL(r2, r1.and_other(r2));
BOOST_CHECK_EQUAL(42, r1.or_other(r2).unwrap());
BOOST_CHECK_EQUAL("foo", r1.and_other(r2).unwrap_err());
}
{
auto r1_gen = []() -> toml::result<int, std::string> {
return toml::ok(42);
};
auto r2_gen = []() -> toml::result<int, std::string> {
return toml::err<std::string>("foo");
};
const auto r3 = r1_gen();
const auto r4 = r2_gen();
BOOST_CHECK_EQUAL(r3, r1_gen().or_other (r2_gen()));
BOOST_CHECK_EQUAL(r4, r1_gen().and_other(r2_gen()));
BOOST_CHECK_EQUAL(42, r1_gen().or_other (r2_gen()).unwrap());
BOOST_CHECK_EQUAL("foo", r1_gen().and_other(r2_gen()).unwrap_err());
}
}

View File

@@ -0,0 +1,54 @@
#define BOOST_TEST_MODULE "test_serialize_file"
#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>
#include <iostream>
#include <fstream>
BOOST_AUTO_TEST_CASE(test_example)
{
const auto data = toml::parse("toml/tests/example.toml");
{
std::ofstream ofs("tmp1.toml");
ofs << std::setw(80) << data;
}
auto serialized = toml::parse("tmp1.toml");
{
auto& owner = toml::get<toml::table>(serialized.at("owner"));
auto& bio = toml::get<std::string>(owner.at("bio"));
const auto CR = std::find(bio.begin(), bio.end(), '\r');
if(CR != bio.end())
{
bio.erase(CR);
}
}
BOOST_CHECK(data == serialized);
}
BOOST_AUTO_TEST_CASE(test_fruit)
{
const auto data = toml::parse("toml/tests/fruit.toml");
{
std::ofstream ofs("tmp2.toml");
ofs << std::setw(80) << data;
}
const auto serialized = toml::parse("tmp2.toml");
BOOST_CHECK(data == serialized);
}
BOOST_AUTO_TEST_CASE(test_hard_example)
{
const auto data = toml::parse("toml/tests/hard_example.toml");
{
std::ofstream ofs("tmp3.toml");
ofs << std::setw(80) << data;
}
const auto serialized = toml::parse("tmp3.toml");
BOOST_CHECK(data == serialized);
}

View File

@@ -34,6 +34,7 @@
#endif #endif
#include "toml/parser.hpp" #include "toml/parser.hpp"
#include "toml/serializer.hpp"
#include "toml/to_toml.hpp" #include "toml/to_toml.hpp"
#include "toml/from_toml.hpp" #include "toml/from_toml.hpp"
#include "toml/get.hpp" #include "toml/get.hpp"

View File

@@ -14,6 +14,36 @@
namespace toml namespace toml
{ {
// To avoid non-threadsafe std::localtime. In C11 (not C++11!), localtime_s is
// provided in the absolutely same purpose, but C++11 is actually not compatible
// with C11. We need to dispatch the function depending on the OS.
namespace detail
{
// TODO: find more sophisticated way to handle this
#if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _BSD_SOURCE || _SVID_SOURCE || _POSIX_SOURCE
inline std::tm localtime_s(const std::time_t* src)
{
std::tm dst;
const auto result = ::localtime_r(src, &dst);
if(!result)
{
throw std::runtime_error("localtime_r failed.");
}
return dst;
}
#else
// XXX: On Windows, std::localtime is thread-safe because they uses thread-local
// storage to store the instance of std::tm. On the other platforms, it may not
// be thread-safe.
inline std::tm localtime_s(const std::time_t* src)
{
const auto result = std::localtime(src);
if(!result) {throw std::runtime_error("localtime failed.");}
return *result;
}
#endif
} // detail
enum class month_t : std::int8_t enum class month_t : std::int8_t
{ {
Jan = 0, Jan = 0,
@@ -50,10 +80,8 @@ struct local_date
explicit local_date(const std::chrono::system_clock::time_point& tp) explicit local_date(const std::chrono::system_clock::time_point& tp)
{ {
const auto t = std::chrono::system_clock::to_time_t(tp); const auto t = std::chrono::system_clock::to_time_t(tp);
const auto tmp = std::localtime(&t); //XXX: not threadsafe! const auto time = detail::localtime_s(&t);
assert(tmp); // if std::localtime fails, tmp is nullptr
const std::tm time = *tmp;
*this = local_date(time); *this = local_date(time);
} }
@@ -330,10 +358,8 @@ struct local_datetime
explicit local_datetime(const std::chrono::system_clock::time_point& tp) explicit local_datetime(const std::chrono::system_clock::time_point& tp)
{ {
const auto t = std::chrono::system_clock::to_time_t(tp); const auto t = std::chrono::system_clock::to_time_t(tp);
const auto tmp = std::localtime(&t); //XXX: not threadsafe! std::tm time = detail::localtime_s(&t);
assert(tmp); // if std::localtime fails, tmp is nullptr
std::tm time = *tmp;
this->date = local_date(time); this->date = local_date(time);
this->time = local_time(time); this->time = local_time(time);
@@ -482,10 +508,8 @@ struct offset_datetime
static time_offset get_local_offset() static time_offset get_local_offset()
{ {
// get current timezone // get current timezone
const auto tmp1 = std::time(nullptr); const auto tmp1 = std::time(nullptr);
const auto tmp2 = std::localtime(&tmp1); // XXX not threadsafe! const auto t = detail::localtime_s(&tmp1);
assert(tmp2);
std::tm t = *tmp2;
std::array<char, 6> buf; std::array<char, 6> buf;
const auto result = std::strftime(buf.data(), 6, "%z", &t); // +hhmm\0 const auto result = std::strftime(buf.data(), 6, "%z", &t); // +hhmm\0

View File

@@ -295,7 +295,7 @@ T get(const toml::value& v)
// ============================================================================ // ============================================================================
// find and get // find and get
template<typename T> template<typename T = ::toml::value>
decltype(::toml::get<T>(std::declval<const ::toml::value&>())) decltype(::toml::get<T>(std::declval<const ::toml::value&>()))
find(const toml::table& tab, const toml::key& ky, find(const toml::table& tab, const toml::key& ky,
std::string tablename = "unknown table") std::string tablename = "unknown table")
@@ -307,7 +307,7 @@ find(const toml::table& tab, const toml::key& ky,
} }
return ::toml::get<T>(tab.at(ky)); return ::toml::get<T>(tab.at(ky));
} }
template<typename T> template<typename T = ::toml::value>
decltype(::toml::get<T>(std::declval<::toml::value&>())) decltype(::toml::get<T>(std::declval<::toml::value&>()))
find(toml::table& tab, const toml::key& ky, find(toml::table& tab, const toml::key& ky,
std::string tablename = "unknown table") std::string tablename = "unknown table")
@@ -319,7 +319,7 @@ find(toml::table& tab, const toml::key& ky,
} }
return ::toml::get<T>(tab[ky]); return ::toml::get<T>(tab[ky]);
} }
template<typename T> template<typename T = ::toml::value>
decltype(::toml::get<T>(std::declval<::toml::value&&>())) decltype(::toml::get<T>(std::declval<::toml::value&&>()))
find(toml::table&& tab, const toml::key& ky, find(toml::table&& tab, const toml::key& ky,
std::string tablename = "unknown table") std::string tablename = "unknown table")
@@ -332,7 +332,7 @@ find(toml::table&& tab, const toml::key& ky,
return ::toml::get<T>(std::move(tab[ky])); return ::toml::get<T>(std::move(tab[ky]));
} }
template<typename T> template<typename T = ::toml::value>
decltype(::toml::get<T>(std::declval<const ::toml::value&>())) decltype(::toml::get<T>(std::declval<const ::toml::value&>()))
find(const toml::value& v, const toml::key& ky) find(const toml::value& v, const toml::key& ky)
{ {
@@ -345,7 +345,7 @@ find(const toml::value& v, const toml::key& ky)
} }
return ::toml::get<T>(tab.at(ky)); return ::toml::get<T>(tab.at(ky));
} }
template<typename T> template<typename T = ::toml::value>
decltype(::toml::get<T>(std::declval<::toml::value&>())) decltype(::toml::get<T>(std::declval<::toml::value&>()))
find(toml::value& v, const toml::key& ky) find(toml::value& v, const toml::key& ky)
{ {
@@ -358,7 +358,7 @@ find(toml::value& v, const toml::key& ky)
} }
return ::toml::get<T>(tab.at(ky)); return ::toml::get<T>(tab.at(ky));
} }
template<typename T> template<typename T = ::toml::value>
decltype(::toml::get<T>(std::declval<::toml::value&&>())) decltype(::toml::get<T>(std::declval<::toml::value&&>()))
find(toml::value&& v, const toml::key& ky) find(toml::value&& v, const toml::key& ky)
{ {

View File

@@ -34,8 +34,8 @@ parse_boolean(location<Container>& loc)
} }
} }
loc.iter() = first; //rollback loc.iter() = first; //rollback
return err(format_underline("[error] toml::parse_boolean", loc, return err(std::string("[error] toml::parse_boolean: "
"token is not boolean", {"boolean is `true` or `false`"})); "the next token is not a boolean"));
} }
template<typename Container> template<typename Container>
@@ -63,8 +63,8 @@ parse_binary_integer(location<Container>& loc)
return ok(std::make_pair(retval, token.unwrap())); return ok(std::make_pair(retval, token.unwrap()));
} }
loc.iter() = first; loc.iter() = first;
return err(format_underline("[error] toml::parse_binary_integer", loc, return err(std::string("[error] toml::parse_binary_integer:"
"token is not binary integer", {"binary integer is like: 0b0011"})); "the next token is not an integer"));
} }
template<typename Container> template<typename Container>
@@ -84,9 +84,8 @@ parse_octal_integer(location<Container>& loc)
return ok(std::make_pair(retval, token.unwrap())); return ok(std::make_pair(retval, token.unwrap()));
} }
loc.iter() = first; loc.iter() = first;
return err(std::string("[error] toml::parse_octal_integer:"
return err(format_underline("[error] toml::parse_octal_integer", loc, "the next token is not an integer"));
"token is not octal integer", {"octal integer is like: 0o775"}));
} }
template<typename Container> template<typename Container>
@@ -106,8 +105,8 @@ parse_hexadecimal_integer(location<Container>& loc)
return ok(std::make_pair(retval, token.unwrap())); return ok(std::make_pair(retval, token.unwrap()));
} }
loc.iter() = first; loc.iter() = first;
return err(format_underline("[error] toml::parse_hexadecimal_integer", loc, return err(std::string("[error] toml::parse_hexadecimal_integer"
"token is not hex integer", {"hex integer is like: 0xC0FFEE"})); "the next token is not an integer"));
} }
template<typename Container> template<typename Container>
@@ -134,10 +133,8 @@ parse_integer(location<Container>& loc)
return ok(std::make_pair(retval, token.unwrap())); return ok(std::make_pair(retval, token.unwrap()));
} }
loc.iter() = first; loc.iter() = first;
return err(format_underline("[error] toml::parse_integer", loc, return err(std::string("[error] toml::parse_integer: "
"token is not integer", {"integer is like: +42", "the next token is not an integer"));
"hex integer is like: 0xC0FFEE", "octal integer is like: 0o775",
"binary integer is like: 0b0011"}));
} }
template<typename Container> template<typename Container>
@@ -225,8 +222,8 @@ parse_floating(location<Container>& loc)
return ok(std::make_pair(v, token.unwrap())); return ok(std::make_pair(v, token.unwrap()));
} }
loc.iter() = first; loc.iter() = first;
return err(format_underline("[error] toml::parse_floating: ", loc, return err(std::string("[error] toml::parse_floating: "
"token is not a float", {"floating point is like: -3.14e+1"})); "the next token is not a float"));
} }
template<typename Container> template<typename Container>
@@ -286,9 +283,8 @@ result<std::string, std::string> parse_escape_sequence(location<Container>& loc)
const auto first = loc.iter(); const auto first = loc.iter();
if(first == loc.end() || *first != '\\') if(first == loc.end() || *first != '\\')
{ {
return err(format_underline("[error]: " return err(std::string("[error]: toml::parse_escape_sequence: "
"toml::parse_escape_sequence: location does not points \"\\\"", "the next token is not an escape sequence \"\\\""));
loc, "should be \"\\\""));
} }
++loc.iter(); ++loc.iter();
switch(*loc.iter()) switch(*loc.iter())
@@ -527,8 +523,8 @@ parse_string(location<Container>& loc)
if(const auto rslt = parse_ml_literal_string(loc)) {return rslt;} if(const auto rslt = parse_ml_literal_string(loc)) {return rslt;}
if(const auto rslt = parse_basic_string(loc)) {return rslt;} if(const auto rslt = parse_basic_string(loc)) {return rslt;}
if(const auto rslt = parse_literal_string(loc)) {return rslt;} if(const auto rslt = parse_literal_string(loc)) {return rslt;}
return err(format_underline("[error] toml::parse_string: not a string", return err(std::string("[error] toml::parse_string: "
loc, "not a string")); "the next token is not a string"));
} }
template<typename Container> template<typename Container>
@@ -576,11 +572,9 @@ parse_local_date(location<Container>& loc)
} }
else else
{ {
auto msg = format_underline("[error]: toml::parse_local_date: "
"invalid format", loc, token.unwrap_err(),
{"local date is like: 1979-05-27"});
loc.iter() = first; loc.iter() = first;
return err(std::move(msg)); return err(std::string("[error]: toml::parse_local_date: "
"the next token is not a local_date"));
} }
} }
@@ -661,11 +655,9 @@ parse_local_time(location<Container>& loc)
} }
else else
{ {
auto msg = format_underline("[error]: toml::parse_local_time: "
"invalid format", loc, token.unwrap_err(),
{"local time is like: 00:32:00.999999"});
loc.iter() = first; loc.iter() = first;
return err(std::move(msg)); return err(std::string("[error]: toml::parse_local_time: "
"the next token is not a local_time"));
} }
} }
@@ -706,11 +698,9 @@ parse_local_datetime(location<Container>& loc)
} }
else else
{ {
auto msg = format_underline("[error]: toml::parse_local_datetime: "
"invalid format", loc, token.unwrap_err(),
{"local datetime is like: 1979-05-27T00:32:00.999999"});
loc.iter() = first; loc.iter() = first;
return err(std::move(msg)); return err(std::string("[error]: toml::parse_local_datetime: "
"the next token is not a local_datetime"));
} }
} }
@@ -757,12 +747,9 @@ parse_offset_datetime(location<Container>& loc)
} }
else else
{ {
auto msg = format_underline("[error]: toml::parse_offset_datetime: "
"invalid format", loc, token.unwrap_err(),
{"offset datetime is like: 1979-05-27T00:32:00-07:00",
"or in UTC (w/o offset) : 1979-05-27T00:32:00Z"});
loc.iter() = first; loc.iter() = first;
return err(std::move(msg)); return err(std::string("[error]: toml::parse_offset_datetime: "
"the next token is not a local_datetime"));
} }
} }
@@ -781,8 +768,8 @@ result<key, std::string> parse_simple_key(location<Container>& loc)
{ {
return ok(bare.unwrap().str()); return ok(bare.unwrap().str());
} }
return err(format_underline("[error] toml::parse_simple_key: " return err(std::string("[error] toml::parse_simple_key: "
"the next token is not a simple key", loc, "not a key")); "the next token is not a simple key"));
} }
// dotted key become vector of keys // dotted key become vector of keys
@@ -835,8 +822,8 @@ result<std::vector<key>, std::string> parse_key(location<Container>& loc)
{ {
return ok(std::vector<key>(1, smpl.unwrap())); return ok(std::vector<key>(1, smpl.unwrap()));
} }
return err(format_underline("toml::parse_key: the next token is not a key", return err(std::string("[error] toml::parse_key: "
loc, "not a key")); "the next token is not a key"));
} }
// forward-decl to implement parse_array and parse_table // forward-decl to implement parse_array and parse_table
@@ -854,8 +841,7 @@ parse_array(location<Container>& loc)
} }
if(*loc.iter() != '[') if(*loc.iter() != '[')
{ {
return err(format_underline("[error] toml::parse_array: " return err("[error] toml::parse_array: token is not an array");
"token is not an array", loc, "should be ["));
} }
++loc.iter(); ++loc.iter();
@@ -903,13 +889,13 @@ parse_array(location<Container>& loc)
} }
else else
{ {
return err(format_underline("[error] toml::parse_array: " throw syntax_error(format_underline("[error] toml::parse_array:"
"missing array separator `,`", loc, "should be `,`")); " missing array separator `,`", loc, "should be `,`"));
} }
} }
} }
loc.iter() = first; loc.iter() = first;
return err(format_underline("[error] toml::parse_array: " throw syntax_error(format_underline("[error] toml::parse_array: "
"array did not closed by `]`", loc, "should be closed")); "array did not closed by `]`", loc, "should be closed"));
} }
@@ -971,7 +957,8 @@ parse_key_value_pair(location<Container>& loc)
} }
else else
{ {
msg = val.unwrap_err(); msg = format_underline("[error] toml::parse_key_value_pair: "
"invalid value format", loc, val.unwrap_err());
} }
loc.iter() = first; loc.iter() = first;
return err(msg); return err(msg);
@@ -1035,9 +1022,9 @@ insert_nested_key(table& root, const toml::value& v,
"[error] toml::insert_value: array of table (\"", "[error] toml::insert_value: array of table (\"",
format_dotted_keys(first, last), "\") collides with" format_dotted_keys(first, last), "\") collides with"
" existing value"), get_region(tab->at(k)), " existing value"), get_region(tab->at(k)),
concat_to_string("this ", tab->at(k).type(), "value" concat_to_string("this ", tab->at(k).type(),
"already exists"), get_region(v), "while inserting" " value already exists"), get_region(v),
"this array-of-tables")); "while inserting this array-of-tables"));
} }
array& a = tab->at(k).template cast<toml::value_t::Array>(); array& a = tab->at(k).template cast<toml::value_t::Array>();
if(!(a.front().is(value_t::Table))) if(!(a.front().is(value_t::Table)))
@@ -1046,9 +1033,34 @@ insert_nested_key(table& root, const toml::value& v,
"[error] toml::insert_value: array of table (\"", "[error] toml::insert_value: array of table (\"",
format_dotted_keys(first, last), "\") collides with" format_dotted_keys(first, last), "\") collides with"
" existing value"), get_region(tab->at(k)), " existing value"), get_region(tab->at(k)),
concat_to_string("this ", tab->at(k).type(), "value" concat_to_string("this ", tab->at(k).type(),
"already exists"), get_region(v), "while inserting" " value already exists"), get_region(v),
"this array-of-tables")); "while inserting this array-of-tables"));
}
// avoid conflicting array of table like the following.
// ```toml
// a = [{b = 42}] # define a as an array of *inline* tables
// [[a]] # a is an array of *multi-line* tables
// b = 54
// ```
// Here, from the type information, these cannot be detected
// bacause inline table is also a table.
// But toml v0.5.0 explicitly says it is invalid. The above
// array-of-tables has a static size and appending to the
// array is invalid.
// In this library, multi-line table value has a region
// that points to the key of the table (e.g. [[a]]). By
// comparing the first two letters in key, we can detect
// the array-of-table is inline or multiline.
if(detail::get_region(a.front()).str().substr(0,2) != "[[")
{
throw syntax_error(format_underline(concat_to_string(
"[error] toml::insert_value: array of table (\"",
format_dotted_keys(first, last), "\") collides with"
" existing array-of-tables"), get_region(tab->at(k)),
concat_to_string("this ", tab->at(k).type(),
" value has static size"), get_region(v),
"appending this to the statically sized array"));
} }
a.push_back(v); a.push_back(v);
return ok(true); return ok(true);
@@ -1155,10 +1167,11 @@ parse_inline_table(location<Container>& loc)
table retval; table retval;
if(!(loc.iter() != loc.end() && *loc.iter() == '{')) if(!(loc.iter() != loc.end() && *loc.iter() == '{'))
{ {
return err(format_underline("[error] toml::parse_inline_table: " return err(std::string("[error] toml::parse_inline_table: "
"the next token is not an inline table", loc, "not `{`.")); "the next token is not an inline table"));
} }
++loc.iter(); ++loc.iter();
// it starts from "{". it should be formatted as inline-table
while(loc.iter() != loc.end()) while(loc.iter() != loc.end())
{ {
maybe<lex_ws>::invoke(loc); maybe<lex_ws>::invoke(loc);
@@ -1198,13 +1211,14 @@ parse_inline_table(location<Container>& loc)
} }
else else
{ {
return err(format_underline("[error] toml:::parse_inline_table:" throw syntax_error(format_underline("[error] "
" missing table separator `,` ", loc, "should be `,`")); "toml:::parse_inline_table: missing table separator `,` ",
loc, "should be `,`"));
} }
} }
} }
loc.iter() = first; loc.iter() = first;
return err(format_underline("[error] toml::parse_inline_table: " throw syntax_error(format_underline("[error] toml::parse_inline_table: "
"inline table did not closed by `}`", loc, "should be closed")); "inline table did not closed by `}`", loc, "should be closed"));
} }
@@ -1331,7 +1345,7 @@ result<table, std::string> parse_ml_table(location<Container>& loc)
return err(std::string("toml::parse_ml_table: input is empty")); return err(std::string("toml::parse_ml_table: input is empty"));
} }
// XXX at lest one newline is needed // XXX at lest one newline is needed.
using skip_line = repeat< using skip_line = repeat<
sequence<maybe<lex_ws>, maybe<lex_comment>, lex_newline>, at_least<1>>; sequence<maybe<lex_ws>, maybe<lex_comment>, lex_newline>, at_least<1>>;
skip_line::invoke(loc); skip_line::invoke(loc);
@@ -1351,6 +1365,7 @@ result<table, std::string> parse_ml_table(location<Container>& loc)
loc.iter() = before; loc.iter() = before;
return ok(tab); return ok(tab);
} }
if(const auto kv = parse_key_value_pair(loc)) if(const auto kv = parse_key_value_pair(loc))
{ {
const std::vector<key>& keys = kv.unwrap().first; const std::vector<key>& keys = kv.unwrap().first;
@@ -1367,6 +1382,17 @@ result<table, std::string> parse_ml_table(location<Container>& loc)
return err(kv.unwrap_err()); return err(kv.unwrap_err());
} }
// comment lines are skipped by the above function call.
// However, since the `skip_line` requires at least 1 newline, it fails
// if the file ends with ws and/or comment without newline.
// `skip_line` matches `ws? + comment? + newline`, not `ws` or `comment`
// itself. To skip the last ws and/or comment, call lexers.
// It does not matter if these fails, so the return value is discarded.
lex_ws::invoke(loc);
lex_comment::invoke(loc);
// skip_line is (whitespace? comment? newline)_{1,}. multiple empty lines
// and comments after the last key-value pairs are allowed.
const auto newline = skip_line::invoke(loc); const auto newline = skip_line::invoke(loc);
if(!newline && loc.iter() != loc.end()) if(!newline && loc.iter() != loc.end())
{ {
@@ -1379,11 +1405,10 @@ result<table, std::string> parse_ml_table(location<Container>& loc)
return err(msg); return err(msg);
} }
// comment lines are skipped by the above function call. // the skip_lines only matches with lines that includes newline.
// However, if the file ends with comment without newline, // to skip the last line that includes comment and/or whitespace
// it might cause parsing error because skip_line matches // but no newline, call them one more time.
// `comment + newline`, not `comment` itself. to skip the lex_ws::invoke(loc);
// last comment, call lex_comment one more time.
lex_comment::invoke(loc); lex_comment::invoke(loc);
} }
return ok(tab); return ok(tab);
@@ -1472,10 +1497,10 @@ inline table parse(std::istream& is, std::string fname = "unknown file")
// be compared to char. However, since we are always out of luck, we need to // be compared to char. However, since we are always out of luck, we need to
// check our chars are equivalent to BOM. To do this, first we need to // check our chars are equivalent to BOM. To do this, first we need to
// convert char to unsigned char to guarantee the comparability. // convert char to unsigned char to guarantee the comparability.
if(letters.size() >= 3) if(loc.source()->size() >= 3)
{ {
std::array<unsigned char, 3> BOM; std::array<unsigned char, 3> BOM;
std::memcpy(BOM.data(), letters.data(), 3); std::memcpy(BOM.data(), loc.source()->data(), 3);
if(BOM[0] == 0xEF && BOM[1] == 0xBB && BOM[2] == 0xBF) if(BOM[0] == 0xEF && BOM[1] == 0xBB && BOM[2] == 0xBF)
{ {
loc.iter() += 3; // BOM found. skip. loc.iter() += 3; // BOM found. skip.
@@ -1492,7 +1517,7 @@ inline table parse(std::istream& is, std::string fname = "unknown file")
inline table parse(const std::string& fname) inline table parse(const std::string& fname)
{ {
std::ifstream ifs(fname.c_str()); std::ifstream ifs(fname.c_str(), std::ios_base::binary);
if(!ifs.good()) if(!ifs.good())
{ {
throw std::runtime_error("toml::parse: file open error -> " + fname); throw std::runtime_error("toml::parse: file open error -> " + fname);

View File

@@ -262,10 +262,8 @@ inline std::string format_underline(const std::string& message,
std::max(line_number1.size(), line_number2.size()); std::max(line_number1.size(), line_number2.size());
std::ostringstream retval; std::ostringstream retval;
retval << message; retval << message << newline;
retval << newline; retval << " --> " << reg1.name() << newline;
retval << " --> ";
retval << reg1.name() << newline;;
// --------------------------------------- // ---------------------------------------
retval << ' ' << std::setw(line_num_width) << line_number1; retval << ' ' << std::setw(line_num_width) << line_number1;
retval << " | " << line1 << newline; retval << " | " << line1 << newline;
@@ -276,7 +274,14 @@ inline std::string format_underline(const std::string& message,
retval << ' '; retval << ' ';
retval << comment_for_underline1 << newline; retval << comment_for_underline1 << newline;
// --------------------------------------- // ---------------------------------------
retval << " ..." << newline; if(reg2.name() != reg1.name())
{
retval << " --> " << reg2.name() << newline;
}
else
{
retval << " ..." << newline;
}
retval << ' ' << std::setw(line_num_width) << line_number2; retval << ' ' << std::setw(line_num_width) << line_number2;
retval << " | " << line2 << newline; retval << " | " << line2 << newline;
retval << make_string(line_num_width + 1, ' '); retval << make_string(line_num_width + 1, ' ');

View File

@@ -2,6 +2,7 @@
// Distributed under the MIT License. // Distributed under the MIT License.
#ifndef TOML11_RESULT_H #ifndef TOML11_RESULT_H
#define TOML11_RESULT_H #define TOML11_RESULT_H
#include "traits.hpp"
#include <type_traits> #include <type_traits>
#include <stdexcept> #include <stdexcept>
#include <utility> #include <utility>
@@ -13,19 +14,6 @@
namespace toml namespace toml
{ {
#if __cplusplus >= 201703L
template<typename F, typename ... Args>
using return_type_of_t = std::invoke_result_t<F, Args...>;
#else
// result_of is deprecated after C++17
template<typename F, typename ... Args>
using return_type_of_t = typename std::result_of<F(Args...)>::type;
#endif
template<typename T> template<typename T>
struct success struct success
{ {
@@ -396,23 +384,20 @@ struct result
return std::move(this->succ.value); return std::move(this->succ.value);
} }
template<typename U> value_type& unwrap_or(value_type& opt) &
value_type& unwrap_or(U& opt) &
{ {
if(is_err()) {return opt;} if(is_err()) {return opt;}
return this->succ.value; return this->succ.value;
} }
template<typename U> value_type const& unwrap_or(value_type const& opt) const&
value_type const& unwrap_or(U const& opt) const&
{ {
if(is_err()) {return opt;} if(is_err()) {return opt;}
return this->succ.value; return this->succ.value;
} }
template<typename U> value_type unwrap_or(value_type opt) &&
value_type&& unwrap_or(U&& opt) &&
{ {
if(is_err()) {return std::move(opt);} if(is_err()) {return opt;}
return std::move(this->succ.value); return this->succ.value;
} }
error_type& unwrap_err() & error_type& unwrap_err() &
@@ -444,21 +429,21 @@ struct result
// F: T -> U // F: T -> U
// retval: result<U, E> // retval: result<U, E>
template<typename F> template<typename F>
result<return_type_of_t<F, value_type&>, error_type> result<detail::return_type_of_t<F, value_type&>, error_type>
map(F&& f) & map(F&& f) &
{ {
if(this->is_ok()){return ok(f(this->as_ok()));} if(this->is_ok()){return ok(f(this->as_ok()));}
return err(this->as_err()); return err(this->as_err());
} }
template<typename F> template<typename F>
result<return_type_of_t<F, value_type const&>, error_type> result<detail::return_type_of_t<F, value_type const&>, error_type>
map(F&& f) const& map(F&& f) const&
{ {
if(this->is_ok()){return ok(f(this->as_ok()));} if(this->is_ok()){return ok(f(this->as_ok()));}
return err(this->as_err()); return err(this->as_err());
} }
template<typename F> template<typename F>
result<return_type_of_t<F, value_type &&>, error_type> result<detail::return_type_of_t<F, value_type &&>, error_type>
map(F&& f) && map(F&& f) &&
{ {
if(this->is_ok()){return ok(f(std::move(this->as_ok())));} if(this->is_ok()){return ok(f(std::move(this->as_ok())));}
@@ -469,21 +454,21 @@ struct result
// F: E -> F // F: E -> F
// retval: result<T, F> // retval: result<T, F>
template<typename F> template<typename F>
result<value_type, return_type_of_t<F, error_type&>> result<value_type, detail::return_type_of_t<F, error_type&>>
map_err(F&& f) & map_err(F&& f) &
{ {
if(this->is_err()){return err(f(this->as_err()));} if(this->is_err()){return err(f(this->as_err()));}
return ok(this->as_ok()); return ok(this->as_ok());
} }
template<typename F> template<typename F>
result<value_type, return_type_of_t<F, error_type const&>> result<value_type, detail::return_type_of_t<F, error_type const&>>
map_err(F&& f) const& map_err(F&& f) const&
{ {
if(this->is_err()){return err(f(this->as_err()));} if(this->is_err()){return err(f(this->as_err()));}
return ok(this->as_ok()); return ok(this->as_ok());
} }
template<typename F> template<typename F>
result<value_type, return_type_of_t<F, error_type&&>> result<value_type, detail::return_type_of_t<F, error_type&&>>
map_err(F&& f) && map_err(F&& f) &&
{ {
if(this->is_err()){return err(f(std::move(this->as_err())));} if(this->is_err()){return err(f(std::move(this->as_err())));}
@@ -494,21 +479,21 @@ struct result
// F: T -> U // F: T -> U
// retval: U // retval: U
template<typename F, typename U> template<typename F, typename U>
return_type_of_t<F, value_type&> detail::return_type_of_t<F, value_type&>
map_or_else(F&& f, U&& opt) & map_or_else(F&& f, U&& opt) &
{ {
if(this->is_err()){return std::forward<U>(opt);} if(this->is_err()){return std::forward<U>(opt);}
return f(this->as_ok()); return f(this->as_ok());
} }
template<typename F, typename U> template<typename F, typename U>
return_type_of_t<F, value_type const&> detail::return_type_of_t<F, value_type const&>
map_or_else(F&& f, U&& opt) const& map_or_else(F&& f, U&& opt) const&
{ {
if(this->is_err()){return std::forward<U>(opt);} if(this->is_err()){return std::forward<U>(opt);}
return f(this->as_ok()); return f(this->as_ok());
} }
template<typename F, typename U> template<typename F, typename U>
return_type_of_t<F, value_type&&> detail::return_type_of_t<F, value_type&&>
map_or_else(F&& f, U&& opt) && map_or_else(F&& f, U&& opt) &&
{ {
if(this->is_err()){return std::forward<U>(opt);} if(this->is_err()){return std::forward<U>(opt);}
@@ -519,21 +504,21 @@ struct result
// F: E -> U // F: E -> U
// retval: U // retval: U
template<typename F, typename U> template<typename F, typename U>
return_type_of_t<F, error_type&> detail::return_type_of_t<F, error_type&>
map_err_or_else(F&& f, U&& opt) & map_err_or_else(F&& f, U&& opt) &
{ {
if(this->is_ok()){return std::forward<U>(opt);} if(this->is_ok()){return std::forward<U>(opt);}
return f(this->as_err()); return f(this->as_err());
} }
template<typename F, typename U> template<typename F, typename U>
return_type_of_t<F, error_type const&> detail::return_type_of_t<F, error_type const&>
map_err_or_else(F&& f, U&& opt) const& map_err_or_else(F&& f, U&& opt) const&
{ {
if(this->is_ok()){return std::forward<U>(opt);} if(this->is_ok()){return std::forward<U>(opt);}
return f(this->as_err()); return f(this->as_err());
} }
template<typename F, typename U> template<typename F, typename U>
return_type_of_t<F, error_type&&> detail::return_type_of_t<F, error_type&&>
map_err_or_else(F&& f, U&& opt) && map_err_or_else(F&& f, U&& opt) &&
{ {
if(this->is_ok()){return std::forward<U>(opt);} if(this->is_ok()){return std::forward<U>(opt);}
@@ -545,21 +530,21 @@ struct result
// toml::err(error_type) should be convertible to U. // toml::err(error_type) should be convertible to U.
// normally, type U is another result<S, F> and E is convertible to F // normally, type U is another result<S, F> and E is convertible to F
template<typename F> template<typename F>
return_type_of_t<F, value_type&> detail::return_type_of_t<F, value_type&>
and_then(F&& f) & and_then(F&& f) &
{ {
if(this->is_ok()){return f(this->as_ok());} if(this->is_ok()){return f(this->as_ok());}
return err(this->as_err()); return err(this->as_err());
} }
template<typename F> template<typename F>
return_type_of_t<F, value_type const&> detail::return_type_of_t<F, value_type const&>
and_then(F&& f) const& and_then(F&& f) const&
{ {
if(this->is_ok()){return f(this->as_ok());} if(this->is_ok()){return f(this->as_ok());}
return err(this->as_err()); return err(this->as_err());
} }
template<typename F> template<typename F>
return_type_of_t<F, value_type&&> detail::return_type_of_t<F, value_type&&>
and_then(F&& f) && and_then(F&& f) &&
{ {
if(this->is_ok()){return f(std::move(this->as_ok()));} if(this->is_ok()){return f(std::move(this->as_ok()));}
@@ -571,27 +556,47 @@ struct result
// toml::ok(value_type) should be convertible to U. // toml::ok(value_type) should be convertible to U.
// normally, type U is another result<S, F> and T is convertible to S // normally, type U is another result<S, F> and T is convertible to S
template<typename F> template<typename F>
return_type_of_t<F, error_type&> detail::return_type_of_t<F, error_type&>
or_else(F&& f) & or_else(F&& f) &
{ {
if(this->is_err()){return f(this->as_err());} if(this->is_err()){return f(this->as_err());}
return ok(this->as_ok()); return ok(this->as_ok());
} }
template<typename F> template<typename F>
return_type_of_t<F, error_type const&> detail::return_type_of_t<F, error_type const&>
or_else(F&& f) const& or_else(F&& f) const&
{ {
if(this->is_err()){return f(this->as_err());} if(this->is_err()){return f(this->as_err());}
return ok(this->as_ok()); return ok(this->as_ok());
} }
template<typename F> template<typename F>
return_type_of_t<F, error_type&&> detail::return_type_of_t<F, error_type&&>
or_else(F&& f) && or_else(F&& f) &&
{ {
if(this->is_err()){return f(std::move(this->as_err()));} if(this->is_err()){return f(std::move(this->as_err()));}
return ok(std::move(this->as_ok())); return ok(std::move(this->as_ok()));
} }
// if *this is error, returns *this. otherwise, returns other.
result and_other(const result& other) const&
{
return this->is_err() ? *this : other;
}
result and_other(result&& other) &&
{
return this->is_err() ? std::move(*this) : std::move(other);
}
// if *this is okay, returns *this. otherwise, returns other.
result or_other(const result& other) const&
{
return this->is_ok() ? *this : other;
}
result or_other(result&& other) &&
{
return this->is_ok() ? std::move(*this) : std::move(other);
}
void swap(result<T, E>& other) void swap(result<T, E>& other)
{ {
result<T, E> tmp(std::move(*this)); result<T, E> tmp(std::move(*this));
@@ -638,5 +643,22 @@ void swap(result<T, E>& lhs, result<T, E>& rhs)
return; return;
} }
// this might be confusing because it eagerly evaluated, while in the other
// cases operator && and || are short-circuited.
//
// template<typename T, typename E>
// inline result<T, E>
// operator&&(const result<T, E>& lhs, const result<T, E>& rhs) noexcept
// {
// return lhs.is_ok() ? rhs : lhs;
// }
//
// template<typename T, typename E>
// inline result<T, E>
// operator||(const result<T, E>& lhs, const result<T, E>& rhs) noexcept
// {
// return lhs.is_ok() ? lhs : rhs;
// }
} // toml11 } // toml11
#endif// TOML11_RESULT_H #endif// TOML11_RESULT_H

511
toml/serializer.hpp Normal file
View File

@@ -0,0 +1,511 @@
// Copyright Toru Niina 2019.
// Distributed under the MIT License.
#ifndef TOML11_SERIALIZER_HPP
#define TOML11_SERIALIZER_HPP
#include "value.hpp"
#include "lexer.hpp"
#include <limits>
namespace toml
{
struct serializer
{
serializer(const std::size_t w = 80,
const int float_prec = std::numeric_limits<toml::floating>::max_digits10,
const bool can_be_inlined = false,
std::vector<toml::key> ks = {})
: can_be_inlined_(can_be_inlined), float_prec_(float_prec), width_(w),
keys_(std::move(ks))
{}
~serializer() = default;
std::string operator()(const toml::boolean& b) const
{
return b ? "true" : "false";
}
std::string operator()(const integer i) const
{
return std::to_string(i);
}
std::string operator()(const toml::floating f) const
{
std::string token = [=] {
// every float value needs decimal point (or exponent).
std::ostringstream oss;
oss << std::setprecision(float_prec_) << std::showpoint << f;
return oss.str();
}();
if(token.back() == '.') // 1. => 1.0
{
token += '0';
}
const auto e = std::find_if(token.cbegin(), token.cend(),
[](const char c) -> bool {
return c == 'E' || c == 'e';
});
if(e == token.cend())
{
return token; // there is no exponent part. just return it.
}
// zero-prefix in an exponent is not allowed in TOML.
// remove it if it exists.
bool sign_exists = false;
std::size_t zero_prefix = 0;
for(auto iter = std::next(e), iend = token.cend(); iter != iend; ++iter)
{
if(*iter == '+' || *iter == '-'){sign_exists = true; continue;}
if(*iter == '0'){zero_prefix += 1;}
else {break;}
}
if(zero_prefix != 0)
{
const auto offset = std::distance(token.cbegin(), e) +
(sign_exists ? 2 : 1);
token.erase(offset, zero_prefix);
}
return token;
}
std::string operator()(const string& s) const
{
if(s.kind == string_t::basic)
{
if(std::find(s.str.cbegin(), s.str.cend(), '\n') != s.str.cend())
{
// if linefeed is contained, make it multiline-string.
const std::string open("\"\"\"\n");
const std::string close("\\\n\"\"\"");
return open + this->escape_ml_basic_string(s.str) + close;
}
// no linefeed. try to make it oneline-string.
std::string oneline = this->escape_basic_string(s.str);
if(oneline.size() + 2 < width_ || width_ < 2)
{
const std::string quote("\"");
return quote + oneline + quote;
}
// the line is too long compared to the specified width.
// split it into multiple lines.
std::string token("\"\"\"\n");
while(!oneline.empty())
{
if(oneline.size() < width_)
{
token += oneline;
oneline.clear();
}
else if(oneline.at(width_-2) == '\\')
{
token += oneline.substr(0, width_-2);
token += "\\\n";
oneline.erase(0, width_-2);
}
else
{
token += oneline.substr(0, width_-1);
token += "\\\n";
oneline.erase(0, width_-1);
}
}
return token + std::string("\\\n\"\"\"");
}
else // the string `s` is literal-string.
{
if(std::find(s.str.cbegin(), s.str.cend(), '\n') != s.str.cend() ||
std::find(s.str.cbegin(), s.str.cend(), '\'') != s.str.cend() )
{
const std::string open("'''\n");
const std::string close("'''");
return open + s.str + close;
}
else
{
const std::string quote("'");
return quote + s.str + quote;
}
}
}
std::string operator()(const local_date& d) const
{
std::ostringstream oss;
oss << d;
return oss.str();
}
std::string operator()(const local_time& t) const
{
std::ostringstream oss;
oss << t;
return oss.str();
}
std::string operator()(const local_datetime& dt) const
{
std::ostringstream oss;
oss << dt;
return oss.str();
}
std::string operator()(const offset_datetime& odt) const
{
std::ostringstream oss;
oss << odt;
return oss.str();
}
std::string operator()(const array& v) const
{
if(!v.empty() && v.front().is(value_t::Table))// v is an array of tables
{
// if it's not inlined, we need to add `[[table.key]]`.
// but if it can be inlined, we need `table.key = [...]`.
if(this->can_be_inlined_)
{
std::string token;
if(!keys_.empty())
{
token += this->serialize_key(keys_.back());
token += " = ";
}
bool width_exceeds = false;
token += "[\n";
for(const auto& item : v)
{
const auto t =
this->make_inline_table(item.cast<value_t::Table>());
if(t.size() + 1 > width_ || // +1 for the last comma {...},
std::find(t.cbegin(), t.cend(), '\n') != t.cend())
{
width_exceeds = true;
break;
}
token += t;
token += ",\n";
}
if(!width_exceeds)
{
token += "]\n";
return token;
}
// if width_exceeds, serialize it as [[array.of.tables]].
}
std::string token;
for(const auto& item : v)
{
token += "[[";
token += this->serialize_dotted_key(keys_);
token += "]]\n";
token += this->make_multiline_table(item.cast<value_t::Table>());
}
return token;
}
if(v.empty())
{
return std::string("[]");
}
// not an array of tables. normal array. first, try to make it inline.
{
const auto inl = this->make_inline_array(v);
if(inl.size() < this->width_ &&
std::find(inl.cbegin(), inl.cend(), '\n') == inl.cend())
{
return inl;
}
}
// if the length exceeds this->width_, print multiline array
std::string token;
std::string current_line;
token += "[\n";
for(const auto& item : v)
{
auto next_elem = toml::visit(*this, item);
// newline between array-value and comma is not allowed
if(next_elem.back() == '\n'){next_elem.pop_back();}
if(current_line.size() + next_elem.size() + 1 < this->width_)
{
current_line += next_elem;
current_line += ',';
}
else if(current_line.empty())
{
// the next elem cannot be within the width.
token += next_elem;
token += ",\n";
// keep current line empty
}
else // current_line has some tokens and it exceeds width
{
assert(current_line.back() == ',');
token += current_line;
token += '\n';
current_line = next_elem;
current_line += ',';
}
}
if(!current_line.empty())
{
if(current_line.back() != '\n') {current_line += '\n';}
token += current_line;
}
token += "]\n";
return token;
}
std::string operator()(const table& v) const
{
if(this->can_be_inlined_)
{
std::string token;
if(!this->keys_.empty())
{
token += this->serialize_key(this->keys_.back());
token += " = ";
}
token += this->make_inline_table(v);
if(token.size() < this->width_)
{
return token;
}
}
std::string token;
if(!keys_.empty())
{
token += '[';
token += this->serialize_dotted_key(keys_);
token += "]\n";
}
token += this->make_multiline_table(v);
return token;
}
private:
std::string serialize_key(const toml::key& key) const
{
detail::location<toml::key> loc(key, key);
detail::lex_unquoted_key::invoke(loc);
if(loc.iter() == loc.end())
{
return key; // all the tokens are consumed. the key is unquoted-key.
}
std::string token("\"");
token += this->escape_basic_string(key);
token += "\"";
return token;
}
std::string serialize_dotted_key(const std::vector<toml::key>& keys) const
{
std::string token;
if(keys.empty()){return token;}
for(const auto& k : keys)
{
token += this->serialize_key(k);
token += '.';
}
token.erase(token.size() - 1, 1); // remove trailing `.`
return token;
}
std::string escape_basic_string(const std::string& s) const
{
//XXX assuming `s` is a valid utf-8 sequence.
std::string retval;
for(const char c : s)
{
switch(c)
{
case '\\': {retval += "\\\\"; break;}
case '\"': {retval += "\\\""; break;}
case '\b': {retval += "\\b"; break;}
case '\t': {retval += "\\t"; break;}
case '\f': {retval += "\\f"; break;}
case '\n': {retval += "\\n"; break;}
case '\r': {retval += "\\r"; break;}
default : {retval += c; break;}
}
}
return retval;
}
std::string escape_ml_basic_string(const std::string& s) const
{
std::string retval;
for(auto i=s.cbegin(), e=s.cend(); i!=e; ++i)
{
switch(*i)
{
case '\\': {retval += "\\\\"; break;}
case '\"': {retval += "\\\""; break;}
case '\b': {retval += "\\b"; break;}
case '\t': {retval += "\\t"; break;}
case '\f': {retval += "\\f"; break;}
case '\n': {retval += "\n"; break;}
case '\r':
{
if(std::next(i) != e && *std::next(i) == '\n')
{
retval += "\r\n";
++i;
}
else
{
retval += "\\r";
}
break;
}
default: {retval += *i; break;}
}
}
return retval;
}
std::string make_inline_array(const array& v) const
{
std::string token;
token += '[';
bool is_first = true;
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 += ']';
return token;
}
std::string make_inline_table(const table& v) const
{
assert(this->can_be_inlined_);
std::string token;
token += '{';
bool is_first = true;
for(const auto& kv : v)
{
// in inline tables, trailing comma is not allowed (toml-lang #569).
if(is_first) {is_first = false;} else {token += ',';}
token += this->serialize_key(kv.first);
token += '=';
token += visit(serializer(std::numeric_limits<std::size_t>::max(),
this->float_prec_, true), kv.second);
}
token += '}';
return token;
}
std::string make_multiline_table(const table& v) const
{
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]
for(const auto kv : v)
{
if(kv.second.is(value_t::Table) || is_array_of_tables(kv.second))
{
continue;
}
const auto key_and_sep = this->serialize_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);
if(token.back() != '\n')
{
token += '\n';
}
}
// normal tables / array of tables
// after multiline table appeared, the other tables cannot be inline
// because the table would be assigned into the table.
// [foo]
// ...
// bar = {...} # <- bar will be a member of [foo].
bool multiline_table_printed = false;
for(const auto& kv : v)
{
if(!kv.second.is(value_t::Table) && !is_array_of_tables(kv.second))
{
continue; // other stuff are already serialized. skip them.
}
std::vector<toml::key> ks(this->keys_);
ks.push_back(kv.first);
auto tmp = visit(serializer(
this->width_, this->float_prec_, !multiline_table_printed, ks),
kv.second);
if((!multiline_table_printed) &&
std::find(tmp.cbegin(), tmp.cend(), '\n') != tmp.cend())
{
multiline_table_printed = true;
}
else
{
// still inline tables only.
tmp += '\n';
}
token += tmp;
}
return token;
}
bool is_array_of_tables(const value& v) const
{
if(!v.is(value_t::Array)) {return false;}
const auto& a = v.cast<value_t::Array>();
return !a.empty() && a.front().is(value_t::Table);
}
private:
bool can_be_inlined_;
int float_prec_;
std::size_t width_;
std::vector<toml::key> keys_;
};
inline std::string
format(const value& v, std::size_t w = 80,
int fprec = std::numeric_limits<toml::floating>::max_digits10)
{
return visit(serializer(w, fprec, true), v);
}
inline std::string
format(const table& t, std::size_t w = 80,
int fprec = std::numeric_limits<toml::floating>::max_digits10)
{
return serializer(w, fprec, true)(t);
}
template<typename charT, typename traits>
std::basic_ostream<charT, traits>&
operator<<(std::basic_ostream<charT, traits>& os, const value& v)
{
// get status of std::setw().
const std::size_t w = os.width();
const int fprec = os.precision();
os.width(0);
// the root object can't be an inline table. so pass `false`.
os << visit(serializer(w, fprec, false), v);
return os;
}
} // toml
#endif// TOML11_SERIALIZER_HPP

View File

@@ -15,6 +15,9 @@ namespace detail
template<typename T> template<typename T>
using unwrap_t = typename std::decay<T>::type; using unwrap_t = typename std::decay<T>::type;
// ---------------------------------------------------------------------------
// check whether type T is a kind of container/map class
struct has_iterator_impl struct has_iterator_impl
{ {
template<typename T> static std::true_type check(typename T::iterator*); template<typename T> static std::true_type check(typename T::iterator*);
@@ -63,6 +66,9 @@ struct has_resize_method : decltype(has_resize_method_impl::check<T>(nullptr)){}
#undef decltype(...) #undef decltype(...)
#endif #endif
// ---------------------------------------------------------------------------
// C++17 and/or/not
template<typename ...> struct conjunction : std::true_type{}; template<typename ...> struct conjunction : std::true_type{};
template<typename T> struct conjunction<T> : T{}; template<typename T> struct conjunction<T> : T{};
template<typename T, typename ... Ts> template<typename T, typename ... Ts>
@@ -80,6 +86,9 @@ struct disjunction<T, Ts...> :
template<typename T> template<typename T>
struct negation : std::integral_constant<bool, !static_cast<bool>(T::value)>{}; struct negation : std::integral_constant<bool, !static_cast<bool>(T::value)>{};
// ---------------------------------------------------------------------------
// normal type checker
template<typename T> struct is_std_pair : std::false_type{}; template<typename T> struct is_std_pair : std::false_type{};
template<typename T1, typename T2> template<typename T1, typename T2>
struct is_std_pair<std::pair<T1, T2>> : std::true_type{}; struct is_std_pair<std::pair<T1, T2>> : std::true_type{};
@@ -92,7 +101,9 @@ template<typename T> struct is_chrono_duration: std::false_type{};
template<typename Rep, typename Period> template<typename Rep, typename Period>
struct is_chrono_duration<std::chrono::duration<Rep, Period>>: std::true_type{}; struct is_chrono_duration<std::chrono::duration<Rep, Period>>: std::true_type{};
// to use toml::get<std::tuple<T1, T2, ...>> in C++11 // ---------------------------------------------------------------------------
// C++14 index_sequence
template<std::size_t ... Ns> struct index_sequence{}; template<std::size_t ... Ns> struct index_sequence{};
template<typename IS, std::size_t N> struct push_back_index_sequence{}; template<typename IS, std::size_t N> struct push_back_index_sequence{};
@@ -116,6 +127,21 @@ struct index_sequence_maker<0>
template<std::size_t N> template<std::size_t N>
using make_index_sequence = typename index_sequence_maker<N-1>::type; using make_index_sequence = typename index_sequence_maker<N-1>::type;
// ---------------------------------------------------------------------------
// return_type_of_t
#if __cplusplus >= 201703L
template<typename F, typename ... Args>
using return_type_of_t = std::invoke_result_t<F, Args...>;
#else
// result_of is deprecated after C++17
template<typename F, typename ... Args>
using return_type_of_t = typename std::result_of<F(Args...)>::type;
#endif
}// detail }// detail
}//toml }//toml
#endif // TOML_TRAITS #endif // TOML_TRAITS

View File

@@ -137,17 +137,6 @@ template<> struct toml_value_t<LocalDate >{static constexpr value_t value =
template<> struct toml_value_t<LocalTime >{static constexpr value_t value = value_t::LocalTime ;}; template<> struct toml_value_t<LocalTime >{static constexpr value_t value = value_t::LocalTime ;};
template<> struct toml_value_t<Array >{static constexpr value_t value = value_t::Array ;}; template<> struct toml_value_t<Array >{static constexpr value_t value = value_t::Array ;};
template<> struct toml_value_t<Table >{static constexpr value_t value = value_t::Table ;}; template<> struct toml_value_t<Table >{static constexpr value_t value = value_t::Table ;};
template<typename T> constexpr value_t toml_value_t<T>::value;
constexpr value_t toml_value_t<Boolean >::value;
constexpr value_t toml_value_t<Integer >::value;
constexpr value_t toml_value_t<Float >::value;
constexpr value_t toml_value_t<String >::value;
constexpr value_t toml_value_t<OffsetDatetime>::value;
constexpr value_t toml_value_t<LocalDatetime >::value;
constexpr value_t toml_value_t<LocalDate >::value;
constexpr value_t toml_value_t<LocalTime >::value;
constexpr value_t toml_value_t<Array >::value;
constexpr value_t toml_value_t<Table >::value;
template<typename T> template<typename T>
struct is_exact_toml_type : disjunction< struct is_exact_toml_type : disjunction<

View File

@@ -767,6 +767,8 @@ inline bool operator<(const toml::value& lhs, const toml::value& rhs)
return lhs.cast<value_t::Float >() < rhs.cast<value_t::Float >(); return lhs.cast<value_t::Float >() < rhs.cast<value_t::Float >();
case value_t::String : case value_t::String :
return lhs.cast<value_t::String >() < rhs.cast<value_t::String >(); return lhs.cast<value_t::String >() < rhs.cast<value_t::String >();
case value_t::OffsetDatetime:
return lhs.cast<value_t::OffsetDatetime>() < rhs.cast<value_t::OffsetDatetime>();
case value_t::LocalDatetime: case value_t::LocalDatetime:
return lhs.cast<value_t::LocalDatetime>() < rhs.cast<value_t::LocalDatetime>(); return lhs.cast<value_t::LocalDatetime>() < rhs.cast<value_t::LocalDatetime>();
case value_t::LocalDate: case value_t::LocalDate:
@@ -801,17 +803,93 @@ inline bool operator>=(const toml::value& lhs, const toml::value& rhs)
} }
inline std::string format_error(const std::string& err_msg, inline std::string format_error(const std::string& err_msg,
const toml::value& v, const std::string& comment) const toml::value& v, const std::string& comment,
std::vector<std::string> hints = {})
{ {
return detail::format_underline(err_msg, detail::get_region(v), comment); return detail::format_underline(err_msg, detail::get_region(v), comment,
std::move(hints));
} }
inline std::string format_error(const std::string& err_msg, inline std::string format_error(const std::string& err_msg,
const toml::value& v1, const std::string& comment1, const toml::value& v1, const std::string& comment1,
const toml::value& v2, const std::string& comment2) const toml::value& v2, const std::string& comment2,
std::vector<std::string> hints = {})
{ {
return detail::format_underline(err_msg, detail::get_region(v1), comment1, return detail::format_underline(err_msg, detail::get_region(v1), comment1,
detail::get_region(v2), comment2); detail::get_region(v2), comment2,
std::move(hints));
}
template<typename Visitor>
detail::return_type_of_t<Visitor, const toml::boolean&>
visit(Visitor&& visitor, const toml::value& v)
{
switch(v.type())
{
case value_t::Boolean : {return visitor(v.cast<value_t::Boolean >());}
case value_t::Integer : {return visitor(v.cast<value_t::Integer >());}
case value_t::Float : {return visitor(v.cast<value_t::Float >());}
case value_t::String : {return visitor(v.cast<value_t::String >());}
case value_t::OffsetDatetime: {return visitor(v.cast<value_t::OffsetDatetime>());}
case value_t::LocalDatetime : {return visitor(v.cast<value_t::LocalDatetime >());}
case value_t::LocalDate : {return visitor(v.cast<value_t::LocalDate >());}
case value_t::LocalTime : {return visitor(v.cast<value_t::LocalTime >());}
case value_t::Array : {return visitor(v.cast<value_t::Array >());}
case value_t::Table : {return visitor(v.cast<value_t::Table >());}
case value_t::Empty : break;
case value_t::Unknown : break;
default: break;
}
throw std::runtime_error(format_error("[error] toml::visit: toml::value "
"does not have any valid value.", v, "here"));
}
template<typename Visitor>
detail::return_type_of_t<Visitor, toml::boolean&>
visit(Visitor&& visitor, toml::value& v)
{
switch(v.type())
{
case value_t::Boolean : {return visitor(v.cast<value_t::Boolean >());}
case value_t::Integer : {return visitor(v.cast<value_t::Integer >());}
case value_t::Float : {return visitor(v.cast<value_t::Float >());}
case value_t::String : {return visitor(v.cast<value_t::String >());}
case value_t::OffsetDatetime: {return visitor(v.cast<value_t::OffsetDatetime>());}
case value_t::LocalDatetime : {return visitor(v.cast<value_t::LocalDatetime >());}
case value_t::LocalDate : {return visitor(v.cast<value_t::LocalDate >());}
case value_t::LocalTime : {return visitor(v.cast<value_t::LocalTime >());}
case value_t::Array : {return visitor(v.cast<value_t::Array >());}
case value_t::Table : {return visitor(v.cast<value_t::Table >());}
case value_t::Empty : break;
case value_t::Unknown : break;
default: break;
}
throw std::runtime_error(format_error("[error] toml::visit: toml::value "
"does not have any valid value.", v, "here"));
}
template<typename Visitor>
detail::return_type_of_t<Visitor, toml::boolean&>
visit(Visitor&& visitor, toml::value&& v)
{
switch(v.type())
{
case value_t::Boolean : {return visitor(std::move(v.cast<value_t::Boolean >()));}
case value_t::Integer : {return visitor(std::move(v.cast<value_t::Integer >()));}
case value_t::Float : {return visitor(std::move(v.cast<value_t::Float >()));}
case value_t::String : {return visitor(std::move(v.cast<value_t::String >()));}
case value_t::OffsetDatetime: {return visitor(std::move(v.cast<value_t::OffsetDatetime>()));}
case value_t::LocalDatetime : {return visitor(std::move(v.cast<value_t::LocalDatetime >()));}
case value_t::LocalDate : {return visitor(std::move(v.cast<value_t::LocalDate >()));}
case value_t::LocalTime : {return visitor(std::move(v.cast<value_t::LocalTime >()));}
case value_t::Array : {return visitor(std::move(v.cast<value_t::Array >()));}
case value_t::Table : {return visitor(std::move(v.cast<value_t::Table >()));}
case value_t::Empty : break;
case value_t::Unknown : break;
default: break;
}
throw std::runtime_error(format_error("[error] toml::visit: toml::value "
"does not have any valid value.", v, "here"));
} }
}// toml }// toml