Search code examples
c++boostboost-program-optionsboost-propertytree

Read/Write inifiles with boost::{program_options,property_tree}


Utilizing boost, I would like to

  1. read options from an inifile, abort if an unknown option is encountered in the inifile and
  2. save them later in another inifile.

The first part can be done with 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;
}

To the best of my knowledge writing an inifile is not possible with boost::program_options, but boost::property_tree works:

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

Now the question is how can I translate the data stored in the po::variables_map to pt::ptree?

Reading the boost documentation leaves me with the impression that this is not possible. Is the following the only viable way?

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

It introduces quite a bit of redundancy for my taste. However, reading data into a property tree from the beginning does not seem to support checking for undefined/misspelled options.

Alternatively,is it possible to iterate over the contents of variables_map and somehow infer the corresponding datatype of each element?

The full code is here:

/*
 * 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;
}

The contents of the inifile "testini.ini" are:

[ops1]
i0=2

Solution

  • After giving this problem some more time, I found a suitable compact solution:

    The key is to write a function that translates entries from the variables_map to the propTree depending on their datatype (thx to sehe for putting me on the right track):

    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); }
        }
    }
    

    The full working example writes the correct inifile containing all read info:

    /*
     * 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;
    }
    

    Taking a variables_map vm from reading the commandline, it is also possible to update the values in the property tree (from reading the inifile) with:

    string opsName = "ops1.i0"; if(vm.count(opsName)) p.i0 = vm[opsName].as<int>();