Однако чтение данных в дереве свойств с самого начала, похоже, не поддерживает проверку неопределенных / неправильно написанных опций
Хорошо. Это не совсем так. Вы можете сделать свою собственную функцию разбора, которая добавляет логику. Если хотите, используйте переводчики дерева свойств.
Вот расширенный пример, показывающий три параметра различных типов для проверки:
enum class restricted { value1, value2 };
struct params {
int i0 = 1;
restricted r1 = restricted::value2;
std::string s2 = "some default";
};
Нам понадобится функция разбора, подобная этой:
params read_inifile(std::string filename) {
params p;
pt::ptree tree;
std::ifstream file(filename);
read_ini(file, tree);
p.i0 = tree.get("ops1.i0", 1);
p.r1 = tree.get("ops1.r1", restricted::value2);
p.s2 = tree.get("ops1.s2", "some default");
return p;
}
Типы Streamable
Чтобы перевести и проверить перечисление, вам просто нужно реализовать потоковые операторы:
static inline std::istream& operator>>(std::istream& is, restricted& r) {
std::string v;
if (is >> std::ws >> v) {
if (boost::iequals("value1", v))
r = restricted::value1;
else if (boost::iequals("value2", v))
r = restricted::value2;
else
throw std::runtime_error("invalid restricted value");
}
return is;
}
static inline std::ostream& operator<<(std::ostream& os, restricted r) {
switch(r) {
case restricted::value1: return os << "value1";
case restricted::value2: return os << "value2";
default: return os << "invalid";
}
}
Пользовательские переводчики
Давайте представим, что i0
требуется пользовательская проверка. В этом примере давайте ТРЕБУЕМ, чтобы это было нечетное число:
namespace translators {
template <typename T>
struct must_be_odd {
typedef T internal_type;
typedef T external_type;
boost::optional<T> get_value(const std::string& str) const {
if (str.empty()) return boost::none;
T v = boost::lexical_cast<T>(str);
if (v % 2 == 0)
throw std::runtime_error("value must be odd");
return boost::make_optional(v);
}
boost::optional<std::string> put_value(const T& i0) {
assert(i0 % 2); // assert that the value was odd
return boost::lexical_cast<std::string>(i0);
}
};
static const must_be_odd<int> i0;
}
Теперь мы можем просто предоставить переводчик (здесь, действующий больше как пользовательский валидатор, такой как Boost Program Options, также имеет их):
p.i0 = tree.get("ops1.i0", 1, translators::i0);
Посмотри Live On Coliru
Неподдерживаемые параметры
Это немного больше работы. Вам придется перебирать дерево, проверяя результирующие пути по известному набору. Вот пример разумно общей реализации этого (которая должна корректно работать с чувствительными к регистру деревьями любого (широкого) строкового типа):
template <typename Tree,
typename Path = typename Tree::path_type,
typename Key = typename Path::key_type,
typename Cmp = typename Tree::key_compare>
std::size_t unsupported(Tree const& tree, std::set<Key, Cmp> const& supported, Path prefix = "") {
if (tree.size()) {
std::size_t n = 0;
for (auto& node : tree) {
Path sub = prefix;
sub /= node.first;
n += unsupported(node.second, supported, sub);
}
return n;
} else {
if (!supported.count(prefix.dump()) && tree.template get_value_optional<std::string>())
return 1;
}
return 0;
}
Вы можете использовать его так:
if (auto n = unsupported(tree, {"ops1.i0", "ops1.r1", "ops2.s2"})) {
throw std::runtime_error(std::to_string(n) + " unsupported options");
}
Полная демонстрация
Live On Coliru
#include <boost/algorithm/string.hpp>
#include <iostream>
#include <set>
enum class restricted { value1, value2 };
static inline std::istream& operator>>(std::istream& is, restricted& r) {
std::string v;
if (is >> std::ws >> v) {
if (boost::iequals("value1", v))
r = restricted::value1;
else if (boost::iequals("value2", v))
r = restricted::value2;
else
throw std::runtime_error("invalid restricted value");
}
return is;
}
static inline std::ostream& operator<<(std::ostream& os, restricted r) {
switch(r) {
case restricted::value1: return os << "value1";
case restricted::value2: return os << "value2";
default: return os << "invalid";
}
}
struct params {
int i0 = 1;
restricted r1 = restricted::value2;
std::string s2 = "some default";
};
#include <boost/property_tree/ini_parser.hpp>
#include <boost/lexical_cast.hpp>
#include <fstream>
namespace pt = boost::property_tree;
namespace translators {
template <typename T>
struct must_be_odd {
typedef T internal_type;
typedef T external_type;
boost::optional<T> get_value(const std::string& str) const {
if (str.empty()) return boost::none;
T v = boost::lexical_cast<T>(str);
if (v % 2 == 0)
throw std::runtime_error("value must be odd");
return boost::make_optional(v);
}
boost::optional<std::string> put_value(const T& i0) {
assert(i0 % 2); // assert that the value was odd
return boost::lexical_cast<std::string>(i0);
}
};
static const must_be_odd<int> i0;
}
template <typename Tree,
typename Path = typename Tree::path_type,
typename Key = typename Path::key_type,
typename Cmp = typename Tree::key_compare>
std::size_t unsupported(Tree const& tree, std::set<Key, Cmp> const& supported, Path prefix = "") {
if (tree.size()) {
std::size_t n = 0;
for (auto& node : tree) {
Path sub = prefix;
sub /= node.first;
n += unsupported(node.second, supported, sub);
}
return n;
} else {
if (!supported.count(prefix.dump()) && tree.template get_value_optional<std::string>())
return 1;
}
return 0;
}
params read_inifile(std::string filename) {
params p;
try {
pt::ptree tree;
std::ifstream file(filename);
read_ini(file, tree);
p.i0 = tree.get("ops1.i0", 1, translators::i0);
p.r1 = tree.get("ops1.r1", restricted::value2);
p.s2 = tree.get("ops1.s2", "some default");
if (auto n = unsupported(tree, {"ops1.i0", "ops1.r1", "ops2.s2"})) {
throw std::runtime_error(std::to_string(n) + " unsupported options");
}
} catch (std::exception const& e) {
std::cerr << "error: " << e.what() << "\n";
throw std::runtime_error("read_inifile");
}
return p;
}
pt::ptree to_ptree(params const& p) {
pt::ptree tree;
tree.put("ops1.i0", p.i0, translators::i0);
tree.put("ops1.r1", p.r1);
tree.put("ops1.s2", p.s2);
return tree;
}
int main() {
params const p = read_inifile("./testini.ini"); // get options from filename
write_ini("./used0.ini", to_ptree(p)); // save options to used.ini
std::cout << p.i0 << std::endl;
}
Для ввода типа
[ops1]
i0=17
i99=oops
[oops1]
also=oops
печать
error: 2 unsupported options
terminate called after throwing an instance of 'std::runtime_error'
what(): read_inifile
И изменение 17
на 18
печать :
error: value must be odd
terminate called after throwing an instance of 'std::runtime_error'
what(): read_inifile
При правильном вводе, used0.ini
будет записано как ожидалось:
[ops1]
i0=1
r1=value2
s2=some default