forked from lix-project/lix
1967 lines
61 KiB
Markdown
1967 lines
61 KiB
Markdown
|
toml11
|
||
|
======
|
||
|
|
||
|
[![Build Status on GitHub Actions](https://github.com/ToruNiina/toml11/workflows/build/badge.svg)](https://github.com/ToruNiina/toml11/actions)
|
||
|
[![Build Status on TravisCI](https://travis-ci.org/ToruNiina/toml11.svg?branch=master)](https://travis-ci.org/ToruNiina/toml11)
|
||
|
[![Build status on Appveyor](https://ci.appveyor.com/api/projects/status/m2n08a926asvg5mg/branch/master?svg=true)](https://ci.appveyor.com/project/ToruNiina/toml11/branch/master)
|
||
|
[![Build status on CircleCI](https://circleci.com/gh/ToruNiina/toml11/tree/master.svg?style=svg)](https://circleci.com/gh/ToruNiina/toml11/tree/master)
|
||
|
[![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)
|
||
|
|
||
|
toml11 is a C++11 (or later) header-only toml parser/encoder depending only on C++ standard library.
|
||
|
|
||
|
- It is compatible to the latest version of [TOML v1.0.0](https://toml.io/en/v1.0.0).
|
||
|
- It is one of the most TOML standard compliant libraries, tested with [the language agnostic test suite for TOML parsers by BurntSushi](https://github.com/BurntSushi/toml-test).
|
||
|
- It shows highly informative error messages. You can see the error messages about invalid files at [CircleCI](https://circleci.com/gh/ToruNiina/toml11).
|
||
|
- It has configurable container. You can use any random-access containers and key-value maps as backend containers.
|
||
|
- It optionally preserves comments without any overhead.
|
||
|
- It has configurable serializer that supports comments, inline tables, literal strings and multiline strings.
|
||
|
- It supports user-defined type conversion from/into toml values.
|
||
|
- It correctly handles UTF-8 sequences, with or without BOM, both on posix and Windows.
|
||
|
|
||
|
## Example
|
||
|
|
||
|
```cpp
|
||
|
#include <toml.hpp>
|
||
|
#include <iostream>
|
||
|
|
||
|
int main()
|
||
|
{
|
||
|
// ```toml
|
||
|
// title = "an example toml file"
|
||
|
// nums = [3, 1, 4, 1, 5]
|
||
|
// ```
|
||
|
auto data = toml::parse("example.toml");
|
||
|
|
||
|
// find a value with the specified type from a table
|
||
|
std::string title = toml::find<std::string>(data, "title");
|
||
|
|
||
|
// convert the whole array into any container automatically
|
||
|
std::vector<int> nums = toml::find<std::vector<int>>(data, "nums");
|
||
|
|
||
|
// access with STL-like manner
|
||
|
if(!data.contains("foo"))
|
||
|
{
|
||
|
data["foo"] = "bar";
|
||
|
}
|
||
|
|
||
|
// pass a fallback
|
||
|
std::string name = toml::find_or<std::string>(data, "name", "not found");
|
||
|
|
||
|
// width-dependent formatting
|
||
|
std::cout << std::setw(80) << data << std::endl;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## Table of Contents
|
||
|
|
||
|
- [Integration](#integration)
|
||
|
- [Decoding a toml file](#decoding-a-toml-file)
|
||
|
- [In the case of syntax error](#in-the-case-of-syntax-error)
|
||
|
- [Invalid UTF-8 Codepoints](#invalid-utf-8-codepoints)
|
||
|
- [Finding a toml value](#finding-a-toml-value)
|
||
|
- [Finding a value in a table](#finding-a-value-in-a-table)
|
||
|
- [In case of error](#in-case-of-error)
|
||
|
- [Dotted keys](#dotted-keys)
|
||
|
- [Casting a toml value](#casting-a-toml-value)
|
||
|
- [Checking value type](#checking-value-type)
|
||
|
- [More about conversion](#more-about-conversion)
|
||
|
- [Converting an array](#converting-an-array)
|
||
|
- [Converting a table](#converting-a-table)
|
||
|
- [Getting an array of tables](#getting-an-array-of-tables)
|
||
|
- [Cost of conversion](#cost-of-conversion)
|
||
|
- [Converting datetime and its variants](#converting-datetime-and-its-variants)
|
||
|
- [Getting with a fallback](#getting-with-a-fallback)
|
||
|
- [Expecting conversion](#expecting-conversion)
|
||
|
- [Visiting a toml::value](#visiting-a-tomlvalue)
|
||
|
- [Constructing a toml::value](#constructing-a-tomlvalue)
|
||
|
- [Preserving Comments](#preserving-comments)
|
||
|
- [Customizing containers](#customizing-containers)
|
||
|
- [TOML literal](#toml-literal)
|
||
|
- [Conversion between toml value and arbitrary types](#conversion-between-toml-value-and-arbitrary-types)
|
||
|
- [Formatting user-defined error messages](#formatting-user-defined-error-messages)
|
||
|
- [Obtaining location information](#obtaining-location-information)
|
||
|
- [Exceptions](#exceptions)
|
||
|
- [Colorize Error Messages](#colorize-error-messages)
|
||
|
- [Serializing TOML data](#serializing-toml-data)
|
||
|
- [Underlying types](#underlying-types)
|
||
|
- [Unreleased TOML features](#unreleased-toml-features)
|
||
|
- [Breaking Changes from v2](#breaking-changes-from-v2)
|
||
|
- [Running Tests](#running-tests)
|
||
|
- [Contributors](#contributors)
|
||
|
- [Licensing Terms](#licensing-terms)
|
||
|
|
||
|
## Integration
|
||
|
|
||
|
Just include the file after adding it to the include path.
|
||
|
|
||
|
```cpp
|
||
|
#include <toml.hpp> // that's all! now you can use it.
|
||
|
#include <iostream>
|
||
|
|
||
|
int main()
|
||
|
{
|
||
|
const auto data = toml::parse("example.toml");
|
||
|
const auto title = toml::find<std::string>(data, "title");
|
||
|
std::cout << "the title is " << title << std::endl;
|
||
|
return 0;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
The convenient way is to add this repository as a git-submodule or to install
|
||
|
it in your system by CMake.
|
||
|
|
||
|
Note for MSVC: We recommend to set `/Zc:__cplusplus` to detect C++ version correctly.
|
||
|
|
||
|
## Decoding a toml file
|
||
|
|
||
|
To parse a toml file, the only thing you have to do is
|
||
|
to pass a filename to the `toml::parse` function.
|
||
|
|
||
|
```cpp
|
||
|
const std::string fname("sample.toml");
|
||
|
const toml::value data = toml::parse(fname);
|
||
|
```
|
||
|
|
||
|
As required by the TOML specification, the top-level value is always a table.
|
||
|
You can find a value inside it, cast it into a table explicitly, and insert it as a value into other `toml::value`.
|
||
|
|
||
|
If it encounters an error while opening a file, it will throw `std::runtime_error`.
|
||
|
|
||
|
You can also pass a `std::istream` to the `toml::parse` function.
|
||
|
To show a filename in an error message, however, it is recommended to pass the
|
||
|
filename with the stream.
|
||
|
|
||
|
```cpp
|
||
|
std::ifstream ifs("sample.toml", std::ios_base::binary);
|
||
|
assert(ifs.good());
|
||
|
const auto data = toml::parse(ifs, /*optional -> */ "sample.toml");
|
||
|
```
|
||
|
|
||
|
**Note**: When you are **on Windows, open a file in binary mode**.
|
||
|
If a file is opened in text-mode, CRLF ("\r\n") will automatically be
|
||
|
converted to LF ("\n") and this causes inconsistency between file size
|
||
|
and the contents that would be read. This causes weird error.
|
||
|
|
||
|
### In the case of syntax error
|
||
|
|
||
|
If there is a syntax error in a toml file, `toml::parse` will throw
|
||
|
`toml::syntax_error` that inherits `std::exception`.
|
||
|
|
||
|
toml11 has clean and informative error messages inspired by Rust and
|
||
|
it looks like the following.
|
||
|
|
||
|
```console
|
||
|
terminate called after throwing an instance of 'toml::syntax_error'
|
||
|
what(): [error] toml::parse_table: invalid line format # error description
|
||
|
--> example.toml # file name
|
||
|
3 | a = 42 = true # line num and content
|
||
|
| ^------ expected newline, but got '='. # error reason
|
||
|
```
|
||
|
|
||
|
If you (mistakenly) duplicate tables and got an error, it is helpful to see
|
||
|
where they are. toml11 shows both at the same time like the following.
|
||
|
|
||
|
```console
|
||
|
terminate called after throwing an instance of 'toml::syntax_error'
|
||
|
what(): [error] toml::insert_value: table ("table") already exists.
|
||
|
--> duplicate-table.toml
|
||
|
1 | [table]
|
||
|
| ~~~~~~~ table already exists here
|
||
|
...
|
||
|
3 | [table]
|
||
|
| ~~~~~~~ table defined twice
|
||
|
```
|
||
|
|
||
|
When toml11 encounters a malformed value, it tries to detect what type it is.
|
||
|
Then it shows hints to fix the format. An error message while reading one of
|
||
|
the malformed files in [the language agnostic test suite](https://github.com/BurntSushi/toml-test).
|
||
|
is shown below.
|
||
|
|
||
|
```console
|
||
|
what(): [error] bad time: should be HH:MM:SS.subsec
|
||
|
--> ./datetime-malformed-no-secs.toml
|
||
|
1 | no-secs = 1987-07-05T17:45Z
|
||
|
| ^------- HH:MM:SS.subsec
|
||
|
|
|
||
|
Hint: pass: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999
|
||
|
Hint: fail: 1979-05-27T7:32:00, 1979-05-27 17:32
|
||
|
```
|
||
|
|
||
|
You can find other examples in a job named `output_result` on
|
||
|
[CircleCI](https://circleci.com/gh/ToruNiina/toml11).
|
||
|
|
||
|
Since the error message generation is generally a difficult task, the current
|
||
|
status is not ideal. If you encounter a weird error message, please let us know
|
||
|
and contribute to improve the quality!
|
||
|
|
||
|
### Invalid UTF-8 codepoints
|
||
|
|
||
|
It throws `syntax_error` if a value of an escape sequence
|
||
|
representing unicode character is not a valid UTF-8 codepoint.
|
||
|
|
||
|
```console
|
||
|
what(): [error] toml::read_utf8_codepoint: input codepoint is too large.
|
||
|
--> utf8.toml
|
||
|
1 | exceeds_unicode = "\U0011FFFF example"
|
||
|
| ^--------- should be in [0x00..0x10FFFF]
|
||
|
```
|
||
|
|
||
|
## Finding a toml value
|
||
|
|
||
|
After parsing successfully, you can obtain the values from the result of
|
||
|
`toml::parse` using `toml::find` function.
|
||
|
|
||
|
```toml
|
||
|
# sample.toml
|
||
|
answer = 42
|
||
|
pi = 3.14
|
||
|
numbers = [1,2,3]
|
||
|
time = 1979-05-27T07:32:00Z
|
||
|
```
|
||
|
|
||
|
``` cpp
|
||
|
const auto data = toml::parse("sample.toml");
|
||
|
const auto answer = toml::find<std::int64_t >(data, "answer");
|
||
|
const auto pi = toml::find<double >(data, "pi");
|
||
|
const auto numbers = toml::find<std::vector<int>>(data, "numbers");
|
||
|
const auto timepoint = toml::find<std::chrono::system_clock::time_point>(data, "time");
|
||
|
```
|
||
|
|
||
|
By default, `toml::find` returns a `toml::value`.
|
||
|
|
||
|
```cpp
|
||
|
const toml::value& answer = toml::find(data, "answer");
|
||
|
```
|
||
|
|
||
|
When you pass an exact TOML type that does not require type conversion,
|
||
|
`toml::find` returns a reference without copying the value.
|
||
|
|
||
|
```cpp
|
||
|
const auto data = toml::parse("sample.toml");
|
||
|
const auto& answer = toml::find<toml::integer>(data, "answer");
|
||
|
```
|
||
|
|
||
|
If the specified type requires conversion, you can't take a reference to the value.
|
||
|
See also [underlying types](#underlying-types).
|
||
|
|
||
|
**NOTE**: For some technical reason, automatic conversion between `integer` and
|
||
|
`floating` is not supported. If you want to get a floating value even if a value
|
||
|
has integer value, you need to convert it manually after obtaining a value,
|
||
|
like the following.
|
||
|
|
||
|
```cpp
|
||
|
const auto vx = toml::find(data, "x");
|
||
|
double x = vx.is_floating() ? vx.as_floating(std::nothrow) :
|
||
|
static_cast<double>(vx.as_integer()); // it throws if vx is neither
|
||
|
// floating nor integer.
|
||
|
```
|
||
|
|
||
|
### Finding a value in a table
|
||
|
|
||
|
There are several way to get a value defined in a table.
|
||
|
First, you can get a table as a normal value and find a value from the table.
|
||
|
|
||
|
```toml
|
||
|
[fruit]
|
||
|
name = "apple"
|
||
|
[fruit.physical]
|
||
|
color = "red"
|
||
|
shape = "round"
|
||
|
```
|
||
|
|
||
|
``` cpp
|
||
|
const auto data = toml::parse("fruit.toml");
|
||
|
const auto& fruit = toml::find(data, "fruit");
|
||
|
const auto name = toml::find<std::string>(fruit, "name");
|
||
|
|
||
|
const auto& physical = toml::find(fruit, "physical");
|
||
|
const auto color = toml::find<std::string>(physical, "color");
|
||
|
const auto shape = toml::find<std::string>(physical, "shape");
|
||
|
```
|
||
|
|
||
|
Here, variable `fruit` is a `toml::value` and can be used as the first argument
|
||
|
of `toml::find`.
|
||
|
|
||
|
Second, you can pass as many arguments as the number of subtables to `toml::find`.
|
||
|
|
||
|
```cpp
|
||
|
const auto data = toml::parse("fruit.toml");
|
||
|
const auto color = toml::find<std::string>(data, "fruit", "physical", "color");
|
||
|
const auto shape = toml::find<std::string>(data, "fruit", "physical", "shape");
|
||
|
```
|
||
|
|
||
|
### Finding a value in an array
|
||
|
|
||
|
You can find n-th value in an array by `toml::find`.
|
||
|
|
||
|
```toml
|
||
|
values = ["foo", "bar", "baz"]
|
||
|
```
|
||
|
|
||
|
``` cpp
|
||
|
const auto data = toml::parse("sample.toml");
|
||
|
const auto values = toml::find(data, "values");
|
||
|
const auto bar = toml::find<std::string>(values, 1);
|
||
|
```
|
||
|
|
||
|
`toml::find` can also search array recursively.
|
||
|
|
||
|
```cpp
|
||
|
const auto data = toml::parse("fruit.toml");
|
||
|
const auto bar = toml::find<std::string>(data, "values", 1);
|
||
|
```
|
||
|
|
||
|
Before calling `toml::find`, you can check if a value corresponding to a key
|
||
|
exists. You can use both `bool toml::value::contains(const key&) const` and
|
||
|
`std::size_t toml::value::count(const key&) const`. Those behaves like the
|
||
|
`std::map::contains` and `std::map::count`.
|
||
|
|
||
|
```cpp
|
||
|
const auto data = toml::parse("fruit.toml");
|
||
|
if(data.contains("fruit") && data.at("fruit").count("physical") != 0)
|
||
|
{
|
||
|
// ...
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### In case of error
|
||
|
|
||
|
If the value does not exist, `toml::find` throws `std::out_of_range` with the
|
||
|
location of the table.
|
||
|
|
||
|
```console
|
||
|
terminate called after throwing an instance of 'std::out_of_range'
|
||
|
what(): [error] key "answer" not found
|
||
|
--> example.toml
|
||
|
6 | [tab]
|
||
|
| ~~~~~ in this table
|
||
|
```
|
||
|
|
||
|
----
|
||
|
|
||
|
If the specified type differs from the actual value contained, it throws
|
||
|
`toml::type_error` that inherits `std::exception`.
|
||
|
|
||
|
Similar to the case of syntax error, toml11 also displays clean error messages.
|
||
|
The error message when you choose `int` to get `string` value would be like this.
|
||
|
|
||
|
```console
|
||
|
terminate called after throwing an instance of 'toml::type_error'
|
||
|
what(): [error] toml::value bad_cast to integer
|
||
|
--> example.toml
|
||
|
3 | title = "TOML Example"
|
||
|
| ~~~~~~~~~~~~~~ the actual type is string
|
||
|
```
|
||
|
|
||
|
**NOTE**: In order to show this kind of error message, all the toml values have
|
||
|
a pointer to represent its range in a file. The entire contents of a file is
|
||
|
shared by `toml::value`s and remains on the heap memory. It is recommended to
|
||
|
destruct all the `toml::value` classes after configuring your application
|
||
|
if you have a large TOML file compared to the memory resource.
|
||
|
|
||
|
### 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
|
||
|
physical.color = "orange"
|
||
|
physical.shape = "round"
|
||
|
```
|
||
|
|
||
|
This is equivalent to the following.
|
||
|
|
||
|
```toml
|
||
|
[physical]
|
||
|
color = "orange"
|
||
|
shape = "round"
|
||
|
```
|
||
|
|
||
|
You can get both of the above tables with the same c++ code.
|
||
|
|
||
|
```cpp
|
||
|
const auto physical = toml::find(data, "physical");
|
||
|
const auto color = toml::find<std::string>(physical, "color");
|
||
|
```
|
||
|
|
||
|
The following code does not work for the above toml file.
|
||
|
|
||
|
```cpp
|
||
|
// XXX this does not work!
|
||
|
const auto color = toml::find<std::string>(data, "physical.color");
|
||
|
```
|
||
|
|
||
|
The above code works with the following toml file.
|
||
|
|
||
|
```toml
|
||
|
"physical.color" = "orange"
|
||
|
# equivalent to {"physical.color": "orange"},
|
||
|
# NOT {"physical": {"color": "orange"}}.
|
||
|
```
|
||
|
|
||
|
|
||
|
## Casting a toml value
|
||
|
|
||
|
### `toml::get`
|
||
|
|
||
|
`toml::parse` returns `toml::value`. `toml::value` is a union type that can
|
||
|
contain one of the following types.
|
||
|
|
||
|
- `toml::boolean` (`bool`)
|
||
|
- `toml::integer` (`std::int64_t`)
|
||
|
- `toml::floating` (`double`)
|
||
|
- `toml::string` (a type convertible to std::string)
|
||
|
- `toml::local_date`
|
||
|
- `toml::local_time`
|
||
|
- `toml::local_datetime`
|
||
|
- `toml::offset_datetime`
|
||
|
- `toml::array` (by default, `std::vector<toml::value>`)
|
||
|
- It depends. See [customizing containers](#customizing-containers) for detail.
|
||
|
- `toml::table` (by default, `std::unordered_map<toml::key, toml::value>`)
|
||
|
- It depends. See [customizing containers](#customizing-containers) for detail.
|
||
|
|
||
|
To get a value inside, you can use `toml::get<T>()`. The usage is the same as
|
||
|
`toml::find<T>` (actually, `toml::find` internally uses `toml::get` after casting
|
||
|
a value to `toml::table`).
|
||
|
|
||
|
``` cpp
|
||
|
const toml::value data = toml::parse("sample.toml");
|
||
|
const toml::value answer_ = toml::get<toml::table >(data).at("answer");
|
||
|
const std::int64_t answer = toml::get<std::int64_t>(answer_);
|
||
|
```
|
||
|
|
||
|
When you pass an exact TOML type that does not require type conversion,
|
||
|
`toml::get` returns a reference through which you can modify the content
|
||
|
(if the `toml::value` is `const`, it returns `const` reference).
|
||
|
|
||
|
```cpp
|
||
|
toml::value data = toml::parse("sample.toml");
|
||
|
toml::value answer_ = toml::get<toml::table >(data).at("answer");
|
||
|
toml::integer& answer = toml::get<toml::integer>(answer_);
|
||
|
answer = 6 * 9; // write to data.answer. now `answer_` contains 54.
|
||
|
```
|
||
|
|
||
|
If the specified type requires conversion, you can't take a reference to the value.
|
||
|
See also [underlying types](#underlying-types).
|
||
|
|
||
|
It also throws a `toml::type_error` if the type differs.
|
||
|
|
||
|
### `as_xxx`
|
||
|
|
||
|
You can also use a member function to cast a value.
|
||
|
|
||
|
```cpp
|
||
|
const std::int64_t answer = data.as_table().at("answer").as_integer();
|
||
|
```
|
||
|
|
||
|
It also throws a `toml::type_error` if the type differs. If you are sure that
|
||
|
the value `v` contains a value of the specified type, you can suppress checking
|
||
|
by passing `std::nothrow`.
|
||
|
|
||
|
```cpp
|
||
|
const auto& answer = data.as_table().at("answer");
|
||
|
if(answer.is_integer() && answer.as_integer(std::nothrow) == 42)
|
||
|
{
|
||
|
std::cout << "value is 42" << std::endl;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
If `std::nothrow` is passed, the functions are marked as noexcept.
|
||
|
|
||
|
By casting a `toml::value` into an array or a table, you can iterate over the
|
||
|
elements.
|
||
|
|
||
|
```cpp
|
||
|
const auto data = toml::parse("example.toml");
|
||
|
std::cout << "keys in the top-level table are the following: \n";
|
||
|
for(const auto& [k, v] : data.as_table())
|
||
|
{
|
||
|
std::cout << k << '\n';
|
||
|
}
|
||
|
|
||
|
const auto& fruits = toml::find(data, "fruits");
|
||
|
for(const auto& v : fruits.as_array())
|
||
|
{
|
||
|
std::cout << toml::find<std::string>(v, "name") << '\n';
|
||
|
}
|
||
|
```
|
||
|
|
||
|
The full list of the functions is below.
|
||
|
|
||
|
```cpp
|
||
|
namespace toml {
|
||
|
class value {
|
||
|
// ...
|
||
|
const boolean& as_boolean() const&;
|
||
|
const integer& as_integer() const&;
|
||
|
const floating& as_floating() const&;
|
||
|
const string& as_string() const&;
|
||
|
const offset_datetime& as_offset_datetime() const&;
|
||
|
const local_datetime& as_local_datetime() const&;
|
||
|
const local_date& as_local_date() const&;
|
||
|
const local_time& as_local_time() const&;
|
||
|
const array& as_array() const&;
|
||
|
const table& as_table() const&;
|
||
|
// --------------------------------------------------------
|
||
|
// non-const version
|
||
|
boolean& as_boolean() &;
|
||
|
// ditto...
|
||
|
// --------------------------------------------------------
|
||
|
// rvalue version
|
||
|
boolean&& as_boolean() &&;
|
||
|
// ditto...
|
||
|
|
||
|
// --------------------------------------------------------
|
||
|
// noexcept versions ...
|
||
|
const boolean& as_boolean(const std::nothrow_t&) const& noexcept;
|
||
|
boolean& as_boolean(const std::nothrow_t&) & noexcept;
|
||
|
boolean&& as_boolean(const std::nothrow_t&) && noexcept;
|
||
|
// ditto...
|
||
|
};
|
||
|
} // toml
|
||
|
```
|
||
|
|
||
|
### `at()`
|
||
|
|
||
|
You can access to the element of a table and an array by `toml::basic_value::at`.
|
||
|
|
||
|
```cpp
|
||
|
const toml::value v{1,2,3,4,5};
|
||
|
std::cout << v.at(2).as_integer() << std::endl; // 3
|
||
|
|
||
|
const toml::value v{{"foo", 42}, {"bar", 3.14}};
|
||
|
std::cout << v.at("foo").as_integer() << std::endl; // 42
|
||
|
```
|
||
|
|
||
|
If an invalid key (integer for a table, string for an array), it throws
|
||
|
`toml::type_error` for the conversion. If the provided key is out-of-range,
|
||
|
it throws `std::out_of_range`.
|
||
|
|
||
|
Note that, although `std::string` has `at()` member function, `toml::value::at`
|
||
|
throws if the contained type is a string. Because `std::string` does not
|
||
|
contain `toml::value`.
|
||
|
|
||
|
### `operator[]`
|
||
|
|
||
|
You can also access to the element of a table and an array by
|
||
|
`toml::basic_value::operator[]`.
|
||
|
|
||
|
```cpp
|
||
|
const toml::value v{1,2,3,4,5};
|
||
|
std::cout << v[2].as_integer() << std::endl; // 3
|
||
|
|
||
|
const toml::value v{{"foo", 42}, {"bar", 3.14}};
|
||
|
std::cout << v["foo"].as_integer() << std::endl; // 42
|
||
|
```
|
||
|
|
||
|
When you access to a `toml::value` that is not initialized yet via
|
||
|
`operator[](const std::string&)`, the `toml::value` will be a table,
|
||
|
just like the `std::map`.
|
||
|
|
||
|
```cpp
|
||
|
toml::value v; // not initialized as a table.
|
||
|
v["foo"] = 42; // OK. `v` will be a table.
|
||
|
```
|
||
|
|
||
|
Contrary, if you access to a `toml::value` that contains an array via `operator[]`,
|
||
|
it does not check anything. It converts `toml::value` without type check and then
|
||
|
access to the n-th element without boundary check, just like the `std::vector::operator[]`.
|
||
|
|
||
|
```cpp
|
||
|
toml::value v; // not initialized as an array
|
||
|
v[2] = 42; // error! UB
|
||
|
```
|
||
|
|
||
|
Please make sure that the `toml::value` has an array inside when you access to
|
||
|
its element via `operator[]`.
|
||
|
|
||
|
## Checking value type
|
||
|
|
||
|
You can check the type of a value by `is_xxx` function.
|
||
|
|
||
|
```cpp
|
||
|
const toml::value v = /* ... */;
|
||
|
if(v.is_integer())
|
||
|
{
|
||
|
std::cout << "value is an integer" << std::endl;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
The complete list of the functions is below.
|
||
|
|
||
|
```cpp
|
||
|
namespace toml {
|
||
|
class value {
|
||
|
// ...
|
||
|
bool is_boolean() const noexcept;
|
||
|
bool is_integer() const noexcept;
|
||
|
bool is_floating() const noexcept;
|
||
|
bool is_string() const noexcept;
|
||
|
bool is_offset_datetime() const noexcept;
|
||
|
bool is_local_datetime() const noexcept;
|
||
|
bool is_local_date() const noexcept;
|
||
|
bool is_local_time() const noexcept;
|
||
|
bool is_array() const noexcept;
|
||
|
bool is_table() const noexcept;
|
||
|
bool is_uninitialized() const noexcept;
|
||
|
// ...
|
||
|
};
|
||
|
} // toml
|
||
|
```
|
||
|
|
||
|
Also, you can get `enum class value_t` from `toml::value::type()`.
|
||
|
|
||
|
```cpp
|
||
|
switch(data.at("something").type())
|
||
|
{
|
||
|
case toml::value_t::integer: /*do some stuff*/ ; break;
|
||
|
case toml::value_t::floating: /*do some stuff*/ ; break;
|
||
|
case toml::value_t::string : /*do some stuff*/ ; break;
|
||
|
default : throw std::runtime_error(
|
||
|
"unexpected type : " + toml::stringize(data.at("something").type()));
|
||
|
}
|
||
|
```
|
||
|
|
||
|
The complete list of the `enum`s can be found in the section
|
||
|
[underlying types](#underlying-types).
|
||
|
|
||
|
The `enum`s can be used as a parameter of `toml::value::is` function like the following.
|
||
|
|
||
|
```cpp
|
||
|
toml::value v = /* ... */;
|
||
|
if(v.is(toml::value_t::boolean)) // ...
|
||
|
```
|
||
|
|
||
|
## More about conversion
|
||
|
|
||
|
Since `toml::find` internally uses `toml::get`, all the following examples work
|
||
|
with both `toml::get` and `toml::find`.
|
||
|
|
||
|
### Converting an array
|
||
|
|
||
|
You can get any kind of `container` class from a `toml::array`
|
||
|
except for `map`-like classes.
|
||
|
|
||
|
``` cpp
|
||
|
// # sample.toml
|
||
|
// numbers = [1,2,3]
|
||
|
|
||
|
const auto numbers = toml::find(data, "numbers");
|
||
|
|
||
|
const auto vc = toml::get<std::vector<int> >(numbers);
|
||
|
const auto ls = toml::get<std::list<int> >(numbers);
|
||
|
const auto dq = toml::get<std::deque<int> >(numbers);
|
||
|
const auto ar = toml::get<std::array<int, 3>>(numbers);
|
||
|
// if the size of data.at("numbers") is larger than that of std::array,
|
||
|
// it will throw toml::type_error because std::array is not resizable.
|
||
|
```
|
||
|
|
||
|
Surprisingly, you can convert `toml::array` into `std::pair` and `std::tuple`.
|
||
|
|
||
|
```cpp
|
||
|
// numbers = [1,2,3]
|
||
|
const auto tp = toml::get<std::tuple<short, int, unsigned int>>(numbers);
|
||
|
```
|
||
|
|
||
|
This functionality is helpful when you have a toml file like the following.
|
||
|
|
||
|
```toml
|
||
|
array_of_arrays = [[1, 2, 3], ["foo", "bar", "baz"]] # toml allows this
|
||
|
```
|
||
|
|
||
|
What is the corresponding C++ type?
|
||
|
Obviously, it is a `std::pair` of `std::vector`s.
|
||
|
|
||
|
```cpp
|
||
|
const auto array_of_arrays = toml::find(data, "array_of_arrays");
|
||
|
const auto aofa = toml::get<
|
||
|
std::pair<std::vector<int>, std::vector<std::string>>
|
||
|
>(array_of_arrays);
|
||
|
```
|
||
|
|
||
|
If you don't know the type of the elements, you can use `toml::array`,
|
||
|
which is a `std::vector` of `toml::value`, instead.
|
||
|
|
||
|
```cpp
|
||
|
const auto a_of_a = toml::get<toml::array>(array_of_arrays);
|
||
|
const auto first = toml::get<std::vector<int>>(a_of_a.at(0));
|
||
|
```
|
||
|
|
||
|
You can change the implementation of `toml::array` with `std::deque` or some
|
||
|
other array-like container. See [Customizing containers](#customizing-containers)
|
||
|
for detail.
|
||
|
|
||
|
### Converting a table
|
||
|
|
||
|
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
|
||
|
[tab]
|
||
|
key1 = "foo" # all the values are
|
||
|
key2 = "bar" # toml String
|
||
|
```
|
||
|
|
||
|
```cpp
|
||
|
const auto data = toml::parse("sample.toml");
|
||
|
const auto tab = toml::find<std::map<std::string, std::string>>(data, "tab");
|
||
|
std::cout << tab["key1"] << std::endl; // foo
|
||
|
std::cout << tab["key2"] << std::endl; // bar
|
||
|
```
|
||
|
|
||
|
But since `toml::table` is just an alias of `std::unordered_map<toml::key, toml::value>`,
|
||
|
normally you don't need to convert it because it has all the functionalities that
|
||
|
`std::unordered_map` has (e.g. `operator[]`, `count`, and `find`). In most cases
|
||
|
`toml::table` is sufficient.
|
||
|
|
||
|
```cpp
|
||
|
toml::table tab = toml::get<toml::table>(data);
|
||
|
if(data.count("title") != 0)
|
||
|
{
|
||
|
data["title"] = std::string("TOML example");
|
||
|
}
|
||
|
```
|
||
|
|
||
|
You can change the implementation of `toml::table` with `std::map` or some
|
||
|
other map-like container. See [Customizing containers](#customizing-containers)
|
||
|
for detail.
|
||
|
|
||
|
### Getting an array of tables
|
||
|
|
||
|
An array of tables is just an array of tables.
|
||
|
You can get it in completely the same way as the other arrays and tables.
|
||
|
|
||
|
```toml
|
||
|
# sample.toml
|
||
|
array_of_inline_tables = [{key = "value1"}, {key = "value2"}, {key = "value3"}]
|
||
|
|
||
|
[[array_of_tables]]
|
||
|
key = "value4"
|
||
|
[[array_of_tables]]
|
||
|
key = "value5"
|
||
|
[[array_of_tables]]
|
||
|
key = "value6"
|
||
|
```
|
||
|
|
||
|
```cpp
|
||
|
const auto data = toml::parse("sample.toml");
|
||
|
const auto aot1 = toml::find<std::vector<toml::table>>(data, "array_of_inline_tables");
|
||
|
const auto aot2 = toml::find<std::vector<toml::table>>(data, "array_of_tables");
|
||
|
```
|
||
|
|
||
|
### Cost of conversion
|
||
|
|
||
|
Although conversion through `toml::(get|find)` is convenient, it has additional
|
||
|
copy-cost because it copies data contained in `toml::value` to the
|
||
|
user-specified type. Of course in some cases this overhead is not ignorable.
|
||
|
|
||
|
```cpp
|
||
|
// the following code constructs a std::vector.
|
||
|
// it requires heap allocation for vector and element conversion.
|
||
|
const auto array = toml::find<std::vector<int>>(data, "foo");
|
||
|
```
|
||
|
|
||
|
By passing the exact types, `toml::get` returns reference that has no overhead.
|
||
|
|
||
|
``` cpp
|
||
|
const auto& tab = toml::find<toml::table>(data, "tab");
|
||
|
const auto& numbers = toml::find<toml::array>(data, "numbers");
|
||
|
```
|
||
|
|
||
|
Also, `as_xxx` are zero-overhead because they always return a reference.
|
||
|
|
||
|
``` cpp
|
||
|
const auto& tab = toml::find(data, "tab" ).as_table();
|
||
|
const auto& numbers = toml::find(data, "numbers").as_array();
|
||
|
```
|
||
|
|
||
|
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
|
||
|
const auto& num0 = toml::get<toml::integer>(numbers.at(0));
|
||
|
const auto& num1 = toml::get<toml::integer>(numbers.at(1));
|
||
|
const auto& num2 = toml::get<toml::integer>(numbers.at(2));
|
||
|
```
|
||
|
|
||
|
### Converting datetime and its variants
|
||
|
|
||
|
TOML v0.5.0 has 4 different datetime objects, `local_date`, `local_time`,
|
||
|
`local_datetime`, and `offset_datetime`.
|
||
|
|
||
|
Since `local_date`, `local_datetime`, and `offset_datetime` represent a time
|
||
|
point, you can convert them to `std::chrono::system_clock::time_point`.
|
||
|
|
||
|
Contrary, `local_time` does not represents a time point because they lack a
|
||
|
date information, but it can be converted to `std::chrono::duration` that
|
||
|
represents a duration from the beginning of the day, `00:00:00.000`.
|
||
|
|
||
|
```toml
|
||
|
# sample.toml
|
||
|
date = 2018-12-23
|
||
|
time = 12:30:00
|
||
|
l_dt = 2018-12-23T12:30:00
|
||
|
o_dt = 2018-12-23T12:30:00+09:30
|
||
|
```
|
||
|
|
||
|
```cpp
|
||
|
const auto data = toml::parse("sample.toml");
|
||
|
|
||
|
const auto date = toml::get<std::chrono::system_clock::time_point>(data.at("date"));
|
||
|
const auto l_dt = toml::get<std::chrono::system_clock::time_point>(data.at("l_dt"));
|
||
|
const auto o_dt = toml::get<std::chrono::system_clock::time_point>(data.at("o_dt"));
|
||
|
|
||
|
const auto time = toml::get<std::chrono::minutes>(data.at("time")); // 12 * 60 + 30 min
|
||
|
```
|
||
|
|
||
|
`local_date` and `local_datetime` are assumed to be in the local timezone when
|
||
|
they are converted into `time_point`. On the other hand, `offset_datetime` only
|
||
|
uses the offset part of the data and it does not take local timezone into account.
|
||
|
|
||
|
To contain datetime data, toml11 defines its own datetime types.
|
||
|
For more detail, you can see the definitions in [toml/datetime.hpp](toml/datetime.hpp).
|
||
|
|
||
|
## Getting with a fallback
|
||
|
|
||
|
`toml::find_or` returns a default value if the value is not found or has a
|
||
|
different type.
|
||
|
|
||
|
```cpp
|
||
|
const auto data = toml::parse("example.toml");
|
||
|
const auto num = toml::find_or(data, "num", 42);
|
||
|
```
|
||
|
|
||
|
It works recursively if you pass several keys for subtables.
|
||
|
In that case, the last argument is considered to be the optional value.
|
||
|
All other arguments between `toml::value` and the optinoal value are considered as keys.
|
||
|
|
||
|
```cpp
|
||
|
// [fruit.physical]
|
||
|
// color = "red"
|
||
|
auto data = toml::parse("fruit.toml");
|
||
|
auto color = toml::find_or(data, "fruit", "physical", "color", "red");
|
||
|
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^
|
||
|
// arguments optional value
|
||
|
```
|
||
|
|
||
|
Also, `toml::get_or` returns a default value if `toml::get<T>` failed.
|
||
|
|
||
|
```cpp
|
||
|
toml::value v("foo"); // v contains String
|
||
|
const int value = toml::get_or(v, 42); // conversion fails. it returns 42.
|
||
|
```
|
||
|
|
||
|
These functions automatically deduce what type you want to get
|
||
|
from the default value you passed.
|
||
|
|
||
|
To get a reference through this function, take care about the default value.
|
||
|
|
||
|
```cpp
|
||
|
toml::value v("foo"); // v contains String
|
||
|
toml::integer& i = toml::get_or(v, 42); // does not work because binding `42`
|
||
|
// to `integer&` is invalid
|
||
|
toml::integer opt = 42;
|
||
|
toml::integer& i = toml::get_or(v, opt); // this works.
|
||
|
```
|
||
|
|
||
|
## Expecting conversion
|
||
|
|
||
|
By using `toml::expect`, you will get your expected value or an error message
|
||
|
without throwing `toml::type_error`.
|
||
|
|
||
|
```cpp
|
||
|
const auto value = toml::expect<std::string>(data.at("title"));
|
||
|
if(value.is_ok()) {
|
||
|
std::cout << value.unwrap() << std::endl;
|
||
|
} else {
|
||
|
std::cout << value.unwrap_err() << std::endl;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Also, you can pass a function object to modify the expected value.
|
||
|
|
||
|
```cpp
|
||
|
const auto value = toml::expect<int>(data.at("number"))
|
||
|
.map(// function that receives expected type (here, int)
|
||
|
[](const int number) -> double {
|
||
|
return number * 1.5 + 1.0;
|
||
|
}).unwrap_or(/*default value =*/ 3.14);
|
||
|
```
|
||
|
|
||
|
## Visiting a toml::value
|
||
|
|
||
|
toml11 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
|
||
|
receive all the possible TOML types. Also, the result types should be the same
|
||
|
each other.
|
||
|
|
||
|
## Constructing a toml::value
|
||
|
|
||
|
`toml::value` can be constructed in various ways.
|
||
|
|
||
|
```cpp
|
||
|
toml::value v(true); // boolean
|
||
|
toml::value v(42); // integer
|
||
|
toml::value v(3.14); // floating
|
||
|
toml::value v("foobar"); // string
|
||
|
toml::value v(toml::local_date(2019, toml::month_t::Apr, 1)); // date
|
||
|
toml::value v{1, 2, 3, 4, 5}; // array
|
||
|
toml::value v{{"foo", 42}, {"bar", 3.14}, {"baz", "qux"}}; // table
|
||
|
```
|
||
|
|
||
|
When constructing a string, you can choose to use either literal or basic string.
|
||
|
By default, it will be a basic string.
|
||
|
|
||
|
```cpp
|
||
|
toml::value v("foobar", toml::string_t::basic );
|
||
|
toml::value v("foobar", toml::string_t::literal);
|
||
|
```
|
||
|
|
||
|
Datetime objects can be constructed from `std::tm` and
|
||
|
`std::chrono::system_clock::time_point`. But you need to specify what type
|
||
|
you use to avoid ambiguity.
|
||
|
|
||
|
```cpp
|
||
|
const auto now = std::chrono::system_clock::now();
|
||
|
toml::value v(toml::local_date(now));
|
||
|
toml::value v(toml::local_datetime(now));
|
||
|
toml::value v(toml::offset_datetime(now));
|
||
|
```
|
||
|
|
||
|
Since local time is not equivalent to a time point, because it lacks date
|
||
|
information, it will be constructed from `std::chrono::duration`.
|
||
|
|
||
|
```cpp
|
||
|
toml::value v(toml::local_time(std::chrono::hours(10)));
|
||
|
```
|
||
|
|
||
|
You can construct an array object not only from `initializer_list`, but also
|
||
|
from STL containers. In that case, the element type must be convertible to
|
||
|
`toml::value`.
|
||
|
|
||
|
```cpp
|
||
|
std::vector<int> vec{1,2,3,4,5};
|
||
|
toml::value v(vec);
|
||
|
```
|
||
|
|
||
|
When you construct an array value, all the elements of `initializer_list`
|
||
|
must be convertible into `toml::value`.
|
||
|
|
||
|
If a `toml::value` has an array, you can `push_back` an element in it.
|
||
|
|
||
|
```cpp
|
||
|
toml::value v{1,2,3,4,5};
|
||
|
v.push_back(6);
|
||
|
```
|
||
|
|
||
|
`emplace_back` also works.
|
||
|
|
||
|
## Preserving comments
|
||
|
|
||
|
toml11 v3 or later allows you yo choose whether comments are preserved or not via template parameter
|
||
|
|
||
|
```cpp
|
||
|
const auto data1 = toml::parse<toml::discard_comments >("example.toml");
|
||
|
const auto data2 = toml::parse<toml::preserve_comments>("example.toml");
|
||
|
```
|
||
|
|
||
|
or macro definition.
|
||
|
|
||
|
```cpp
|
||
|
#define TOML11_PRESERVE_COMMENTS_BY_DEFAULT
|
||
|
#include <toml11/toml.hpp>
|
||
|
```
|
||
|
|
||
|
This feature is controlled by template parameter in `toml::basic_value<...>`.
|
||
|
`toml::value` is an alias of `toml::basic_value<...>`.
|
||
|
|
||
|
If template parameter is explicitly specified, the return value of `toml::parse`
|
||
|
will be `toml::basic_value<toml::preserve_comments>`.
|
||
|
If the macro is defined, the alias `toml::value` will be
|
||
|
`toml::basic_value<toml::preserve_comments>`.
|
||
|
|
||
|
Comments related to a value can be obtained by `toml::value::comments()`.
|
||
|
The return value has the same interface as `std::vector<std::string>`.
|
||
|
|
||
|
```cpp
|
||
|
const auto& com = v.comments();
|
||
|
for(const auto& c : com)
|
||
|
{
|
||
|
std::cout << c << std::endl;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Comments just before and just after (within the same line) a value are kept in a value.
|
||
|
|
||
|
```toml
|
||
|
# this is a comment for v1.
|
||
|
v1 = "foo"
|
||
|
|
||
|
v2 = "bar" # this is a comment for v2.
|
||
|
# Note that this comment is NOT a comment for v2.
|
||
|
|
||
|
# this comment is not related to any value
|
||
|
# because there are empty lines between v3.
|
||
|
# this comment will be ignored even if you set `preserve_comments`.
|
||
|
|
||
|
# this is a comment for v3
|
||
|
# this is also a comment for v3.
|
||
|
v3 = "baz" # ditto.
|
||
|
```
|
||
|
|
||
|
Each comment line becomes one element of a `std::vector`.
|
||
|
|
||
|
Hash signs will be removed, but spaces after hash sign will not be removed.
|
||
|
|
||
|
```cpp
|
||
|
v1.comments().at(0) == " this is a comment for v1."s;
|
||
|
|
||
|
v2.comments().at(1) == " this is a comment for v1."s;
|
||
|
|
||
|
v3.comments().at(0) == " this is a comment for v3."s;
|
||
|
v3.comments().at(1) == " this is also a comment for v3."s;
|
||
|
v3.comments().at(2) == " ditto."s;
|
||
|
```
|
||
|
|
||
|
Note that a comment just after an opening brace of an array will not be a
|
||
|
comment for the array.
|
||
|
|
||
|
```toml
|
||
|
# this is a comment for a.
|
||
|
a = [ # this is not a comment for a. this will be ignored.
|
||
|
1, 2, 3,
|
||
|
# this is a comment for `42`.
|
||
|
42, # this is also a comment for `42`.
|
||
|
5
|
||
|
] # this is a comment for a.
|
||
|
```
|
||
|
|
||
|
You can also append and modify comments.
|
||
|
The interfaces are the same as `std::vector<std::string>`.
|
||
|
|
||
|
```cpp
|
||
|
toml::basic_value<toml::preserve_comments> v(42);
|
||
|
v.comments().push_back(" add this comment.");
|
||
|
// # add this comment.
|
||
|
// i = 42
|
||
|
```
|
||
|
|
||
|
Also, you can pass a `std::vector<std::string>` when constructing a
|
||
|
`toml::basic_value<toml::preserve_comments>`.
|
||
|
|
||
|
```cpp
|
||
|
std::vector<std::string> comments{"comment 1", "comment 2"};
|
||
|
const toml::basic_value<toml::preserve_comments> v1(42, std::move(comments));
|
||
|
const toml::basic_value<toml::preserve_comments> v2(42, {"comment 1", "comment 2"});
|
||
|
```
|
||
|
|
||
|
When `toml::discard_comments` is chosen, comments will not be contained in a value.
|
||
|
`value::comments()` will always be kept empty.
|
||
|
All the modification on comments would be ignored.
|
||
|
All the element access in a `discard_comments` causes the same error as accessing
|
||
|
an element of an empty `std::vector`.
|
||
|
|
||
|
The comments will also be serialized. If comments exist, those comments will be
|
||
|
added just before the values.
|
||
|
|
||
|
__NOTE__: Result types from `toml::parse(...)` and
|
||
|
`toml::parse<toml::preserve_comments>(...)` are different.
|
||
|
|
||
|
## Customizing containers
|
||
|
|
||
|
Actually, `toml::basic_value` has 3 template arguments.
|
||
|
|
||
|
```cpp
|
||
|
template<typename Comment, // discard/preserve_comment
|
||
|
template<typename ...> class Table = std::unordered_map,
|
||
|
template<typename ...> class Array = std::vector>
|
||
|
class basic_value;
|
||
|
```
|
||
|
|
||
|
This enables you to change the containers used inside. E.g. you can use
|
||
|
`std::map` to contain a table object instead of `std::unordered_map`.
|
||
|
And also can use `std::deque` as a array object instead of `std::vector`.
|
||
|
|
||
|
You can set these parameters while calling `toml::parse` function.
|
||
|
|
||
|
```cpp
|
||
|
const auto data = toml::parse<
|
||
|
toml::preserve_comments, std::map, std::deque
|
||
|
>("example.toml");
|
||
|
```
|
||
|
|
||
|
Needless to say, the result types from `toml::parse(...)` and
|
||
|
`toml::parse<Com, Map, Cont>(...)` are different (unless you specify the same
|
||
|
types as default).
|
||
|
|
||
|
Note that, since `toml::table` and `toml::array` is an alias for a table and an
|
||
|
array of a default `toml::value`, so it is different from the types actually
|
||
|
contained in a `toml::basic_value` when you customize containers.
|
||
|
To get the actual type in a generic way, use
|
||
|
`typename toml::basic_type<C, T, A>::table_type` and
|
||
|
`typename toml::basic_type<C, T, A>::array_type`.
|
||
|
|
||
|
## TOML literal
|
||
|
|
||
|
toml11 supports `"..."_toml` literal.
|
||
|
It accept both a bare value and a file content.
|
||
|
|
||
|
```cpp
|
||
|
using namespace toml::literals::toml_literals;
|
||
|
|
||
|
// `_toml` can convert a bare value without key
|
||
|
const toml::value v = u8"0xDEADBEEF"_toml;
|
||
|
// v is an Integer value containing 0xDEADBEEF.
|
||
|
|
||
|
// raw string literal (`R"(...)"` is useful for this purpose)
|
||
|
const toml::value t = u8R"(
|
||
|
title = "this is TOML literal"
|
||
|
[table]
|
||
|
key = "value"
|
||
|
)"_toml;
|
||
|
// the literal will be parsed and the result will be contained in t
|
||
|
```
|
||
|
|
||
|
The literal function is defined in the same way as the standard library literals
|
||
|
such as `std::literals::string_literals::operator""s`.
|
||
|
|
||
|
```cpp
|
||
|
namespace toml
|
||
|
{
|
||
|
inline namespace literals
|
||
|
{
|
||
|
inline namespace toml_literals
|
||
|
{
|
||
|
toml::value operator"" _toml(const char* str, std::size_t len);
|
||
|
} // toml_literals
|
||
|
} // literals
|
||
|
} // toml
|
||
|
```
|
||
|
|
||
|
Access to the operator can be gained with `using namespace toml::literals;`,
|
||
|
`using namespace toml::toml_literals`, and `using namespace toml::literals::toml_literals`.
|
||
|
|
||
|
Note that a key that is composed only of digits is allowed in TOML.
|
||
|
And, unlike the file parser, toml-literal allows a bare value without a key.
|
||
|
Thus it is difficult to distinguish arrays having integers and definitions of
|
||
|
tables that are named as digits.
|
||
|
Currently, literal `[1]` becomes a table named "1".
|
||
|
To ensure a literal to be considered as an array with one element, you need to
|
||
|
add a comma after the first element (like `[1,]`).
|
||
|
|
||
|
```cpp
|
||
|
"[1,2,3]"_toml; // This is an array
|
||
|
"[table]"_toml; // This is a table that has an empty table named "table" inside.
|
||
|
"[[1,2,3]]"_toml; // This is an array of arrays
|
||
|
"[[table]]"_toml; // This is a table that has an array of tables inside.
|
||
|
|
||
|
"[[1]]"_toml; // This literal is ambiguous.
|
||
|
// Currently, it becomes a table that has array of table "1".
|
||
|
"1 = [{}]"_toml; // This is a table that has an array of table named 1.
|
||
|
"[[1,]]"_toml; // This is an array of arrays.
|
||
|
"[[1],]"_toml; // ditto.
|
||
|
```
|
||
|
|
||
|
NOTE: `_toml` literal returns a `toml::value` that does not have comments.
|
||
|
|
||
|
## Conversion between toml value and arbitrary types
|
||
|
|
||
|
You can also use `toml::get` and other related functions with the types
|
||
|
you defined after you implement a way to convert it.
|
||
|
|
||
|
```cpp
|
||
|
namespace ext
|
||
|
{
|
||
|
struct foo
|
||
|
{
|
||
|
int a;
|
||
|
double b;
|
||
|
std::string c;
|
||
|
};
|
||
|
} // ext
|
||
|
|
||
|
const auto data = toml::parse("example.toml");
|
||
|
|
||
|
// to do this
|
||
|
const foo f = toml::find<ext::foo>(data, "foo");
|
||
|
```
|
||
|
|
||
|
There are 3 ways to use `toml::get` with the types that you defined.
|
||
|
|
||
|
The first one is to implement `from_toml(const toml::value&)` member function.
|
||
|
|
||
|
```cpp
|
||
|
namespace ext
|
||
|
{
|
||
|
struct foo
|
||
|
{
|
||
|
int a;
|
||
|
double b;
|
||
|
std::string c;
|
||
|
|
||
|
void from_toml(const toml::value& v)
|
||
|
{
|
||
|
this->a = toml::find<int >(v, "a");
|
||
|
this->b = toml::find<double >(v, "b");
|
||
|
this->c = toml::find<std::string>(v, "c");
|
||
|
return;
|
||
|
}
|
||
|
};
|
||
|
} // ext
|
||
|
```
|
||
|
|
||
|
In this way, because `toml::get` first constructs `foo` without arguments,
|
||
|
the type should be default-constructible.
|
||
|
|
||
|
The second is to implement `constructor(const toml::value&)`.
|
||
|
|
||
|
```cpp
|
||
|
namespace ext
|
||
|
{
|
||
|
struct foo
|
||
|
{
|
||
|
explicit foo(const toml::value& v)
|
||
|
: a(toml::find<int>(v, "a")), b(toml::find<double>(v, "b")),
|
||
|
c(toml::find<std::string>(v, "c"))
|
||
|
{}
|
||
|
|
||
|
int a;
|
||
|
double b;
|
||
|
std::string c;
|
||
|
};
|
||
|
} // ext
|
||
|
```
|
||
|
|
||
|
Note that implicit default constructor declaration will be suppressed
|
||
|
when a constructor is defined. If you want to use the struct (here, `foo`)
|
||
|
in a container (e.g. `std::vector<foo>`), you may need to define default
|
||
|
constructor explicitly.
|
||
|
|
||
|
The third is to implement specialization of `toml::from` for your type.
|
||
|
|
||
|
```cpp
|
||
|
namespace ext
|
||
|
{
|
||
|
struct foo
|
||
|
{
|
||
|
int a;
|
||
|
double b;
|
||
|
std::string c;
|
||
|
};
|
||
|
} // ext
|
||
|
|
||
|
namespace toml
|
||
|
{
|
||
|
template<>
|
||
|
struct from<ext::foo>
|
||
|
{
|
||
|
static ext::foo from_toml(const value& v)
|
||
|
{
|
||
|
ext::foo f;
|
||
|
f.a = find<int >(v, "a");
|
||
|
f.b = find<double >(v, "b");
|
||
|
f.c = find<std::string>(v, "c");
|
||
|
return f;
|
||
|
}
|
||
|
};
|
||
|
} // toml
|
||
|
```
|
||
|
|
||
|
In this way, since the conversion function is defined outside of the class,
|
||
|
you can add conversion between `toml::value` and classes defined in another library.
|
||
|
|
||
|
In some cases, a class has a templatized constructor that takes a template, `T`.
|
||
|
It confuses `toml::get/find<T>` because it makes the class "constructible" from
|
||
|
`toml::value`. To avoid this problem, `toml::from` and `from_toml` always
|
||
|
precede constructor. It makes easier to implement conversion between
|
||
|
`toml::value` and types defined in other libraries because it skips constructor.
|
||
|
|
||
|
But, importantly, you cannot define `toml::from<T>` and `T.from_toml` at the same
|
||
|
time because it causes ambiguity in the overload resolution of `toml::get<T>` and `toml::find<T>`.
|
||
|
|
||
|
So the precedence is `toml::from<T>` == `T.from_toml()` > `T(toml::value)`.
|
||
|
|
||
|
If you want to convert any versions of `toml::basic_value`,
|
||
|
you need to templatize the conversion function as follows.
|
||
|
|
||
|
```cpp
|
||
|
struct foo
|
||
|
{
|
||
|
template<typename C, template<typename ...> class M, template<typename ...> class A>
|
||
|
void from_toml(const toml::basic_value<C, M, A>& v)
|
||
|
{
|
||
|
this->a = toml::find<int >(v, "a");
|
||
|
this->b = toml::find<double >(v, "b");
|
||
|
this->c = toml::find<std::string>(v, "c");
|
||
|
return;
|
||
|
}
|
||
|
};
|
||
|
// or
|
||
|
namespace toml
|
||
|
{
|
||
|
template<>
|
||
|
struct from<ext::foo>
|
||
|
{
|
||
|
template<typename C, template<typename ...> class M, template<typename ...> class A>
|
||
|
static ext::foo from_toml(const basic_value<C, M, A>& v)
|
||
|
{
|
||
|
ext::foo f;
|
||
|
f.a = find<int >(v, "a");
|
||
|
f.b = find<double >(v, "b");
|
||
|
f.c = find<std::string>(v, "c");
|
||
|
return f;
|
||
|
}
|
||
|
};
|
||
|
} // toml
|
||
|
```
|
||
|
|
||
|
----
|
||
|
|
||
|
The opposite direction is also supported in a similar way. You can directly
|
||
|
pass your type to `toml::value`'s constructor by introducing `into_toml` or
|
||
|
`toml::into<T>`.
|
||
|
|
||
|
```cpp
|
||
|
namespace ext
|
||
|
{
|
||
|
struct foo
|
||
|
{
|
||
|
int a;
|
||
|
double b;
|
||
|
std::string c;
|
||
|
|
||
|
toml::value into_toml() const // you need to mark it const.
|
||
|
{
|
||
|
return toml::value{{"a", this->a}, {"b", this->b}, {"c", this->c}};
|
||
|
}
|
||
|
};
|
||
|
} // ext
|
||
|
|
||
|
ext::foo f{42, 3.14, "foobar"};
|
||
|
toml::value v(f);
|
||
|
```
|
||
|
|
||
|
The definition of `toml::into<T>` is similar to `toml::from<T>`.
|
||
|
|
||
|
```cpp
|
||
|
namespace ext
|
||
|
{
|
||
|
struct foo
|
||
|
{
|
||
|
int a;
|
||
|
double b;
|
||
|
std::string c;
|
||
|
};
|
||
|
} // ext
|
||
|
|
||
|
namespace toml
|
||
|
{
|
||
|
template<>
|
||
|
struct into<ext::foo>
|
||
|
{
|
||
|
static toml::value into_toml(const ext::foo& f)
|
||
|
{
|
||
|
return toml::value{{"a", f.a}, {"b", f.b}, {"c", f.c}};
|
||
|
}
|
||
|
};
|
||
|
} // toml
|
||
|
|
||
|
ext::foo f{42, 3.14, "foobar"};
|
||
|
toml::value v(f);
|
||
|
```
|
||
|
|
||
|
Any type that can be converted to `toml::value`, e.g. `int`, `toml::table` and
|
||
|
`toml::array` are okay to return from `into_toml`.
|
||
|
|
||
|
You can also return a custom `toml::basic_value` from `toml::into`.
|
||
|
|
||
|
```cpp
|
||
|
namespace toml
|
||
|
{
|
||
|
template<>
|
||
|
struct into<ext::foo>
|
||
|
{
|
||
|
static toml::basic_value<toml::preserve_comments> into_toml(const ext::foo& f)
|
||
|
{
|
||
|
toml::basic_value<toml::preserve_comments> v{{"a", f.a}, {"b", f.b}, {"c", f.c}};
|
||
|
v.comments().push_back(" comment");
|
||
|
return v;
|
||
|
}
|
||
|
};
|
||
|
} // toml
|
||
|
```
|
||
|
|
||
|
But note that, if this `basic_value` would be assigned into other `toml::value`
|
||
|
that discards `comments`, the comments would be dropped.
|
||
|
|
||
|
### Macro to automatically define conversion functions
|
||
|
|
||
|
There is a helper macro that automatically generates conversion functions `from` and `into` for a simple struct.
|
||
|
|
||
|
```cpp
|
||
|
namespace foo
|
||
|
{
|
||
|
struct Foo
|
||
|
{
|
||
|
std::string s;
|
||
|
double d;
|
||
|
int i;
|
||
|
};
|
||
|
} // foo
|
||
|
|
||
|
TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(foo::Foo, s, d, i)
|
||
|
|
||
|
int main()
|
||
|
{
|
||
|
const auto file = toml::parse("example.toml");
|
||
|
auto f = toml::find<foo::Foo>(file, "foo");
|
||
|
}
|
||
|
```
|
||
|
|
||
|
And then you can use `toml::find<foo::Foo>(file, "foo");`
|
||
|
|
||
|
**Note** that, because of a slight difference in implementation of preprocessor between gcc/clang and MSVC, [you need to define `/Zc:preprocessor`](https://github.com/ToruNiina/toml11/issues/139#issuecomment-803683682) to use it in MSVC (Thank you @glebm !).
|
||
|
|
||
|
## Formatting user-defined error messages
|
||
|
|
||
|
When you encounter an error after you read the toml value, you may want to
|
||
|
show the error with the value.
|
||
|
|
||
|
toml11 provides you a function that formats user-defined error message with
|
||
|
related values. With a code like the following,
|
||
|
|
||
|
```cpp
|
||
|
const auto value = toml::find<int>(data, "num");
|
||
|
if(value < 0)
|
||
|
{
|
||
|
std::cerr << toml::format_error("[error] value should be positive",
|
||
|
data.at("num"), "positive number required")
|
||
|
<< std::endl;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
you will get an error message like this.
|
||
|
|
||
|
```console
|
||
|
[error] value should be positive
|
||
|
--> example.toml
|
||
|
3 | num = -42
|
||
|
| ~~~ positive number required
|
||
|
```
|
||
|
|
||
|
When you pass two values to `toml::format_error`,
|
||
|
|
||
|
```cpp
|
||
|
const auto min = toml::find<int>(range, "min");
|
||
|
const auto max = toml::find<int>(range, "max");
|
||
|
if(max < min)
|
||
|
{
|
||
|
std::cerr << toml::format_error("[error] max should be larger than min",
|
||
|
data.at("min"), "minimum number here",
|
||
|
data.at("max"), "maximum number here");
|
||
|
<< std::endl;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
you will get an error message like this.
|
||
|
|
||
|
```console
|
||
|
[error] max should be larger than min
|
||
|
--> example.toml
|
||
|
3 | min = 54
|
||
|
| ~~ minimum number here
|
||
|
...
|
||
|
4 | max = 42
|
||
|
| ~~ maximum number here
|
||
|
```
|
||
|
|
||
|
You can print hints at the end of the message.
|
||
|
|
||
|
```cpp
|
||
|
std::vector<std::string> hints;
|
||
|
hints.push_back("positive number means n >= 0.");
|
||
|
hints.push_back("negative number is not positive.");
|
||
|
std::cerr << toml::format_error("[error] value should be positive",
|
||
|
data.at("num"), "positive number required", hints)
|
||
|
<< std::endl;
|
||
|
```
|
||
|
|
||
|
```console
|
||
|
[error] value should be positive
|
||
|
--> example.toml
|
||
|
2 | num = 42
|
||
|
| ~~ positive number required
|
||
|
|
|
||
|
Hint: positive number means n >= 0.
|
||
|
Hint: negative number is not positive.
|
||
|
```
|
||
|
|
||
|
## Obtaining location information
|
||
|
|
||
|
You can also format error messages in your own way by using `source_location`.
|
||
|
|
||
|
```cpp
|
||
|
struct source_location
|
||
|
{
|
||
|
std::uint_least32_t line() const noexcept;
|
||
|
std::uint_least32_t column() const noexcept;
|
||
|
std::uint_least32_t region() const noexcept;
|
||
|
std::string const& file_name() const noexcept;
|
||
|
std::string const& line_str() const noexcept;
|
||
|
};
|
||
|
// +-- line() +--- length of the region (here, region() == 9)
|
||
|
// v .---+---.
|
||
|
// 12 | value = "foo bar" <- line_str() returns the line itself.
|
||
|
// ^-------- column() points here
|
||
|
```
|
||
|
|
||
|
You can get this by
|
||
|
```cpp
|
||
|
const toml::value v = /*...*/;
|
||
|
const toml::source_location loc = v.location();
|
||
|
```
|
||
|
|
||
|
## Exceptions
|
||
|
|
||
|
The following `exception` classes inherits `toml::exception` that inherits
|
||
|
`std::exception`.
|
||
|
|
||
|
```cpp
|
||
|
namespace toml {
|
||
|
struct exception : public std::exception {/**/};
|
||
|
struct syntax_error : public toml::exception {/**/};
|
||
|
struct type_error : public toml::exception {/**/};
|
||
|
struct internal_error : public toml::exception {/**/};
|
||
|
} // toml
|
||
|
```
|
||
|
|
||
|
`toml::exception` has `toml::exception::location()` member function that returns
|
||
|
`toml::source_location`, in addition to `what()`.
|
||
|
|
||
|
```cpp
|
||
|
namespace toml {
|
||
|
struct exception : public std::exception
|
||
|
{
|
||
|
// ...
|
||
|
source_location const& location() const noexcept;
|
||
|
};
|
||
|
} // toml
|
||
|
```
|
||
|
|
||
|
It represents where the error occurs.
|
||
|
|
||
|
`syntax_error` will be thrown from `toml::parse` and `_toml` literal.
|
||
|
`type_error` will be thrown from `toml::get/find`, `toml::value::as_xxx()`, and
|
||
|
other functions that takes a content inside of `toml::value`.
|
||
|
|
||
|
Note that, currently, from `toml::value::at()` and `toml::find(value, key)`
|
||
|
may throw an `std::out_of_range` that does not inherits `toml::exception`.
|
||
|
|
||
|
Also, in some cases, most likely in the file open error, it will throw an
|
||
|
`std::runtime_error`.
|
||
|
|
||
|
## Colorize Error Messages
|
||
|
|
||
|
By defining `TOML11_COLORIZE_ERROR_MESSAGE`, the error messages from
|
||
|
`toml::parse` and `toml::find|get` will be colorized. By default, this feature
|
||
|
is turned off.
|
||
|
|
||
|
With the following toml file taken from `toml-lang/toml/tests/hard_example.toml`,
|
||
|
|
||
|
```toml
|
||
|
[error]
|
||
|
array = [
|
||
|
"This might most likely happen in multiline arrays",
|
||
|
Like here,
|
||
|
"or here,
|
||
|
and here"
|
||
|
] End of array comment, forgot the #
|
||
|
```
|
||
|
|
||
|
the error message would be like this.
|
||
|
|
||
|
![error-message-1](https://github.com/ToruNiina/toml11/blob/misc/misc/toml11-err-msg-1.png)
|
||
|
|
||
|
With the following,
|
||
|
|
||
|
```toml
|
||
|
[error]
|
||
|
# array = [
|
||
|
# "This might most likely happen in multiline arrays",
|
||
|
# Like here,
|
||
|
# "or here,
|
||
|
# and here"
|
||
|
# ] End of array comment, forgot the #
|
||
|
number = 3.14 pi <--again forgot the #
|
||
|
```
|
||
|
|
||
|
the error message would be like this.
|
||
|
|
||
|
![error-message-2](https://github.com/ToruNiina/toml11/blob/misc/misc/toml11-err-msg-2.png)
|
||
|
|
||
|
The message would be messy when it is written to a file, not a terminal because
|
||
|
it uses [ANSI escape code](https://en.wikipedia.org/wiki/ANSI_escape_code).
|
||
|
|
||
|
Without `TOML11_COLORIZE_ERROR_MESSAGE`, you can still colorize user-defined
|
||
|
error message by passing `true` to the `toml::format_error` function.
|
||
|
If you define `TOML11_COLORIZE_ERROR_MESSAGE`, the value is `true` by default.
|
||
|
If not, the default value would be `false`.
|
||
|
|
||
|
```cpp
|
||
|
std::cerr << toml::format_error("[error] value should be positive",
|
||
|
data.at("num"), "positive number required",
|
||
|
hints, /*colorize = */ true) << std::endl;
|
||
|
```
|
||
|
|
||
|
Note: It colorize `[error]` in red. That means that it detects `[error]` prefix
|
||
|
at the front of the error message. If there is no `[error]` prefix,
|
||
|
`format_error` adds it to the error message.
|
||
|
|
||
|
## Serializing TOML data
|
||
|
|
||
|
toml11 enables you to serialize data into toml format.
|
||
|
|
||
|
```cpp
|
||
|
const toml::value data{{"foo", 42}, {"bar", "baz"}};
|
||
|
std::cout << data << std::endl;
|
||
|
// bar = "baz"
|
||
|
// foo = 42
|
||
|
```
|
||
|
|
||
|
toml11 automatically makes a small table and small array inline.
|
||
|
You can specify the width to make them inline by `std::setw` for streams.
|
||
|
|
||
|
```cpp
|
||
|
const toml::value data{
|
||
|
{"qux", {{"foo", 42}, {"bar", "baz"}}},
|
||
|
{"quux", {"small", "array", "of", "strings"}},
|
||
|
{"foobar", {"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.
|
||
|
|
||
|
To control the precision of floating point numbers, you need to pass
|
||
|
`std::setprecision` to stream.
|
||
|
|
||
|
```cpp
|
||
|
const toml::value data{
|
||
|
{"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
|
||
|
```
|
||
|
|
||
|
There is another way to format toml values, `toml::format()`.
|
||
|
It returns `std::string` that represents a value.
|
||
|
|
||
|
```cpp
|
||
|
const toml::value v{{"a", 42}};
|
||
|
const std::string fmt = toml::format(v);
|
||
|
// a = 42
|
||
|
```
|
||
|
|
||
|
Note that since `toml::format` formats a value, the resulting string may lack
|
||
|
the key value.
|
||
|
|
||
|
```cpp
|
||
|
const toml::value v{3.14};
|
||
|
const std::string fmt = toml::format(v);
|
||
|
// 3.14
|
||
|
```
|
||
|
|
||
|
To control the width and precision, `toml::format` receives optional second and
|
||
|
third arguments to set them. By default, the width is 80 and the precision is
|
||
|
`std::numeric_limits<double>::max_digit10`.
|
||
|
|
||
|
```cpp
|
||
|
const auto serial = toml::format(data, /*width = */ 0, /*prec = */ 17);
|
||
|
```
|
||
|
|
||
|
When you pass a comment-preserving-value, the comment will also be serialized.
|
||
|
An array or a table containing a value that has a comment would not be inlined.
|
||
|
|
||
|
## Underlying types
|
||
|
|
||
|
The toml types (can be used as `toml::*` in this library) and corresponding `enum` names are listed in the table below.
|
||
|
|
||
|
| TOML type | underlying c++ type | enum class |
|
||
|
| -------------- | ---------------------------------- | -------------------------------- |
|
||
|
| Boolean | `bool` | `toml::value_t::boolean` |
|
||
|
| Integer | `std::int64_t` | `toml::value_t::integer` |
|
||
|
| Float | `double` | `toml::value_t::floating` |
|
||
|
| String | `toml::string` | `toml::value_t::string` |
|
||
|
| LocalDate | `toml::local_date` | `toml::value_t::local_date` |
|
||
|
| LocalTime | `toml::local_time` | `toml::value_t::local_time` |
|
||
|
| LocalDatetime | `toml::local_datetime` | `toml::value_t::local_datetime` |
|
||
|
| OffsetDatetime | `toml::offset_datetime` | `toml::value_t::offset_datetime` |
|
||
|
| Array | `array-like<toml::value>` | `toml::value_t::array` |
|
||
|
| Table | `map-like<toml::key, toml::value>` | `toml::value_t::table` |
|
||
|
|
||
|
`array-like` and `map-like` are the STL containers that works like a `std::vector` and
|
||
|
`std::unordered_map`, respectively. By default, `std::vector` and `std::unordered_map`
|
||
|
are used. See [Customizing containers](#customizing-containers) for detail.
|
||
|
|
||
|
`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.
|
||
|
The most important difference between `std::string` and `toml::string` is that
|
||
|
`toml::string` will be formatted as a TOML string when outputted with `ostream`.
|
||
|
This feature is introduced to make it easy to write a custom serializer.
|
||
|
|
||
|
`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.
|
||
|
|
||
|
## Unreleased TOML features
|
||
|
|
||
|
Since TOML v1.0.0-rc.1 has been released, those features are now activated by
|
||
|
default. We no longer need to define `TOML11_USE_UNRELEASED_FEATURES`.
|
||
|
|
||
|
- Leading zeroes in exponent parts of floats are permitted.
|
||
|
- e.g. `1.0e+01`, `5e+05`
|
||
|
- [toml-lang/toml/PR/656](https://github.com/toml-lang/toml/pull/656)
|
||
|
- Allow raw tab characters in basic strings and multi-line basic strings.
|
||
|
- [toml-lang/toml/PR/627](https://github.com/toml-lang/toml/pull/627)
|
||
|
- Allow heterogeneous arrays
|
||
|
- [toml-lang/toml/PR/676](https://github.com/toml-lang/toml/pull/676)
|
||
|
|
||
|
## Note about heterogeneous arrays
|
||
|
|
||
|
Although `toml::parse` allows heterogeneous arrays, constructor of `toml::value`
|
||
|
does not. Here the reason is explained.
|
||
|
|
||
|
```cpp
|
||
|
// this won't be compiled
|
||
|
toml::value v{
|
||
|
"foo", 3.14, 42, {1,2,3,4,5}, {{"key", "value"}}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
There is a workaround for this. By explicitly converting values into
|
||
|
`toml::value`, you can initialize `toml::value` with a heterogeneous array.
|
||
|
Also, you can first initialize a `toml::value` with an array and then
|
||
|
`push_back` into it.
|
||
|
|
||
|
```cpp
|
||
|
// OK!
|
||
|
toml::value v{
|
||
|
toml::value("foo"), toml::value(3.14), toml::value(42),
|
||
|
toml::value{1,2,3,4,5}, toml::value{{"key", "value"}}
|
||
|
}
|
||
|
|
||
|
// OK!
|
||
|
toml::value v(toml::array{});
|
||
|
v.push_back("foo");
|
||
|
v.push_back(3.14);
|
||
|
|
||
|
// OK!
|
||
|
toml::array a;
|
||
|
a.push_back("foo");
|
||
|
a.push_back(3.14);
|
||
|
toml::value v(std::move(a));
|
||
|
```
|
||
|
|
||
|
The reason why the first example is not allowed is the following.
|
||
|
Let's assume that you are initializing a `toml::value` with a table.
|
||
|
|
||
|
```cpp
|
||
|
// # expecting TOML table.
|
||
|
toml::value v{ // [v]
|
||
|
{"answer", 42}, // answer = 42
|
||
|
{"pi", 3.14}, // pi = 3.14
|
||
|
{"foo", "bar"} // foo = "bar"
|
||
|
};
|
||
|
```
|
||
|
|
||
|
This is indistinguishable from a (heterogeneous) TOML array definition.
|
||
|
|
||
|
```toml
|
||
|
v = [
|
||
|
["answer", 42],
|
||
|
["pi", 3.14],
|
||
|
["foo", "bar"],
|
||
|
]
|
||
|
```
|
||
|
|
||
|
This means that the above C++ code makes constructor's overload resolution
|
||
|
ambiguous. So a constructor that allows both "table as an initializer-list" and
|
||
|
"heterogeneous array as an initializer-list" cannot be implemented.
|
||
|
|
||
|
Thus, although it is painful, we need to explicitly cast values into
|
||
|
`toml::value` when you initialize heterogeneous array in a C++ code.
|
||
|
|
||
|
```cpp
|
||
|
toml::value v{
|
||
|
toml::value("foo"), toml::value(3.14), toml::value(42),
|
||
|
toml::value{1,2,3,4,5}, toml::value{{"key", "value"}}
|
||
|
};
|
||
|
```
|
||
|
|
||
|
## Breaking Changes from v2
|
||
|
|
||
|
Although toml11 is relatively new library (it's three years old now), it had
|
||
|
some confusing and inconvenient user-interfaces because of historical reasons.
|
||
|
|
||
|
Between v2 and v3, those interfaces are rearranged.
|
||
|
|
||
|
- `toml::parse` now returns a `toml::value`, not `toml::table`.
|
||
|
- `toml::value` is now an alias of `toml::basic_value<discard_comment, std::vector, std::unordered_map>`.
|
||
|
- See [Customizing containers](#customizing-containers) for detail.
|
||
|
- The elements of `toml::value_t` are renamed as `snake_case`.
|
||
|
- See [Underlying types](#underlying-types) for detail.
|
||
|
- Supports for the CamelCaseNames are dropped.
|
||
|
- See [Underlying types](#underlying-types) for detail.
|
||
|
- `(is|as)_float` has been removed to make the function names consistent with others.
|
||
|
- Since `float` is a keyword, toml11 named a float type as `toml::floating`.
|
||
|
- Also a `value_t` corresponds to `toml::floating` is named `value_t::floating`.
|
||
|
- So `(is|as)_floating` is introduced and `is_float` has been removed.
|
||
|
- See [Casting a toml::value](#casting-a-tomlvalue) and [Checking value type](#checking-value-type) for detail.
|
||
|
- An overload of `toml::find` for `toml::table` has been dropped. Use `toml::value` version instead.
|
||
|
- Because type conversion between a table and a value causes ambiguity while overload resolution
|
||
|
- Since `toml::parse` now returns a `toml::value`, this feature becomes less important.
|
||
|
- Also because `toml::table` is a normal STL container, implementing utility function is easy.
|
||
|
- See [Finding a toml::value](#finding-a-toml-value) for detail.
|
||
|
- An overload of `operator<<` and `toml::format` for `toml::table`s are dropped.
|
||
|
- Use `toml::value` instead.
|
||
|
- See [Serializing TOML data](#serializing-toml-data) for detail.
|
||
|
- Interface around comments.
|
||
|
- See [Preserving Comments](#preserving-comments) for detail.
|
||
|
- An ancient `from_toml/into_toml` has been removed. Use arbitrary type conversion support.
|
||
|
- See [Conversion between toml value and arbitrary types](#conversion-between-toml-value-and-arbitrary-types) for detail.
|
||
|
|
||
|
Such a big change will not happen in the coming years.
|
||
|
|
||
|
## Running Tests
|
||
|
|
||
|
After cloning this repository, run the following command (thank you @jwillikers
|
||
|
for automating test set fetching!).
|
||
|
|
||
|
```sh
|
||
|
$ mkdir build
|
||
|
$ cd build
|
||
|
$ cmake .. -Dtoml11_BUILD_TEST=ON
|
||
|
$ make
|
||
|
$ make test
|
||
|
```
|
||
|
|
||
|
To run the language agnostic test suite, you need to compile
|
||
|
`tests/check_toml_test.cpp` and pass it to the tester.
|
||
|
|
||
|
## Contributors
|
||
|
|
||
|
I appreciate the help of the contributors who introduced the great feature to this library.
|
||
|
|
||
|
- Guillaume Fraux (@Luthaf)
|
||
|
- Windows support and CI on Appvayor
|
||
|
- Intel Compiler support
|
||
|
- Quentin Khan (@xaxousis)
|
||
|
- Found & Fixed a bug around ODR
|
||
|
- Improved error messages for invalid keys to show the location where the parser fails
|
||
|
- Petr Beneš (@wbenny)
|
||
|
- Fixed warnings on MSVC
|
||
|
- Ivan Shynkarenka (@chronoxor)
|
||
|
- Fixed Visual Studio 2019 warnings
|
||
|
- @khoitd1997
|
||
|
- Fixed warnings while type conversion
|
||
|
- @KerstinKeller
|
||
|
- Added installation script to CMake
|
||
|
- J.C. Moyer (@jcmoyer)
|
||
|
- Fixed an example code in the documentation
|
||
|
- Jt Freeman (@blockparty-sh)
|
||
|
- Fixed feature test macro around `localtime_s`
|
||
|
- Suppress warnings in Debug mode
|
||
|
- OGAWA Kenichi (@kenichiice)
|
||
|
- Suppress warnings on intel compiler
|
||
|
- Jordan Williams (@jwillikers)
|
||
|
- Fixed clang range-loop-analysis warnings
|
||
|
- Fixed feature test macro to suppress -Wundef
|
||
|
- Use cache variables in CMakeLists.txt
|
||
|
- Automate test set fetching, update and refactor CMakeLists.txt
|
||
|
- Scott McCaskill
|
||
|
- Parse 9 digits (nanoseconds) of fractional seconds in a `local_time`
|
||
|
- Shu Wang (@halfelf)
|
||
|
- fix "Finding a value in an array" example in README
|
||
|
- @maass-tv and @SeverinLeonhardt
|
||
|
- Fix MSVC warning C4866
|
||
|
- OGAWA KenIchi (@kenichiice)
|
||
|
- Fix include path in README
|
||
|
- Mohammed Alyousef (@MoAlyousef)
|
||
|
- Made testing optional in CMake
|
||
|
- Ivan Shynkarenka (@chronoxor)
|
||
|
- Fix compilation error in `<filesystem>` with MinGW
|
||
|
- Alex Merry (@amerry)
|
||
|
- Add missing include files
|
||
|
- sneakypete81 (@sneakypete81)
|
||
|
- Fix typo in error message
|
||
|
- Oliver Kahrmann (@founderio)
|
||
|
- Fix missing filename in error message if parsed file is empty
|
||
|
- Karl Nilsson (@karl-nilsson)
|
||
|
- Fix many spelling errors
|
||
|
- ohdarling88 (@ohdarling)
|
||
|
- Fix a bug in a constructor of serializer
|
||
|
- estshorter (@estshorter)
|
||
|
- Fix MSVC warning C26478
|
||
|
- Philip Top (@phlptp)
|
||
|
- Improve checking standard library feature availability check
|
||
|
- Louis Marascio (@marascio)
|
||
|
- Fix free-nonheap-object warning
|
||
|
|
||
|
|
||
|
## Licensing terms
|
||
|
|
||
|
This product is licensed under the terms of the [MIT License](LICENSE).
|
||
|
|
||
|
- Copyright (c) 2017-2021 Toru Niina
|
||
|
|
||
|
All rights reserved.
|