|
|
|
@@ -19,38 +19,41 @@
|
|
|
|
|
|
|
|
|
|
#include "hz/debug.h"
|
|
|
|
|
#include "hz/string_algo.h"
|
|
|
|
|
#include "json/picojson.h"
|
|
|
|
|
#include "json/json.hpp"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace rconfig {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
using json = nlohmann::json;
|
|
|
|
|
using namespace std::string_literals;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace impl {
|
|
|
|
|
|
|
|
|
|
inline std::unique_ptr<picojson::value> config_node; ///< Node for serializable branch
|
|
|
|
|
inline std::unique_ptr<picojson::value> default_node; ///< Node for default branch
|
|
|
|
|
inline std::unique_ptr<json> config_node; ///< Node for serializable branch
|
|
|
|
|
inline std::unique_ptr<json> default_node; ///< Node for default branch
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
|
inline void set_node_data(picojson::value& root, const std::string& path, T&& value)
|
|
|
|
|
inline void set_node_data(json& root, const std::string& path, T&& value)
|
|
|
|
|
{
|
|
|
|
|
std::vector<std::string> components;
|
|
|
|
|
hz::string_split(path, '/', components, true);
|
|
|
|
|
|
|
|
|
|
picojson::value* curr = &root;
|
|
|
|
|
json* curr = &root;
|
|
|
|
|
for (std::size_t comp_index = 0; comp_index < components.size(); ++comp_index) {
|
|
|
|
|
const std::string& comp_name = components[comp_index];
|
|
|
|
|
|
|
|
|
|
// we can't have non-object values in the middle of a path
|
|
|
|
|
if (!curr->is<picojson::object>()) {
|
|
|
|
|
throw std::runtime_error(std::string("Cannot set node data \"" + path + "\", component \"" + comp_name + "\" is not an object."));
|
|
|
|
|
if (!curr->is_object()) {
|
|
|
|
|
throw std::runtime_error("Cannot set node data \""s + path + "\", component \"" + comp_name + "\" is not an object.");
|
|
|
|
|
}
|
|
|
|
|
auto& curr_obj = curr->get<picojson::object>();
|
|
|
|
|
if (auto iter = curr_obj.find(comp_name); iter != curr_obj.end()) { // path component exists
|
|
|
|
|
picojson::value& jval = iter->second;
|
|
|
|
|
if (auto iter = curr->find(comp_name); iter != curr->end()) { // path component exists
|
|
|
|
|
json& jval = iter.value();
|
|
|
|
|
if (comp_index + 1 == components.size()) { // it's the "value" component
|
|
|
|
|
jval = picojson::value(std::forward<T>(value));
|
|
|
|
|
jval = json(std::forward<T>(value));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
// continue to the next component
|
|
|
|
@@ -58,10 +61,10 @@ namespace impl {
|
|
|
|
|
|
|
|
|
|
} else { // path component doesn't exist
|
|
|
|
|
if (comp_index + 1 == components.size()) { // it's the "value" component
|
|
|
|
|
curr->get<picojson::object>()[comp_name] = picojson::value(std::forward<T>(value));
|
|
|
|
|
(*curr)[comp_name] = json(std::forward<T>(value));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
curr = &(curr->get<picojson::object>()[comp_name] = picojson::value(picojson::object()));
|
|
|
|
|
curr = &((*curr)[comp_name] = json::object());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -69,26 +72,22 @@ namespace impl {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
|
bool get_node_data(picojson::value& root, const std::string& path, T& value)
|
|
|
|
|
bool get_node_data(json& root, const std::string& path, T& value)
|
|
|
|
|
{
|
|
|
|
|
std::vector<std::string> components;
|
|
|
|
|
hz::string_split(path, '/', components, true);
|
|
|
|
|
|
|
|
|
|
picojson::value* curr = &root;
|
|
|
|
|
json* curr = &root;
|
|
|
|
|
for (std::size_t comp_index = 0; comp_index < components.size(); ++comp_index) {
|
|
|
|
|
const std::string& comp_name = components[comp_index];
|
|
|
|
|
|
|
|
|
|
if (!curr->is<picojson::object>()) { // we can't have non-object values in the middle of a path
|
|
|
|
|
throw std::runtime_error(std::string("Cannot get node data \"" + path + "\", component \"" + comp_name + "\" is not an object."));
|
|
|
|
|
if (!curr->is_object()) { // we can't have non-object values in the middle of a path
|
|
|
|
|
throw std::runtime_error("Cannot get node data \""s + path + "\", component \"" + comp_name + "\" is not an object.");
|
|
|
|
|
}
|
|
|
|
|
auto& curr_obj = curr->get<picojson::object>();
|
|
|
|
|
if (auto iter = curr_obj.find(comp_name); iter != curr_obj.end()) { // path component exists
|
|
|
|
|
picojson::value& jval = iter->second;
|
|
|
|
|
if (auto iter = curr->find(comp_name); iter != curr->end()) { // path component exists
|
|
|
|
|
json& jval = iter.value();
|
|
|
|
|
if (comp_index + 1 == components.size()) { // it's the "value" component
|
|
|
|
|
if (!jval.is<T>()) {
|
|
|
|
|
throw std::runtime_error(std::string("Cannot get node data \"" + path + "\", type mismatch."));
|
|
|
|
|
}
|
|
|
|
|
value = jval.get<T>();
|
|
|
|
|
value = jval.get<T>(); // may throw json::type_error
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
// continue to the next component
|
|
|
|
@@ -102,6 +101,36 @@ namespace impl {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inline void unset_node_data(json& root, const std::string& path)
|
|
|
|
|
{
|
|
|
|
|
std::vector<std::string> components;
|
|
|
|
|
hz::string_split(path, '/', components, true);
|
|
|
|
|
|
|
|
|
|
json* curr = &root;
|
|
|
|
|
for (std::size_t comp_index = 0; comp_index < components.size(); ++comp_index) {
|
|
|
|
|
const std::string& comp_name = components[comp_index];
|
|
|
|
|
|
|
|
|
|
if (auto iter = curr->find(comp_name); iter != curr->end()) { // path component exists
|
|
|
|
|
json& jval = iter.value();
|
|
|
|
|
if (comp_index + 1 == components.size()) { // it's the "value" component
|
|
|
|
|
curr->erase(comp_name);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (!jval.is_object()) { // we can't have non-object values in the middle of a path
|
|
|
|
|
debug_out_error("rconfig", "Component \""s + comp_name + "\" in path \"" + path + "\" is not an object, removing it.");
|
|
|
|
|
curr->erase(comp_name);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
// continue to the next component
|
|
|
|
|
curr = &jval;
|
|
|
|
|
|
|
|
|
|
} else { // path component doesn't exist
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -109,14 +138,14 @@ namespace impl {
|
|
|
|
|
/// Clear user config
|
|
|
|
|
inline void clear_config()
|
|
|
|
|
{
|
|
|
|
|
impl::config_node = std::make_unique<picojson::value>(picojson::object());
|
|
|
|
|
impl::config_node = std::make_unique<json>(json::object());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Clear defaults
|
|
|
|
|
inline void clear_defaults()
|
|
|
|
|
{
|
|
|
|
|
impl::default_node = std::make_unique<picojson::value>(picojson::object());
|
|
|
|
|
impl::default_node = std::make_unique<json>(json::object());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -135,7 +164,7 @@ inline bool init_root()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Get the config branch node
|
|
|
|
|
inline picojson::value& get_config_branch()
|
|
|
|
|
inline json& get_config_branch()
|
|
|
|
|
{
|
|
|
|
|
init_root();
|
|
|
|
|
return *impl::config_node;
|
|
|
|
@@ -144,7 +173,7 @@ inline picojson::value& get_config_branch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Get the default branch node
|
|
|
|
|
inline picojson::value& get_default_branch()
|
|
|
|
|
inline json& get_default_branch()
|
|
|
|
|
{
|
|
|
|
|
init_root();
|
|
|
|
|
return *impl::default_node;
|
|
|
|
@@ -154,18 +183,18 @@ inline picojson::value& get_default_branch()
|
|
|
|
|
|
|
|
|
|
/// Set the data in path
|
|
|
|
|
template<typename T>
|
|
|
|
|
void set_data(const std::string& path, T data)
|
|
|
|
|
bool set_data(const std::string& path, T data)
|
|
|
|
|
{
|
|
|
|
|
impl::set_node_data(get_config_branch(), path, std::move(data));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// picojson has only int64_t, add int for convenience.
|
|
|
|
|
template<>
|
|
|
|
|
inline void set_data<int>(const std::string& path, int data)
|
|
|
|
|
{
|
|
|
|
|
set_data<int64_t>(path, int64_t(data));
|
|
|
|
|
// Since config is loaded from a user file, it may contain some invalid
|
|
|
|
|
// nodes. Don't abort, print warnings.
|
|
|
|
|
try {
|
|
|
|
|
impl::set_node_data(get_config_branch(), path, std::move(data));
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
debug_out_error("rconfig", e.what());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -174,73 +203,69 @@ inline void set_data<int>(const std::string& path, int data)
|
|
|
|
|
template<typename T>
|
|
|
|
|
void set_default_data(const std::string& path, T data)
|
|
|
|
|
{
|
|
|
|
|
// Default data branch must always be valid, so abort if anything is wrong.
|
|
|
|
|
impl::set_node_data(get_default_branch(), path, std::move(data));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// picojson has only int64_t, add int for convenience.
|
|
|
|
|
template<>
|
|
|
|
|
inline void set_default_data<int>(const std::string& path, int data)
|
|
|
|
|
{
|
|
|
|
|
set_default_data<int64_t>(path, int64_t(data));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Get the data from config. If no such node exists, look it up in defaults.
|
|
|
|
|
template<typename T>
|
|
|
|
|
T get_data(const std::string& path)
|
|
|
|
|
{
|
|
|
|
|
T data = {};
|
|
|
|
|
if (!impl::get_node_data(get_config_branch(), path, data)) {
|
|
|
|
|
if (!impl::get_node_data(get_default_branch(), path, data)) {
|
|
|
|
|
throw std::runtime_error(std::string("No such node: ") + path);
|
|
|
|
|
}
|
|
|
|
|
bool found = false;
|
|
|
|
|
try {
|
|
|
|
|
// This can possibly throw because the user config file is incorrect
|
|
|
|
|
found = impl::get_node_data(get_config_branch(), path, data);
|
|
|
|
|
}
|
|
|
|
|
catch(std::exception& e) {
|
|
|
|
|
debug_out_error("rconfig", e.what());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This can throw only for errors within the program.
|
|
|
|
|
if (!found && !impl::get_node_data(get_default_branch(), path, data)) {
|
|
|
|
|
throw std::runtime_error("No such node: "s + path);
|
|
|
|
|
}
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// picojson has only int64_t, add int for convenience.
|
|
|
|
|
template<>
|
|
|
|
|
inline int get_data<int>(const std::string& path)
|
|
|
|
|
{
|
|
|
|
|
return static_cast<int>(get_data<int64_t>(path));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Get the data from defaults.
|
|
|
|
|
template<typename T>
|
|
|
|
|
T get_default_data(const std::string& path)
|
|
|
|
|
{
|
|
|
|
|
T data = {};
|
|
|
|
|
// This can throw only for errors within the program.
|
|
|
|
|
if (!impl::get_node_data(get_default_branch(), path, data)) {
|
|
|
|
|
throw std::runtime_error(std::string("No such node: ") + path);
|
|
|
|
|
throw std::runtime_error("No such node: "s + path);
|
|
|
|
|
}
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// picojson has only int64_t, add int for convenience.
|
|
|
|
|
template<>
|
|
|
|
|
inline int get_default_data<int>(const std::string& path)
|
|
|
|
|
|
|
|
|
|
/// Unset data in config path
|
|
|
|
|
inline void unset_data(const std::string& path)
|
|
|
|
|
{
|
|
|
|
|
return static_cast<int>(get_default_data<int64_t>(path));
|
|
|
|
|
impl::unset_node_data(get_config_branch(), path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inline void dump_config()
|
|
|
|
|
/// Dump config to debug output
|
|
|
|
|
inline void dump_config(bool print_defaults = false)
|
|
|
|
|
{
|
|
|
|
|
debug_begin();
|
|
|
|
|
debug_out_dump("rconfig", "Config:\n" + get_config_branch().serialize() + "\n");
|
|
|
|
|
debug_out_dump("rconfig", "Config:\n" + get_config_branch().dump(4) + "\n");
|
|
|
|
|
debug_end();
|
|
|
|
|
|
|
|
|
|
debug_begin();
|
|
|
|
|
debug_out_dump("rconfig", "Defaults:\n" + get_default_branch().serialize() + "\n");
|
|
|
|
|
debug_end();
|
|
|
|
|
if (print_defaults) {
|
|
|
|
|
debug_begin();
|
|
|
|
|
debug_out_dump("rconfig", "Defaults:\n" + get_default_branch().dump(4) + "\n");
|
|
|
|
|
debug_end();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|