Skip to content

header-only C++(98|11|14|17) TOML v0.5.0 parser/encoder depending on Boost

License

Notifications You must be signed in to change notification settings

ToruNiina/Boost.toml

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Boost.toml

Build Status

日本語

Boost.toml is a header-only toml library depending on Boost.

compatible with TOML v0.5.0.

tested with C++(98|11|14|17) on Linux/macOS. Some functionalities (e.g. construction from std::initilalizer_list (after c++11), getting toml String as a std::string_view (after c++17)) would be disabled if older standard version is given.

Boost.toml depends on relatively later versions of the Boost C++ Library (tested with Boost 1.67.0 on Travis CI).

NOTE: This library is not a part of Boost C++ Library.

Are you looking for non-boost toml parser? Try toml11! It has almost the same (maybe a bit more powerful) functionality and better error messages. But the only weakness is, as the name suggests, it requires compiler which conforms C++11 standard.

Table of Contents

example code

Here is an example toml file (a bit modified from original file found in toml repository).

title = "TOML Example"

[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00

[database]
server = "192.168.1.1"
ports = [ 8001, 8001, 8002 ]
connection_max = 5000
enabled = true

# modified to explain how to get an array of table
[[servers]]
name = "alpha"
ip = "10.0.0.1"
dc = "eqdc10"

[[servers]]
name = "beta"
ip = "10.0.0.2"
dc = "eqdc10"

[clients]
data = [ ["gamma", "delta"], [1, 2] ]

You can read this file with a code like the following.

#include <toml/toml.hpp>

int main()
{
    // reads toml file and return it as a map (toml::key -> toml::value).
    const toml::table file = toml::parse("example.toml");

    // you can get toml values by toml::get function.
    const std::string title = toml::get<std::string>(file.at("title"));

    // toml::get returns lvalue reference.
    const toml::table&  owner = toml::get<toml::table>(file.at("owner"));
    const std::string&  name  = toml::get<std::string>(owner.at("name"));
    const auto&         dob   = toml::get<toml::offset_datetime>(owner.at("dob"));

    const auto& database = toml::get<toml::table>(file.at("database"));
    // you can use a std::string_view if you have a c++17 compatible compiler.
    const auto  server = toml::get<std::string_view>(database.at("server"));
    // you can get a toml::array as your favorite container.
    const auto  ports = toml::get<std::vector<int>>(database.at("ports"));
    // you can cast types if they are convertible.
    const auto  connection_max = toml::get<std::size_t>(database.at("connection_max"));
    const auto  enabled = toml::get<bool>(database.at("enabled"));

    // an array of table is simply an `array<table>`.
    const auto servers = toml::get<std::vector<toml::table>>(file.at("servers"));

    // boost::string_(view|ref) are also supported.
    const auto name_alpha = toml::get<boost::string_view>(servers.at(0).at("name"));
    const auto ip_alpha   = toml::get<boost::string_view>(servers.at(0).at("ip"));
    const auto dc_alpha   = toml::get<boost::string_view>(servers.at(0).at("dc"));
    const auto name_beta  = toml::get<boost::string_ref >(servers.at(1).at("name"));
    const auto ip_beta    = toml::get<boost::string_ref >(servers.at(1).at("ip"));
    const auto dc_beta    = toml::get<boost::string_ref >(servers.at(1).at("dc"));

    const auto& clients = toml::get<toml::table>(file.at("clients"));

    // you can do this!
    // the first array is array of string, the second one is array of int.
    // data = [ ["gamma", "delta"], [1, 2] ]
    const auto  data = toml::get<
        std::pair<std::vector<std::string>, std::vector<int>>
        >(clients.at("data"));
    // it supports std::tuple also (after c++11).

    return 0;
}

parsing toml file

Since this library is header-only, including toml/toml.hpp is the only thing required.

toml::parse function parses toml file. You can pass std::string that represents file name or std::istream& to the function. std::runtime_error would be thrown if a file open error happens.

Both returns toml::table that includes all the data in a file. If an error appeared while parsing a file, it throws std::runtime_error.

const toml::table data = parse("example.toml");
// or
std::ifstream ifs("example.toml");
if(!ifs.good){return 1;}
const toml::table data = parse(ifs);

getting toml values

Boost.toml provides a powerful function, toml::get, to get a value from TOML data.

basic usage of toml::get

You can extract value from toml::table by specifying a type of a value through toml::get<T>.

// you can get a reference when you specify a toml type
toml::value v1(42);
toml::integer& i_ref = toml::get<toml::integer>(v1);
i_ref = 6 * 9; // v1 will be 54.

// you can get a value as non-toml type(e.g. uint32_t) if they are convertible.
// in that case, a reference that points to the internal value cannot be gotten.
std::uint32_t i = toml::get<std::uint32_t>(v1); // 54

// to avoid deep-copy, it is useful to get const reference.
toml::value v2{{"int", 42}, {"float", 3.14}, {"str", "foo"}};
const toml::table& tab = toml::get<toml::table>(v2);

If you pass a convertible type (like int for toml::integer) to toml::get's template argument, it converts a value to the specified type. In that case, you can't get a lvalue reference that points to the contained value because it returns prvalue. You can find underlying types in this section. See this section for more information about type conversions.

If you pass a wrong type (like std::string for toml::integer) to toml::get, it will throw toml::bad_get. toml::bad_get::what might be helpful as an error message because it contains demangled typename powered by boost::typeindex::type_id().pretty_name().

toml::value v(42);
std::string s = toml::get<std::string>(v);

// exception thrown. the message would be something like this.
// terminate called after throwing an instance of 'toml::bad_get'
//   what():  toml::get: toml value has type `toml::integer`, but type `std::basic_string<char, std::char_traits<char>, std::allocator<char> >` is specified.

getting toml::array

You can get a toml::array as your favorite array type. The elements will also be converted to the specified type.

toml::value v{1, 2, 3, 4, 5};
std::vector<int>        vec = toml::get<std::vector<int>       >(v);
std::deque<unsigned>    deq = toml::get<std::deque<unsigned>   >(v);
std::list<std::int64_t> lst = toml::get<std::list<std::int64_t>>(v);
std::array<char, 5>     ary = toml::get<std::array<char, 5>    >(v);

Surprisingly, you can also get a std::pair or std::tuple from toml::array.

toml::value v{1, 2};
std::pair<int, unsigned>  p = toml::get<std::pair<int, unsigned>>(v);

toml::value v{1, 2, 3};
std::tuple<int, int, int> t = toml::get<std::tuple<int, int, int>>(v);

// unfortunately, boost::tuple is currently not supported.

This feature is convenient in the following case.

toml::array of toml::arrays having different types each other

Consider that you have this toml file.

array = [[1, 2, 3], ["foo", "bar", "baz"]]

What is the corresponding C++ type? Of course, its a std::pair of std::vector<int> and std::vector<std::string>.

Boost.toml supports this.

auto v = toml::get<std::pair<std::vector<int>, std::vector<std::string>>>(v);

Or it can be a tuple of two elements.

auto v = toml::get<std::tuple<std::vector<int>, std::vector<std::string>>>(v);

But generally, you might not be able to know the length or type of an array in a file before reading it. In that case, toml::array or std::vector<toml::value> can be used (actually, toml::array is just an alias of boost::container::vector<toml::value>, so in this case conversion may not be needed).

const toml::array& a = toml::get<toml::array>(v);

std::vector<int>    a1 = toml::get<std::vector<int>        >(a.at(0));
std::vector<string> a2 = toml::get<std::vector<std::string>>(a.at(1));

Or you can get this as std::vector<toml::array> because it is an array of arrays.

std::vector<toml::array> a = toml::get<std::vector<toml::array>>(v);

int         a1 = toml::get<int        >(a.at(0).at(0)); // 1
std::string a2 = toml::get<std::string>(a.at(1).at(0)); // "foo"

getting toml::table and Array of Tables

Array of Tables can be obtained in the same way described before.

// it is just an array of tables.
auto tables = toml::get<std::vector<toml::table>>(v);

And you can get also toml::table as your favorite map type.

toml::value v{{"int", 42}, {"float", 3.14}, {"str", "foo"}};

auto std_map  = toml::get<std::map<toml::key, toml::value>             >(v);
auto std_umap = toml::get<std::unordered_map<toml::key, toml::value>   >(v);
auto bst_map  = toml::get<boost::container::map<toml::key, toml::value>>(v);
auto bst_umap = toml::get<boost::unordered_map<toml::key, toml::value> >(v);

NOTE: toml::table is an alias of boost::container::flat_map. So it has all the functionalities that boost::container::flat_map has. In most cases, conversion is not needed.

performance of getting toml::array or toml::table

Because toml::get converts all the elements in an array, it essentially does the same thing as following.

// conversion from toml::array to std::vector<int>.
// essentially same as toml::get<std::vector<int>>(val).
std::vector<int> get_int_vec(const toml::value& v)
{
    const toml::array& ar = v.get<toml::array>();
    std::vector<int> retval(ar.size());
    std::transform(ar.begin(), ar.end(), retval.begin(),
        [](const toml::value& x){return toml::get<int>(x);});
    return retval;
}

getting toml::table as a different map type also copies elements deeply.

std::map<toml::key, toml::value> get_std_map(const toml::value& v)
{
    const toml::table& tb = v.get<toml::table>();
    // it does not need extra toml::get, so it's simpler than array.
    return std::map<toml::key, toml::value>(tb.begin(), tb.end());
}

If the array or table has many many elements, it will take a long time because it constructs completely new array or table and deeply copy all the elements.

In some cases, it is intolerable.

To avoid deep copy, you can get them as a (const) reference. All the elements are kept intact, so you need explicit conversion for each element, but it copies nothing.

toml::value  v{1,2,3,4,5};
toml::array& ar = toml::get<toml::array>(v);
std::int64_t i  = toml::get<std::int64_t>(ar.at(0));
// one more toml::get is needed because toml::array is an array of toml::values.

handling dotted keys

Boost.toml regards dotted keys as a table.

# dotted keys
physical.color = "red"
physical.shape = "sphere"
# the above key-value pairs are equivalent to
physical = {color = "red", shape = "sphere"}
# also equivalent to
[physical]
color = "red"
shape = "sphere"

You can get these values as following.

const auto& physical = toml::get<toml::table>(data.at("physical"));
const auto& color    = toml::get<std::string>(physical.at("color"));
const auto& shape    = toml::get<std::string>(physical.at("shape"));

confirming value type

If you pass wrong template argument to toml::get, toml::bad_get (that is derived from std::exception) will be thrown. Before using toml::get, it should be known what type does the value actually contain.

toml::value v(3.14);
std::string s = toml::get<std::string>(v);

// exception thrown. the message would be something like this.
// terminate called after throwing an instance of 'toml::bad_get'
//   what():  toml::get: toml value has type `toml::float`, but type `std::basic_string<char, std::char_traits<char>, std::allocator<char> >` is specified.

toml::value has toml::value::is() member function that can be used to check a type information.

toml::table data = toml::parse("unknown.toml");
const toml::value& v = data["some_value"]; // what type does it have ???

// receives enum
if(v.is(toml::value::string_tag)) {/* do some stuff */}
// receives type
if(v.is<toml::integer>()) {/* do some stuff */}

toml::value has an enum value that represents type information.

std::cout << v.which() << std::endl; // outputs "integer" or something like that

switch(v.which())
{
    case toml::value::integer_tag  : /* do some stuff */; break;
    case toml::value::string_tag   : /* do some stuff */; break;
    // ...
}

the complete set of this enum values is found in synopsis.

But it is painful to write switch-case every time. Boost.toml provides a way to visit contained value without knowing its value.

See also visiting value that has unknown type.

visiting value that has unknown type

Boost.toml provides toml::apply_visitor function to visit contained value without knowing its type.

toml::table data = toml::parse("sample.toml");
toml::apply_visitor([](const auto& val) {std::cout << val << std::endl;},
                    data["number"]);
toml::visit([](const auto& val) {std::cerr << val << std::endl;},
            data["number"]);

The usage is similar to boost::apply_visitor and boost::variant, bacause it uses them internally.

formatting toml values

Boost.toml also supports formatting toml values.

constructing toml values

toml::value is constructible from toml-convertible types like std::int64_t, double, float, std::string, const char*, and many others.

toml::value v1(42u);
toml::value v2(3.1416);
toml::value v3("string!");

toml::date  d(2018, 4, 1);
toml::time  t(toml::hours(1), toml::minutes(30));
toml::value v3(d, t); // datetime!

Iterators are also supported.

std::vector<toml::integer> is{1,2,3,4,5};
toml::value v1(is.begin(), is.end()); // toml::array of integers

std::map<std::string, double> ds{{"pi", 3.1416}, {"e", 2.7183}};
toml::value v2(ds.begin(), ds.end()); // become a toml::table!

And you can construct toml::value from std::initializer_list when you use C++11 compatible compiler.

toml::value v4{1,2,3,4,5}; // become an array
toml::value v5{{"i", 42}, {"pi", 3.14}, {"key", "value"}}; // become a table

printing toml values

Boost.toml has toml::format function that converts a toml::value to std::string. Since it returns a string, you can output it.

std::string i = toml::format(toml::value(42));
// 42
std::string s = toml::format(toml::value("string with\nnewline"));
// """
// string with
// newline
// """

std::cout << toml::format(toml::value(toml::date(1979, 5, 27), toml::hours(7) + toml::minutes(32)));
// 1979-05-27T07:32:00
std::cout << toml::format(toml::value{1, 2, 3});
// [1,2,3]

toml::format sometimes adds newlines to avoid too long line.

toml::value str("too long string would be splitted to multiple lines, "
                "and the threshould can be passed to toml::format function. "
                "By default, the threshold is 80.");
std::cout << toml::format(str);
// """
// too long string would be splitted to multiple lines, and the threshould can be \
// passed to toml::format function. By default, the threshold is 80.\
// """
toml::value ary{
    "If an array has many elements so the result of toml::format become longer",
    "than a threshold toml::format will print them in multi-line array."
    };
std::cout << toml::format(ary);
// [
//  "If an array has many elements so the result of toml::format become longer",
//  "than a threshold toml::format will print them in multi-line array.",
// ]

toml::format automatically makes an element of array of tables inline when the length of lines will be less than a threshold.

toml::value aot{
    toml::table{{"key1", 1}, {"key2", 3.14}},
    toml::table{{"key1", 2}, {"key2", 6.28}},
    toml::table{{"key1", 3}, {"key2", 9.42}}
};
std::cout << foml::format(aot);
// [
//  {key1 = "value1-1", key2 = "value2-1"},
//  {key1 = "value1-2", key2 = "value2-2"},
//  {key1 = "value1-3", key2 = "value2-3"},
// ]

You can change this threshold by passing a value explicitly to toml::format.

std::cout << toml::format(val, 100);

By default, the threshold is set as 80.

It also supports iostream. When you output it into std::ostream, toml::format will be called automatically.

toml::value val;
std::cout << val;

You can set the threshold by putting std::setw before outputting your toml value.

toml::value val;
std::cout << val; // calls toml::format(val, 80)
std::cout << std::setw(100) << val; // calls toml::format(val, 100)

formatting toml data

If you pass a toml::table to toml::format, it prints a TOML file considering the passed toml::table as a root object.

toml::table tab;
tab["a"]   = toml::integer(42);
tab["pi"]  = toml::floating(3.14);
tab["key"] = toml::string("value");
tab["tab"] = toml::table{{"b", 54}, {"e", 2.718}};

std::cout << tab << std::endl;
// a = 42
// pi = 3.140000
// key = "value"
// [tab]
// b = 54
// e = 2.718000

You can find the simplest example in sample/ directory that reads toml file and outputs its content into stdout. You can see what happens if Boost.toml formats your toml file by passing a file to the sample program.

datetime operation

Depending on Boost.Date_Time, Boost.toml supports datetime operation. Since toml::local_datetime is actually just an alias of boost::posix_time::ptime, you can operate datetime in the same way as Boost.Date_Time.

boost::posix_time::ptime dt = toml::get<boost::posix_time::ptime>(v);
dt += boost::date_time::days(7);
dt += boost::date_time::hours(12);

And just for ease, Boost.toml imports some classes and enums. You can operate your datetime value in the same way as Boost.Date_Time without writing namespace boost::date_time:: explicitly.

toml::value d(toml::date(2018, toml::Apr, 1));
toml::value t(toml::hours(1) + toml::minutes(3) + toml::seconds(10));
toml::value dt(d, t);
toml::get<toml::local_datetime>(dt) += toml::years(1);
toml::get<toml::local_datetime>(dt) += toml::months(2);
toml::get<toml::local_datetime>(dt) += toml::days(10);

See also underlying types.

NOTE: It uses only a header-only portion of the Boost.Date_Time library. So Boost.toml can be used without linking. But in order to use whole the functionalities of Boost.Date_Time library, you might need linking.

underlying types

toml::value is based on a boost::variant that contains following toml value types.

toml value type boost.toml type
- boost::blank (uninitialized type)
boolean bool
integer boost::int64_t
floating double
string struct {std::string str; enum kind_t {basic, literal} kind;}
date boost::gregorian::date
time boost::posix_time::time_duration
local_datetime boost::posix_time::ptime
offset_datetime boost::local_time::local_date_time
array boost::container::vector<value>
table boost::container::flat_map<key, value>

toml::string and basic, literal flags

toml::string has an enum kind_t to represent basic_string and literal_string. But it can be converted to std::string automatically, so users do not need to consider about a difference between toml::string and std::string.

an enum value can be passed to make toml::value that contains toml::string. It affects on the output format. By default, it is set as basic_string.

toml::value v0("foo"); // It will be a basic string by default.
toml::value v1("foo", toml::string::basic);
toml::value v2("bar", toml::string::literal); // become a literal-string.

map class that represents toml::table

Here, flat_map is used to store toml.table because normally the number of elements in data from file will never be changed. You can change it to a boost::container::map by defining BOOST_TOML_USE_MAP_FOR_TABLE before including it or as a compiler flag.

Why not STL container?

Because Boost.Container allows to contain incomplete types. So it allows to contain recursive data type. This feature removes the neccesity of using pointers to implement toml::value that is defined recursively. It will make the whole process faster.

NOTE:

toml::value is a recursively defined data type. toml::array is an array of toml::value, and it is also one of the types that toml::value contains.

So the code looks like this.

struct value
{
    boost::variant<string, integer, // ...
            boost::container::vector<value> // value contains itself inside!
        > storage;
};

In C++17, "Minimal incomplete type support for standard containers" is incorporated into the standard specification. But to realize this implementation before C++17, it may be the best way to use Boost.Container.

types that are convertible from toml value by using toml::get

You can get a reference that points an internal value if you specify one of the underlying types. See also underlying types.

Here, types can be converted from toml types are listed.

toml value type convertible types
boolean bool only
integer types that makes boost::is_integral<T>::value true (excepting bool).
floating types that makes boost::is_floating_point<T>::value true.
string std::string&, std::string_view, boost::string_view, boost::string_ref
date std::tm, std::chrono::time_point
time std::tm, std::chrono::duration
local_datetime std::tm, std::chrono::time_point
offset_datetime std::tm, std::chrono::time_point
array container classes (see below).
table map-like classes (see below).

toml::string can safely be converted to an lvalue of std::string.

Because std::time_t is an integral type, it collides with toml::integer. Thus getting std::time_t from datetime objects is not supported.

Boost.chrono is currently not supported.

toml::array can be converted to a class that ...

  • has member types named iterator and value_type
  • does not have a member type named key_type or mapped_type
  • is not one of the std::string, std::string_view, boost::string_view or boost::string_ref

toml::table can be converted to a class that ...

  • has member types named iterator, value_type, key_type and mapped_type.

supplemental notes

Boost.Date_Time and linking

Boost.toml depends on Boost.Date_Time. As Boost.Date_Time says, it has a few functions that require linking. However, it also says that most of the users can use it without linking because most of the parts are implemented as a header-only library. Boost.toml uses only functions that does not need to link, so you can use it as a header-only library unless you want to use the functions defined in Boost.Date_Time that require linking.

error: wrong number of template arguments (11, should be at least 0)

Boost.toml depends on Boost.MPL because it supports C++98. To use some meta-functions defined in Boost.MPL with a lot of arguments, it overwrites the value of BOOST_MPL_LIMIT_METAFUNCTION_ARITY (see the source). This is required to extend the number of arguments in Boost.MPL. It means that if you include a header of another Boost library which includes a header of Boost.MPL before including boost.toml, the macro couldn't be overwritten. It causes an error while compiling.

To workaround this, you need to include "Boost.toml/toml/toml.hpp" before including other header files.

Synopsis

toml::value

struct value
{
    typedef boost::variant<boost::blank, boolean, integer, floating, string,
        date, time, local_datetime, offset_datetime, array, table> storage_type;

    enum kind
    {
        empty_tag           = 0,
        boolean_tag         = 1,
        integer_tag         = 2,
        float_tag           = 3,
        string_tag          = 4,
        date_tag            = 5,
        time_tag            = 6,
        local_datetime_tag  = 7,
        offset_datetime_tag = 8,
        array_tag           = 9,
        table_tag           = 10,
        undefined_tag       = 11
    };

    value();
    ~value();
    value(const value& v);
    value(value&& v);
    value& operator=(const value& v);
    value& operator=(value&& v); // after c++11.

    // these are enabled for each TOML type by using SFINAE or just overloading.
    template<typename T> value(const T& v);
    template<typename T> value& operator=(const T& v);

    value(const char* v,        string::kind_t k);
    value(const std::string& v, string::kind_t k);
    value(const date&                d, const time& t);
    value(const local_datetime&     dt, const time_zone_ptr tzp);
    value(const date& d, const time& t, const time_zone_ptr tzp);

    // enabled after c++11.
    value(std::string&& v);
    value(toml::array&& v);
    value(toml::table&& v);
    value& operator=(std::string&& v);
    value& operator=(toml::array&& v);
    value& operator=(toml::table&& v);

    // enabled after c++11.
    template<typename T>
    value(std::initializer_list<T> v);
    value(std::initializer_list<std::pair<key, value>> v);

    // enabled for each toml type (string, array, or table).
    template<typename Iterator>
    value(Iterator first, Iterator last);

    int  index() const noexcept;
    kind which() const noexcept;

    template<typename T>
    bool is()       const noexcept;
    bool is(kind k) const noexcept;

    template<typename T> T&       get();
    template<typename T> T const& get();

    void swap(value& rhs);

    template<typename Visitor>
    /* result type of Visitor */ apply_visitor(Visitor v);
    template<typename Visitor>
    /* result type of Visitor */ apply_visitor(Visitor v) const;

    bool operator==(const value& r) const;
    bool operator!=(const value& r) const;
    bool operator< (const value& r) const;
    bool operator> (const value& r) const;
    bool operator<=(const value& r) const;
    bool operator>=(const value& r) const;
};

void swap(value& lhs, value& rhs);

template<typename Visitor>
/* result type of Visitor */ apply_visitor(Visitor, value&);
template<typename Visitor>
/* result type of Visitor */ apply_visitor(Visitor, value const&);

template<typename Visitor>
/* result type of Visitor */ visit(Visitor, value&);
template<typename Visitor>
/* result type of Visitor */ visit(Visitor, value const&);

// output value::kind.
template<typename charT, typename traits>
std::basic_ostream<charT, traits>&
operator<<(std::basic_ostream<charT, traits>& os, value::kind k);

Licensing Terms

This library is distributed under the Boost Software License.

  • copyright (c) 2018, Toru Niina

All rights reserved.