Commit 901cd14c authored by Federico Rossi's avatar Federico Rossi

...

parent 9e39f6c3
# Compiled Object files
*.slo
*.lo
*.o
# Compiled Dynamic libraries
*.so
*.dylib
# Compiled Static libraries
*.lai
*.la
*.a
cmake_minimum_required(VERSION 2.8.7 FATAL_ERROR)
project(lightconf_test)
set(GTEST_DIR $ENV{GTEST_DIR})
add_executable(lightconf_sample
sample/lightconf_sample.cpp
)
if(WIN32)
set(CMAKE_FIND_LIBRARY_SUFFIXES .lib .a ${CMAKE_FIND_LIBRARY_SUFFIXES})
else(WIN32)
set(CMAKE_FIND_LIBRARY_SUFFIXES .a ${CMAKE_FIND_LIBRARY_SUFFIXES})
endif(WIN32)
add_definitions("-std=gnu++11 -g -Wall")
find_package(GTest)
find_package(Threads)
if (GTEST_FOUND)
add_executable(lightconf_test
test/path.cpp
test/group.cpp
test/scanner.cpp
test/readwrite.cpp
)
message("Found GTest...compiling test project.")
message(${GTEST_INCLUDE_DIRS})
include_directories(${GTEST_INCLUDE_DIRS})
target_link_libraries(lightconf_test ${GTEST_BOTH_LIBRARIES})
else (GTEST_FOUND)
message("Could not find GTest...skipping test project.")
endif (GTEST_FOUND)
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
\ No newline at end of file
Copyright (c) 2013, Simon Broadhead
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies,
either expressed or implied, of the FreeBSD Project.
\ No newline at end of file
What is lightconf?
------
lightconf is a lightweight header-only configuration file library for C++11. It is designed to be (almost) compatible with JSON.
### Features
- Header-only means it can be copied directly into your project and used without introducing any deployment issues.
- Hierarchical configuration files with a simple path-based interface for accessing nested properties.
- The primary lightconf text format `.config` is structurally identical to JSON but supports comments. Comments, blank lines, and ordering will be preserved when updating an existing file.
- It can also read and write JSON directly.
- Easily extensible to allow serializing custom types and enums automatically.
- BSD licensed.
Certain features used in lightconf, such as `decltype` and variadic templates, mean that it is only usable by compilers that support C++11. It has been tested using Clang 3.2 and GCC 4.7.2.
### Alternatives
There are various common ways to implement configuration files:
- _JSON_ is a common text format for storing structured data. The drawback of using a JSON library directly is that most JSON libraries do not abstract away the physical structure of a JSON document, and still require explicitly drilling down and extracting values from the document, especially when dealing with custom types. lightconf is not a replacement for JSON, but rather a more specialized interface that is capable of reading and writing both JSON and a slightly more human-friendly format called `.config`.
- _XML_ suffers from the same problems as using a JSON library with the added problem of being extremely verbose.
- _Boost.PropertyTree_ is a nice library similar to lightconf that supports JSON among other formats. The primary drawback is its dependency on Boost.
- _INI_ is a very simple flat key/value format. It does not support hierarchical or vector data.
### File Format
The text-based file format that lightconf is designed to use for primary serialization and deserialization is referred to internally as `.config` (simply to differentiate it from JSON, the file extension is not important). It differs from JSON in the following ways:
- Keys are bare identifiers rather than strings, and are of the form `[A-Za-z][A-Za-z0-9_\-]*` (if a JSON file has keys which don't match this pattern, an exception will be thrown when trying to deserialize it)
- Keys are separated from values by an `=` rather than a colon
- Commas (including trailing commas) in lists and groups are optional
- Comments are supported (JSON with comments will read as well, but the comments will not be preserved when saving a JSON file)
- JSON supports documents with arrays as root elements, but `.config` files always have a group (an object in JSON) as a root, with no braces
### Quick Tutorial
A full working example is available in `sample/lightconf_sample.cpp`. This is a very quick example of how to use lightconf.
The primary object containing configuration information is `lightconf::group`. It acts like a map from string keys to `lightconf::value` values, and is analogous to JSON's _objects_. A value is effectively a union type which can contain the standard JSON types: doubles, strings, bools, arrays (implemented as `std::vector<lightconf::value>`) and other groups.
Values are accessed via paths, which are period separated names corresponding to nested groups. The path `foo.bar.baz` corresponds to the value 123 in the configuration layout `foo = { bar = { baz = 123 } }`.
```cpp
// Deserialize a configuration file stored in a string
lightconf::group config_group = lightconf::config_format::read(source);
// Retrieve a value from the configuration group (throwing an exception if it doesn't exist)
int foo_count = config_group.get<int>("counts.foo_count");
// Retrieve a value from the configuration group (returning a default value if it doesn't exist)
bool is_bar = config_group.get<bool>("flags.is_bar", false);
// Set a value in the configuration group, creating parent groups
config_group.set<double>("constants.transcendental.pi", 3.14159);
// Typed vectors and tuples can also be stored and retrieved, as long as the inner types can be stored
config_group.set<std::vector<string>>("names", { "Fred", "George", "Stephen" });
std::vector<string> names = config_group.get<std::vector<string>>("names");
config_group.set<std::tuple<int, bool>>("my_tuple", std::make_tuple<int, bool>(10, false));
std::tuple<int, bool> my_tuple = config_group.get<std::tuple<int, bool>>("my_tuple");
// Generate a new configuration file using the original one as a base, and attempting to keep
// lines under 80 characters long by wrapping lists and groups that exceed that length
std::string new_source = lightconf::config_format::write(config_group, source, 80);
// We can also create a JSON version
std::string json_source = lightconf::json_format::write(config_group);
```
Custom types and enums can also be serialized directly by adding specializations of the template class `lightconf::value_type_info`. See `sample/lightconf_sample.cpp` for an example.
#ifndef _LIGHTCONF_OUTER_CONFIG_FORMAT_H_
#define _LIGHTCONF_OUTER_CONFIG_FORMAT_H_
#include "internal/config_format.hpp"
#endif // _LIGHTCONF_OUTER_CONFIG_FORMAT_H_
#ifndef _LIGHTCONF_EXCEPTIONS_H_
#define _LIGHTCONF_EXCEPTIONS_H_
#include <stdexcept>
#include <string>
namespace lightconf {
////////////////////
//
//
class lightconf_error : public std::runtime_error {
public:
explicit lightconf_error(const std::string& what) :
std::runtime_error(what)
{ }
};
//
//
class value_error : public lightconf_error {
public:
explicit value_error(const std::string& what) :
lightconf_error(what)
{ }
};
//
//
class path_error : public lightconf_error {
public:
explicit path_error(const std::string& what) :
lightconf_error(what)
{ }
};
//
//
class parse_error : public lightconf_error {
public:
int line() const { return line_; }
int col() const { return col_; }
parse_error(const std::string& what, int line, int col) :
lightconf_error(what),
line_(line),
col_(col)
{ }
private:
int line_;
int col_;
};
//
//
class utf8_error : public parse_error {
public:
utf8_error(const std::string& what, int line, int col) :
parse_error(what, line, col)
{ }
};
////////////////////
}
#endif // _LIGHTCONF_EXCEPTIONS_H_
#ifndef _LIGHTCONF_GROUP_H_
#define _LIGHTCONF_GROUP_H_
#include <map>
#include <type_traits>
#include <vector>
#include "path.hpp"
namespace lightconf {
////////////////////
class value;
template <typename T> struct value_type_info;
typedef std::map<std::string, value> value_map_type;
template <typename T>
using return_type = decltype(value_type_info<T>::extract_value(std::declval<value>()));
//
//
class group {
public:
typedef std::vector<std::string>::const_iterator const_iterator;
typedef std::vector<std::string>::size_type size_type;
template <typename T>
return_type<T> get(const path& key) const;
template <typename T>
return_type<T> get(const path& key, const T& def) const;
template <typename T>
void set(const path& key, const T& val = T());
template <typename T>
bool has(const path& key) const;
void unset(const path& key);
bool operator==(const group& rhs) const;
const_iterator begin() const { return order_.begin(); }
const_iterator end() const { return order_.end(); }
size_type size() const { return order_.size(); }
group();
private:
template <typename InputIterator>
const value * find_value(InputIterator first, InputIterator last, const group **parent_group, path *key_rest) const;
template <typename InputIterator, typename T>
value * create_value(InputIterator first, InputIterator last, const T& val);
void set_key(const std::string& key, const value& val);
value_map_type values_;
std::vector<std::string> order_;
};
////////////////////
}
#endif // _LIGHTCONF_GROUP_H_
#ifndef _LIGHTCONF_GROUP_IMPL_H_
#define _LIGHTCONF_GROUP_IMPL_H_
#include <algorithm>
#include "group.hpp"
#include "value_impl.hpp"
#include "path.hpp"
#include "value_type_info.hpp"
namespace lightconf {
////////////////////
//
//
inline group::group() : values_(), order_()
{ }
//
//
inline bool group::operator==(const group& rhs) const {
if (values_.size() != rhs.values_.size()) {
return false;
}
auto it1 = std::begin(values_);
auto it2 = std::begin(rhs.values_);
while (it1 != std::end(values_)) {
if (*it1 != *it2) {
return false;
}
++it1;
++it2;
}
auto it3 = std::begin(order_);
auto it4 = std::begin(rhs.order_);
while (it3 != std::end(order_)) {
if (*it3 != *it4) {
return false;
}
++it3;
++it4;
}
return true;
}
//
//
template <typename T>
inline return_type<T> group::get(const path& key) const {
const value *val;
const group *parent_group;
path key_rest;
val = find_value(std::begin(key), std::end(key), &parent_group, &key_rest);
if (val) {
return val->get<T>();
}
throw path_error("non-existent path requested: " + key.fullpath());
}
//
//
template <typename T>
inline return_type<T> group::get(const path& key, const T& def) const {
const value *result;
const group *parent_group;
path key_rest;
result = find_value(std::begin(key), std::end(key), &parent_group, &key_rest);
if (result) {
return result->get<T>(def);
}
return def;
}
//
//
template <typename T>
inline void group::set(const path& key, const T& val) {
const value *result_const;
const group *parent_group_const;
group *parent_group;
value *result;
path key_rest;
result_const = find_value(std::begin(key), std::end(key), &parent_group_const, &key_rest);
parent_group = const_cast<group *>(parent_group_const);
result = const_cast<value *>(result_const);
if (result) {
*result = value_type_info<T>::create_value(val);
} else {
parent_group->create_value(std::begin(key_rest), std::end(key_rest), val);
}
}
//
//
template <typename T>
inline bool group::has(const path& key) const {
const value *result;
const group *parent_group;
path key_rest;
result = find_value(std::begin(key), std::end(key), &parent_group, &key_rest);
if (result) {
return result->is<T>();
}
return false;
}
//
//
inline void group::unset(const path& key) {
const value *result_const;
const group *parent_group_const;
group *parent_group;
value *result;
path key_rest;
result_const = find_value(std::begin(key), std::end(key), &parent_group_const, &key_rest);
parent_group = const_cast<group *>(parent_group_const);
result = const_cast<value *>(result_const);
if (result) {
parent_group->values_.erase(*(std::end(key) - 1));
auto it = std::find(parent_group->order_.begin(), parent_group->order_.end(), *(std::end(key) - 1));
parent_group->order_.erase(it);
}
}
//
//
template <typename InputIterator>
inline const value *group::find_value(InputIterator first, InputIterator last,
const group **parent_group, path *key_rest) const {
if (first == last) {
throw path_error("value at empty path requested");
}
auto it = values_.find(*first);
if (first == last - 1) {
if (it == values_.end()) {
*parent_group = this;
*key_rest = path(first, last);
return 0;
} else {
*parent_group = this;
*key_rest = path();
return &it->second;
}
} else {
if (it == values_.end() || !it->second.template is<group>()) {
*parent_group = this;
*key_rest = path(first, last);
return 0;
} else {
const group& grp = it->second.template get<group>();
return grp.find_value(first + 1, last, parent_group, key_rest);
}
}
}
//
//
template <typename InputIterator, typename T>
inline value *group::create_value(InputIterator first, InputIterator last, const T& val) {
if (first == last) {
throw path_error("value at empty path created");
}
char c = (*first)[0];
if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) {
throw path_error("key starts with an invalid character");
}
for (char c : *first) {
if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')
&& (c < '0' || c > '9') && c != '_' && c != '-') {
throw path_error("key contains an invalid character");
}
}
if (first == last - 1) {
set_key(*first, value_type_info<T>::create_value(val));
return &values_.at(*first);
} else {
set_key(*first, value(group()));
const group& grp_const = values_.at(*first).template get<group>();
// we can safely strip constness because we know our child groups are not const
group& grp = const_cast<group&>(grp_const);
return grp.create_value(first + 1, last, val);
}
}
//
//
inline void group::set_key(const std::string& key, const value& val) {
if (values_.find(key) == values_.end()) {
order_.push_back(key);
}
values_[key] = val;
}
////////////////////
}
#endif // _LIGHTCONF_GROUP_IMPL_H_
#ifndef _LIGHTCONF_JSON_FORMAT_H_
#define _LIGHTCONF_JSON_FORMAT_H_
#include <algorithm>
#include <string>
#include "group.hpp"
#include "scanner.hpp"
#include "util.hpp"
#include "writer.hpp"
namespace lightconf { namespace json_format {
////////////////////
group read_group(scanner& sc, bool braces);
value_vector_type read_vector(scanner& sc);
value read_value(scanner& sc);
void write_group(writer& wr, const group& gr);
void write_vector(writer& wr, const value_vector_type& vec);
void write_value(writer& wr, const value& val);
group read(const std::string& src);
std::string write(const group& grp);
//
//
inline group read_group(scanner& sc, bool braces) {
group grp;
sc.expect('{');
while (!sc.peek_token().is_char('}')) {
std::string key = sc.expect_string();
sc.expect(':');
value val = read_value(sc);
grp.set(key, val);
if (sc.peek_token().is_char(',')) {
sc.expect(',');
} else {
break;
}
}
sc.expect('}');
return grp;
}
//
//
inline value_vector_type read_vector(scanner& sc) {
sc.expect('[');
value_vector_type vec;
while (!sc.peek_token().is_char(']')) {
vec.push_back(read_value(sc));
if (sc.peek_token().is_char(',')) {
sc.expect(',');
} else {
break;
}
}
sc.expect(']');
return vec;
}
//
//
inline value read_value(scanner& sc) {
if (sc.peek_token().is_char('{')) {
return value(read_group(sc, true));
} else if (sc.peek_token().is_char('[')) {
return value(read_vector(sc));
} else {
switch (sc.peek_token().type) {
case token_type::identifier_token: {
std::string ident = sc.expect_identifier();
if (ident == "true") {
return value(true);
} else if (ident == "false") {
return value(false);
} else if (ident == "null") {
sc.fail("null is not a valid value", sc.peek_token().line, sc.peek_token().col);
} else {
sc.fail("unexpected identifier", sc.peek_token().line, sc.peek_token().col);
}
break;
}
case token_type::string_token: {
std::string str = sc.expect_string();
return value(str);
}
case token_type::number_token: {
double dbl = sc.expect_number();
return value(dbl);