2020-12-09 08:47:45 +01:00
|
|
|
#include "stdafx.h"
|
2016-02-01 22:55:43 +01:00
|
|
|
#include "Config.h"
|
2020-12-12 13:01:29 +01:00
|
|
|
#include "util/types.hpp"
|
2016-02-01 22:55:43 +01:00
|
|
|
|
2020-03-09 17:18:39 +01:00
|
|
|
#include "util/yaml.hpp"
|
2016-02-01 22:55:43 +01:00
|
|
|
|
2019-11-08 15:32:43 +01:00
|
|
|
#include <charconv>
|
2017-09-16 19:36:53 +02:00
|
|
|
|
2020-02-01 08:43:43 +01:00
|
|
|
LOG_CHANNEL(cfg_log, "CFG");
|
2020-02-01 05:15:50 +01:00
|
|
|
|
2016-02-01 22:55:43 +01:00
|
|
|
namespace cfg
|
|
|
|
{
|
2017-05-20 13:45:02 +02:00
|
|
|
_base::_base(type _type)
|
2016-02-01 22:55:43 +01:00
|
|
|
: m_type(_type)
|
|
|
|
{
|
|
|
|
if (_type != type::node)
|
|
|
|
{
|
2020-12-09 16:04:52 +01:00
|
|
|
cfg_log.fatal("Invalid root node");
|
2016-02-01 22:55:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-14 22:45:07 +01:00
|
|
|
_base::_base(type _type, node* owner, const std::string& name, bool dynamic)
|
2021-03-20 17:06:43 +01:00
|
|
|
: m_type(_type), m_dynamic(dynamic), m_name(name)
|
2016-02-01 22:55:43 +01:00
|
|
|
{
|
2021-03-20 18:06:45 +01:00
|
|
|
for (const auto& node : owner->m_nodes)
|
2016-02-01 22:55:43 +01:00
|
|
|
{
|
2021-03-20 18:06:45 +01:00
|
|
|
if (node->get_name() == name)
|
2017-05-20 13:45:02 +02:00
|
|
|
{
|
2020-12-09 16:04:52 +01:00
|
|
|
cfg_log.fatal("Node already exists: %s", name);
|
2017-05-20 13:45:02 +02:00
|
|
|
}
|
2016-02-01 22:55:43 +01:00
|
|
|
}
|
|
|
|
|
2021-03-20 18:06:45 +01:00
|
|
|
owner->m_nodes.emplace_back(this);
|
2016-08-08 18:01:06 +02:00
|
|
|
}
|
|
|
|
|
2019-11-14 22:45:07 +01:00
|
|
|
bool _base::from_string(const std::string&, bool)
|
2016-08-08 18:01:06 +02:00
|
|
|
{
|
2021-04-07 23:52:18 +02:00
|
|
|
cfg_log.fatal("cfg::_base::from_string() purecall");
|
|
|
|
return false;
|
2016-08-08 18:01:06 +02:00
|
|
|
}
|
|
|
|
|
2017-05-20 13:45:02 +02:00
|
|
|
bool _base::from_list(std::vector<std::string>&&)
|
2016-08-08 18:01:06 +02:00
|
|
|
{
|
2021-04-07 23:52:18 +02:00
|
|
|
cfg_log.fatal("cfg::_base::from_list() purecall");
|
|
|
|
return false;
|
2016-02-01 22:55:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Emit YAML
|
2017-05-20 13:45:02 +02:00
|
|
|
static void encode(YAML::Emitter& out, const class _base& rhs);
|
2016-02-01 22:55:43 +01:00
|
|
|
|
|
|
|
// Incrementally load config entries from YAML::Node.
|
|
|
|
// The config value is preserved if the corresponding YAML node doesn't exist.
|
2019-11-14 22:45:07 +01:00
|
|
|
static void decode(const YAML::Node& data, class _base& rhs, bool dynamic = false);
|
2016-02-01 22:55:43 +01:00
|
|
|
}
|
|
|
|
|
2017-08-02 12:22:53 +02:00
|
|
|
std::vector<std::string> cfg::make_int_range(s64 min, s64 max)
|
|
|
|
{
|
|
|
|
return {std::to_string(min), std::to_string(max)};
|
|
|
|
}
|
|
|
|
|
2016-02-01 22:55:43 +01:00
|
|
|
bool cfg::try_to_int64(s64* out, const std::string& value, s64 min, s64 max)
|
|
|
|
{
|
|
|
|
s64 result;
|
2019-11-08 15:32:43 +01:00
|
|
|
const char* start = &value.front();
|
|
|
|
const char* end = &value.back() + 1;
|
|
|
|
int base = 10;
|
2020-05-15 17:57:48 +02:00
|
|
|
int sign = +1;
|
|
|
|
|
|
|
|
if (start[0] == '-')
|
|
|
|
{
|
|
|
|
sign = -1;
|
|
|
|
start += 1;
|
|
|
|
}
|
2016-02-01 22:55:43 +01:00
|
|
|
|
2019-11-08 15:32:43 +01:00
|
|
|
if (start[0] == '0' && (start[1] == 'x' || start[1] == 'X'))
|
2016-02-01 22:55:43 +01:00
|
|
|
{
|
2019-11-08 15:32:43 +01:00
|
|
|
// Limited hex support
|
|
|
|
base = 16;
|
|
|
|
start += 2;
|
2016-02-01 22:55:43 +01:00
|
|
|
}
|
|
|
|
|
2019-11-08 15:32:43 +01:00
|
|
|
const auto ret = std::from_chars(start, end, result, base);
|
|
|
|
|
2020-05-15 17:57:48 +02:00
|
|
|
if (ret.ec != std::errc() || ret.ptr != end || (start[0] == '-' && sign < 0))
|
2016-02-01 22:55:43 +01:00
|
|
|
{
|
2020-12-31 19:02:03 +01:00
|
|
|
if (out) cfg_log.error("cfg::try_to_int64('%s'): invalid integer", value);
|
2016-02-01 22:55:43 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-05-15 17:57:48 +02:00
|
|
|
result *= sign;
|
|
|
|
|
2016-02-01 22:55:43 +01:00
|
|
|
if (result < min || result > max)
|
|
|
|
{
|
2020-12-31 19:02:03 +01:00
|
|
|
if (out) cfg_log.error("cfg::try_to_int64('%s'): out of bounds (%d..%d)", value, min, max);
|
2020-04-04 14:33:03 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (out) *out = result;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<std::string> cfg::make_uint_range(u64 min, u64 max)
|
|
|
|
{
|
|
|
|
return {std::to_string(min), std::to_string(max)};
|
|
|
|
}
|
|
|
|
|
|
|
|
bool cfg::try_to_uint64(u64* out, const std::string& value, u64 min, u64 max)
|
|
|
|
{
|
|
|
|
u64 result;
|
|
|
|
const char* start = &value.front();
|
|
|
|
const char* end = &value.back() + 1;
|
|
|
|
int base = 10;
|
|
|
|
|
|
|
|
if (start[0] == '0' && (start[1] == 'x' || start[1] == 'X'))
|
|
|
|
{
|
|
|
|
// Limited hex support
|
|
|
|
base = 16;
|
|
|
|
start += 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto ret = std::from_chars(start, end, result, base);
|
|
|
|
|
|
|
|
if (ret.ec != std::errc() || ret.ptr != end)
|
|
|
|
{
|
2020-12-31 19:02:03 +01:00
|
|
|
if (out) cfg_log.error("cfg::try_to_uint64('%s'): invalid integer", value);
|
2020-04-04 14:33:03 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (result < min || result > max)
|
|
|
|
{
|
2020-12-31 19:02:03 +01:00
|
|
|
if (out) cfg_log.error("cfg::try_to_uint64('%s'): out of bounds (%u..%u)", value, min, max);
|
2016-02-01 22:55:43 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (out) *out = result;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-08-03 22:51:05 +02:00
|
|
|
bool cfg::try_to_enum_value(u64* out, decltype(&fmt_class_string<int>::format) func, const std::string& value)
|
|
|
|
{
|
2017-08-13 17:30:44 +02:00
|
|
|
u64 max = -1;
|
|
|
|
|
2016-08-03 22:51:05 +02:00
|
|
|
for (u64 i = 0;; i++)
|
|
|
|
{
|
|
|
|
std::string var;
|
|
|
|
func(var, i);
|
|
|
|
|
|
|
|
if (var == value)
|
|
|
|
{
|
|
|
|
if (out) *out = i;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string hex;
|
|
|
|
fmt_class_string<u64>::format(hex, i);
|
|
|
|
if (var == hex)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
2017-08-13 17:30:44 +02:00
|
|
|
|
|
|
|
max = i;
|
2016-08-03 22:51:05 +02:00
|
|
|
}
|
|
|
|
|
2019-11-08 15:32:43 +01:00
|
|
|
u64 result;
|
|
|
|
const char* start = &value.front();
|
|
|
|
const char* end = &value.back() + 1;
|
|
|
|
int base = 10;
|
|
|
|
|
|
|
|
if (start[0] == '0' && (start[1] == 'x' || start[1] == 'X'))
|
2016-08-03 22:51:05 +02:00
|
|
|
{
|
2019-11-08 15:32:43 +01:00
|
|
|
// Limited hex support
|
|
|
|
base = 16;
|
|
|
|
start += 2;
|
|
|
|
}
|
2017-05-20 18:26:22 +02:00
|
|
|
|
2019-11-08 15:32:43 +01:00
|
|
|
const auto ret = std::from_chars(start, end, result, base);
|
2017-08-13 17:30:44 +02:00
|
|
|
|
2019-11-08 15:32:43 +01:00
|
|
|
if (ret.ec != std::errc() || ret.ptr != end)
|
|
|
|
{
|
2020-02-01 05:15:50 +01:00
|
|
|
if (out) cfg_log.error("cfg::try_to_enum_value('%s'): invalid enum or integer", value);
|
2019-11-08 15:32:43 +01:00
|
|
|
return false;
|
2016-08-03 22:51:05 +02:00
|
|
|
}
|
2019-11-08 15:32:43 +01:00
|
|
|
|
|
|
|
if (result > max)
|
2016-08-03 22:51:05 +02:00
|
|
|
{
|
2020-02-01 05:15:50 +01:00
|
|
|
if (out) cfg_log.error("cfg::try_to_enum_value('%s'): out of bounds(0..%u)", value, max);
|
2016-08-03 22:51:05 +02:00
|
|
|
return false;
|
|
|
|
}
|
2019-11-08 15:32:43 +01:00
|
|
|
|
|
|
|
if (out) *out = result;
|
|
|
|
return true;
|
2016-08-03 22:51:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<std::string> cfg::try_to_enum_list(decltype(&fmt_class_string<int>::format) func)
|
|
|
|
{
|
|
|
|
std::vector<std::string> result;
|
|
|
|
|
|
|
|
for (u64 i = 0;; i++)
|
|
|
|
{
|
|
|
|
std::string var;
|
|
|
|
func(var, i);
|
|
|
|
|
|
|
|
std::string hex;
|
|
|
|
fmt_class_string<u64>::format(hex, i);
|
|
|
|
if (var == hex)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
result.emplace_back(std::move(var));
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-05-20 13:45:02 +02:00
|
|
|
void cfg::encode(YAML::Emitter& out, const cfg::_base& rhs)
|
2016-02-01 22:55:43 +01:00
|
|
|
{
|
|
|
|
switch (rhs.get_type())
|
|
|
|
{
|
|
|
|
case type::node:
|
|
|
|
{
|
|
|
|
out << YAML::BeginMap;
|
2021-03-20 18:06:45 +01:00
|
|
|
for (const auto& node : static_cast<const node&>(rhs).get_nodes())
|
2016-02-01 22:55:43 +01:00
|
|
|
{
|
2021-03-20 18:06:45 +01:00
|
|
|
out << YAML::Key << node->get_name();
|
2017-05-13 20:30:37 +02:00
|
|
|
out << YAML::Value;
|
2021-03-20 18:06:45 +01:00
|
|
|
encode(out, *node);
|
2016-02-01 22:55:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
out << YAML::EndMap;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
case type::set:
|
|
|
|
{
|
|
|
|
out << YAML::BeginSeq;
|
|
|
|
for (const auto& str : static_cast<const set_entry&>(rhs).get_set())
|
|
|
|
{
|
|
|
|
out << str;
|
|
|
|
}
|
|
|
|
|
|
|
|
out << YAML::EndSeq;
|
|
|
|
return;
|
|
|
|
}
|
2017-05-13 20:30:37 +02:00
|
|
|
case type::log:
|
|
|
|
{
|
|
|
|
out << YAML::BeginMap;
|
|
|
|
for (const auto& np : static_cast<const log_entry&>(rhs).get_map())
|
|
|
|
{
|
|
|
|
if (np.second == logs::level::notice) continue;
|
|
|
|
out << YAML::Key << np.first;
|
|
|
|
out << YAML::Value << fmt::format("%s", np.second);
|
|
|
|
}
|
|
|
|
|
|
|
|
out << YAML::EndMap;
|
|
|
|
return;
|
|
|
|
}
|
2017-11-02 07:30:34 +01:00
|
|
|
default:
|
|
|
|
{
|
|
|
|
out << rhs.to_string();
|
|
|
|
return;
|
|
|
|
}
|
2016-02-01 22:55:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-14 22:45:07 +01:00
|
|
|
void cfg::decode(const YAML::Node& data, cfg::_base& rhs, bool dynamic)
|
2016-02-01 22:55:43 +01:00
|
|
|
{
|
2019-11-14 22:45:07 +01:00
|
|
|
if (dynamic && !rhs.get_is_dynamic())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-02-01 22:55:43 +01:00
|
|
|
switch (rhs.get_type())
|
|
|
|
{
|
|
|
|
case type::node:
|
|
|
|
{
|
|
|
|
if (data.IsScalar() || data.IsSequence())
|
|
|
|
{
|
|
|
|
return; // ???
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto& pair : data)
|
|
|
|
{
|
|
|
|
if (!pair.first.IsScalar()) continue;
|
|
|
|
|
|
|
|
// Find the key among existing nodes
|
2021-03-20 18:06:45 +01:00
|
|
|
for (const auto& node : static_cast<node&>(rhs).get_nodes())
|
2016-02-01 22:55:43 +01:00
|
|
|
{
|
2021-03-20 18:06:45 +01:00
|
|
|
if (node->get_name() == pair.first.Scalar())
|
2017-05-20 13:45:02 +02:00
|
|
|
{
|
2021-03-20 18:06:45 +01:00
|
|
|
decode(pair.second, *node, dynamic);
|
2017-05-20 13:45:02 +02:00
|
|
|
}
|
2016-02-01 22:55:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case type::set:
|
|
|
|
{
|
|
|
|
std::vector<std::string> values;
|
|
|
|
|
|
|
|
if (YAML::convert<decltype(values)>::decode(data, values))
|
|
|
|
{
|
|
|
|
rhs.from_list(std::move(values));
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
2017-05-13 20:30:37 +02:00
|
|
|
case type::log:
|
|
|
|
{
|
|
|
|
if (data.IsScalar() || data.IsSequence())
|
|
|
|
{
|
|
|
|
return; // ???
|
|
|
|
}
|
|
|
|
|
|
|
|
std::map<std::string, logs::level> values;
|
|
|
|
|
|
|
|
for (const auto& pair : data)
|
|
|
|
{
|
|
|
|
if (!pair.first.IsScalar() || !pair.second.IsScalar()) continue;
|
|
|
|
|
|
|
|
u64 value;
|
|
|
|
if (cfg::try_to_enum_value(&value, &fmt_class_string<logs::level>::format, pair.second.Scalar()))
|
|
|
|
{
|
|
|
|
values.emplace(pair.first.Scalar(), static_cast<logs::level>(static_cast<int>(value)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static_cast<log_entry&>(rhs).set_map(std::move(values));
|
|
|
|
break;
|
|
|
|
}
|
2016-02-01 22:55:43 +01:00
|
|
|
default:
|
|
|
|
{
|
|
|
|
std::string value;
|
|
|
|
|
|
|
|
if (YAML::convert<std::string>::decode(data, value))
|
|
|
|
{
|
2020-03-31 20:50:23 +02:00
|
|
|
rhs.from_string(value, dynamic);
|
2016-02-01 22:55:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
break; // ???
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string cfg::node::to_string() const
|
|
|
|
{
|
|
|
|
YAML::Emitter out;
|
|
|
|
cfg::encode(out, *this);
|
|
|
|
|
2017-08-02 12:22:53 +02:00
|
|
|
return {out.c_str(), out.size()};
|
2016-02-01 22:55:43 +01:00
|
|
|
}
|
|
|
|
|
2020-03-09 17:18:39 +01:00
|
|
|
bool cfg::node::from_string(const std::string& value, bool dynamic)
|
2017-08-02 12:23:12 +02:00
|
|
|
{
|
2020-03-09 17:18:39 +01:00
|
|
|
auto [result, error] = yaml_load(value);
|
|
|
|
|
|
|
|
if (error.empty())
|
|
|
|
{
|
|
|
|
cfg::decode(result, *this, dynamic);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
cfg_log.fatal("Failed to load node: %s", error);
|
2017-08-02 12:23:12 +02:00
|
|
|
return false;
|
|
|
|
}
|
2016-02-01 22:55:43 +01:00
|
|
|
|
|
|
|
void cfg::node::from_default()
|
|
|
|
{
|
|
|
|
for (auto& node : m_nodes)
|
|
|
|
{
|
2021-03-20 18:06:45 +01:00
|
|
|
node->from_default();
|
2016-02-01 22:55:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-20 13:45:02 +02:00
|
|
|
void cfg::_bool::from_default()
|
2017-01-25 00:22:19 +01:00
|
|
|
{
|
2017-05-20 13:45:02 +02:00
|
|
|
m_value = def;
|
2017-01-25 00:22:19 +01:00
|
|
|
}
|
|
|
|
|
2017-05-20 13:45:02 +02:00
|
|
|
void cfg::string::from_default()
|
2017-01-25 00:22:19 +01:00
|
|
|
{
|
2020-11-26 10:30:51 +01:00
|
|
|
m_value = def;
|
2017-01-25 00:22:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void cfg::set_entry::from_default()
|
|
|
|
{
|
|
|
|
m_set = {};
|
|
|
|
}
|
|
|
|
|
2017-05-13 20:30:37 +02:00
|
|
|
void cfg::log_entry::set_map(std::map<std::string, logs::level>&& map)
|
|
|
|
{
|
2020-03-28 15:28:23 +01:00
|
|
|
m_map = std::move(map);
|
2017-05-13 20:30:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void cfg::log_entry::from_default()
|
|
|
|
{
|
|
|
|
set_map({});
|
|
|
|
}
|