Чтение / запись файлов с надписью boost :: {program_options, property_tree} - PullRequest
0 голосов
/ 08 мая 2018

Используя повышение, я бы хотел

  1. чтение параметров из inifile, отмена, если в файле обнаружена неизвестная опция, и
  2. сохраните их позже в другом файле.

Первая часть может быть сделана с помощью boost :: program_options:

try{
    inifile_options.add_options()
    ("ops1.i0", po::value<int>(&p.nx)->default_value(1), "test integer")
    ;

    po::variables_map vm;
    po::store(po::parse_config_file(pthfnini, inifile_options), vm);
    po::notify(vm);
}   
catch(exception& e){
    cerr << "error: " << e.what() << "\n";
    errorflag=1;
}

Насколько я знаю, написание бустера невозможно с boost :: program_options, но boost :: property_tree работает:

pt::ptree iniPropTree;
pt::ini_parser::write_ini("./used0.ini",iniPropTree);

Теперь вопрос в том, как я могу перевести данные, хранящиеся в po :: variable_map, в pt :: ptree?

Чтение форсированной документации оставляет у меня впечатление, что это невозможно. Является ли следующий единственный жизнеспособный путь?

iniPropTree.put<int>("ops1.i0",vm["ops1.i0"].as<int>();

Это вводит немного избыточности на мой вкус. Однако чтение данных в дереве свойств с самого начала, по-видимому, не поддерживает проверку неопределенных / неправильно написанных опций.

В качестве альтернативы, можно ли перебирать содержимое variable_map и каким-либо образом выводить соответствующий тип данных каждого элемента?

Полный код здесь:

/*
 * g++ iniOps_test.cpp -Wall -std=c++11 -O3 -lboost_system -lboost_program_options -o iniOps_test.exe
 * 
 */

// C++11 & Boost libraries
#include <boost/program_options.hpp>            // po::options_description, po::variables_map, ...
#include <boost/property_tree/ptree.hpp>        // pt::ptree
#include <boost/property_tree/ini_parser.hpp>   // write_ini()
#include <iostream>                             // cout
#include <fstream>                              // ofstream, ifstream


// namespaces
namespace po = boost::program_options;
namespace pt = boost::property_tree;
using namespace std;


struct params{
    std::string inipthfn;
    int i0;
};


void read_inifile(params &p, po::variables_map &vm){

    // initialize variables
    int errorflag=0;
    std::ifstream pthfnini("./testini.ini");
    po::options_description inifile_options("Allowed inifile options");

    try{
        inifile_options.add_options()
        ("ops1.i0", po::value<int>(&p.i0)->default_value(1), "test integer")
        ;

        ;
        po::store(po::parse_config_file(pthfnini, inifile_options), vm);
        po::notify(vm);
    }
    catch(exception& e){
        cerr << "error: " << e.what() << "\n";
        errorflag=1;
    }

    pthfnini.close();
    if(errorflag){ std::cout<<"--- program shutdown due to error in read_inifile ---"<<std::endl; exit(1); }
}


int main(){

    params p;
    po::variables_map vm;
    pt::ptree iniPropTree;

    read_inifile(p,vm);                                     // get options from inifile

    // ??? conversion from vm -> pt ???

    pt::ini_parser::write_ini("./used0.ini",iniPropTree);   // save options to used.ini
    cout << p.i0 << endl;

    return 0;
}

Содержимое файла "testini.ini":

[ops1]
i0=2

Ответы [ 3 ]

0 голосов
/ 08 мая 2018

Однако чтение данных в дереве свойств с самого начала, похоже, не поддерживает проверку неопределенных / неправильно написанных опций

Хорошо. Это не совсем так. Вы можете сделать свою собственную функцию разбора, которая добавляет логику. Если хотите, используйте переводчики дерева свойств.

Вот расширенный пример, показывающий три параметра различных типов для проверки:

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
0 голосов
/ 20 мая 2018

После того, как я уделил этой проблеме больше времени, я нашел подходящее компактное решение:

Ключ в том, чтобы написать функцию, которая переводит записи из файла variable_map в propTree в зависимости от их типа данных (спасибо за то, что поставил меня на правильный путь):

void translate_variables_map_to_ptree(po::variables_map &vm, pt::ptree &propTree){

    for(po::variables_map::iterator it=vm.begin(); it!=vm.end(); it++){
        if( it->second.value().type() == typeid(int) ){ propTree.put<int>(it->first,vm[it->first].as<int>()); }
        else if( it->second.value().type() == typeid(float) ){ propTree.put<float>(it->first,vm[it->first].as<float>()); }
        else if( it->second.value().type() == typeid(double) ){ propTree.put<double>(it->first,vm[it->first].as<double>()); }
        else if( it->second.value().type() == typeid(std::string) ){ propTree.put<std::string>(it->first,vm[it->first].as<std::string>()); }
        else if( it->second.value().type() == typeid(size_t) ){ propTree.put<size_t>(it->first,vm[it->first].as<size_t>()); }
        else{ printf("Error: unknown datatype. Abort!\n"); exit(EXIT_FAILURE); }
    }
}

Полный рабочий пример записывает правильный файл, содержащий всю прочитанную информацию:

/*
 * g++ iniOps_test.cpp -Wall -std=c++11 -O3 -lboost_system -lboost_program_options -o iniOps_test.exe
 * 
 */

// C++11 & Boost libraries
#include <boost/program_options.hpp>            // po::options_description, po::variables_map, ...
#include <boost/property_tree/ptree.hpp>        // pt::ptree
#include <boost/property_tree/ini_parser.hpp>   // write_ini()
#include <iostream>                             // cout
#include <fstream>                              // ofstream, ifstream


// namespaces
namespace po = boost::program_options;
namespace pt = boost::property_tree;
using namespace std;


struct params{
    std::string s0;
    int i0;
};


void read_inifile(params &p, po::variables_map &vm){

    // initialize variables
    int errorflag=0;
    std::ifstream pthfnini("./testini.ini");
    po::options_description inifile_options("Allowed inifile options");

    try{
        inifile_options.add_options()
        ("ops1.i0", po::value<int>(&p.i0)->default_value(1), "test integer")
        ("ops1.s0", po::value<std::string>(&p.s0)->default_value("default"), "test string")
        ;

        ;
        po::store(po::parse_config_file(pthfnini, inifile_options), vm);
        po::notify(vm);
    }
    catch(exception& e){
        cerr << "error: " << e.what() << "\n";
        errorflag=1;
    }

    pthfnini.close();
    if(errorflag){ std::cout<<"--- program shutdown due to error in read_inifile ---"<<std::endl; exit(1); }
}

void translate_variables_map_to_ptree(po::variables_map &vm, pt::ptree &propTree){

    for(po::variables_map::iterator it=vm.begin(); it!=vm.end(); it++){
        if( it->second.value().type() == typeid(int) ){ propTree.put<int>(it->first,vm[it->first].as<int>()); }
        else if( it->second.value().type() == typeid(float) ){ propTree.put<float>(it->first,vm[it->first].as<float>()); }
        else if( it->second.value().type() == typeid(double) ){ propTree.put<double>(it->first,vm[it->first].as<double>()); }
        else if( it->second.value().type() == typeid(std::string) ){ propTree.put<std::string>(it->first,vm[it->first].as<std::string>()); }
        else if( it->second.value().type() == typeid(size_t) ){ propTree.put<size_t>(it->first,vm[it->first].as<size_t>()); }
        else{ printf("Error: unknown datatype. Abort!\n"); exit(EXIT_FAILURE); }
    }
}


int main(){

    params p;
    po::variables_map vm;
    pt::ptree iniPropTree;

    read_inifile(p,vm);                                     // get options from inifile
    translate_variables_map_to_ptree(vm,iniPropTree);       // conversion from vm -> pt 
    pt::ini_parser::write_ini("./used0.ini",iniPropTree);   // save options to used.ini

    cout << p.i0 << endl;
    cout << p.s0 << endl;

    return 0;
}

Взяв переменную_карту vm из чтения командной строки, также можно обновить значения в дереве свойств (из чтения inifile) с помощью:

string opsName = "ops1.i0"; if(vm.count(opsName)) p.i0 = vm[opsName].as<int>();
0 голосов
/ 08 мая 2018

Здесь есть концептуальная проблема.

Параметры командной строки являются текстовыми.

Значения в карте переменных отсутствуют. Используемые типы настраиваются в семантике значений (часть описания параметров).

Если все ваши варианты имеют одинаковый тип, вы можете «обмануть» и жестко закодировать конверсию:

pt::ptree to_ptree(po::variables_map const& vm) {
    pt::ptree tree;
    for (auto& v : vm) {
        if (!v.second.empty() && !v.second.defaulted())
            tree.put(v.first, v.second.as<int>());
    }

    return tree;
}

Что экономит:

[ops1]
i0=1

Если вам нужна большая гибкость, вам нужен как минимум доступ к описаниям опций. Это не предназначение библиотеки, и вы, вероятно, скоро столкнетесь с недокументированными частями реализации.

...