Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Boost parse_config_file, empty key value

I am using Boost program_options to parse a config file in the standard way as
shown in the multiple_sources.cpp example file of program_options.

    ifstream ini_file("config.ini");  
    po::store(po::parse_config_file(ini_file, desc, true), vm);  
    po::notify(vm);

The config.ini file however has empty key=value pairs such as:

key1=value1  
key2=value2  
key3=  
key4=  
key5=value5  

Trying to read this file results in a Boost error:

boost::program_options::invalid_option_value

Is there any mechanism in boost::program_options to read such config files with empty entires?

Any help would be much appreciated.


I am editing this Question since the the problem has not yet been solved. I will just cite the example from Boost (1.53).

This is the full multiple_sources.cpp file:

#include <boost/program_options.hpp>
namespace po = boost::program_options;


#include <iostream>
#include <fstream>
#include <iterator>
using namespace std;

// A helper function to simplify the main part.
template<class T>
ostream& operator<<(ostream& os, const vector<T>& v)
{
    copy(v.begin(), v.end(), ostream_iterator<T>(os, " ")); 
    return os;
}


int main(int ac, char* av[])
{
    try {
        int opt;
        string config_file;

        // Declare a group of options that will be 
        // allowed only on command line
        po::options_description generic("Generic options");
        generic.add_options()
            ("version,v", "print version string")
            ("help", "produce help message")
            //("optimization","optimization level")      
            ("config,c", po::value<string>(&config_file)->default_value("multiple_sources.cfg"),
                  "name of a file of a configuration.")
            ;

        // Declare a group of options that will be 
        // allowed both on command line and in
        // config file
        po::options_description config("Configuration");
        config.add_options()
            ("optimization", po::value<int>(&opt)->default_value(10), 
                  "optimization level")
            ("include-path,I", po::value< vector<string> >()->composing(), 
                 "include path")
            ;

        // Hidden options, will be allowed both on command line and
        // in config file, but will not be shown to the user.
        po::options_description hidden("Hidden options");
        hidden.add_options()
            ("input-file", po::value< vector<string> >(), "input file")
            ;

        po::options_description cmdline_options;
        cmdline_options.add(generic).add(config).add(hidden);

        po::options_description config_file_options;
        config_file_options.add(config).add(hidden);

        po::options_description visible("Allowed options");
        visible.add(generic).add(config);

        po::positional_options_description p;
        p.add("input-file", -1);

        po::variables_map vm;
        store(po::command_line_parser(ac, av).
              options(cmdline_options).positional(p).run(), vm);
        notify(vm);

        ifstream ifs(config_file.c_str());
        if (!ifs)
        {
            cout << "can not open config file: " << config_file << "\n";
            return 0;
        }
        else
        {
            store(parse_config_file(ifs, config_file_options), vm);
            notify(vm);
        }

        if (vm.count("help")) {
            cout << visible << "\n";
            return 0;
        }

        if (vm.count("version")) {
            cout << "Multiple sources example, version 1.0\n";
            return 0;
        }

        if (vm.count("include-path"))
        {
            cout << "Include paths are: " 
                 << vm["include-path"].as< vector<string> >() << "\n";
        }

        if (vm.count("input-file"))
        {
            cout << "Input files are: " 
                 << vm["input-file"].as< vector<string> >() << "\n";
        }

        cout << "Optimization level is " << opt << "\n";                
    }
    catch(exception& e)
    {
        cout << e.what() << "\n";
        return 1;
    }    
    return 0;
}

And the corresponding configuration file (multiple_sources.cfg) is:

#
# Comment out this line to use hard-coded default value of 10
# 
optimization = 1
include-path = /opt

If this file is now modified to:

#
# Comment out this line to use hard-coded default value of 10
# 
optimization = 
include-path = /opt

The following error message is thrown:

the argument for option 'optimization' is invalid

The proposed solutions with validation overloading etc. seem unnecessarily complicated, especially since one might have to write a validation function for each data type, incorporating the possibility of recognizing '\n' other white-space.

Any suggestions? Thank you everyone for taking the time.


Following Aditya's suggestion I have replaced the following line :

            ("optimization", po::value<int>(&opt)->default_value(10), 
                  "optimization level")

with the following :

            ("optimization", po::value<int>(&opt)->zero_tokens(), 
                  "optimization level")

and :

            ("optimization", po::value<int>(&opt)->implicit_value(10), 
                  "optimization level")

and neither of them read blank options. Boost example program "parser_test.cpp" bypasses the use of zero_tokens(), or implicit_value() by reading the key-value pairs into a vector as follows:

void test_config_file(const char* config_file)
{
    options_description desc;
    desc.add_options()
        ("gv1", new untyped_value)
        ("gv2", new untyped_value)
        ("empty_value", new untyped_value)
        ("plug*", new untyped_value)
        ("m1.v1", new untyped_value)
        ("m1.v2", new untyped_value)
        ("b", bool_switch())
    ;

    const char content1[] =
    " gv1 = 0#asd\n"
    "empty_value = \n"
    "plug3 = 7\n"
    "b = true\n"
    "[m1]\n"
    "v1 = 1\n"
    "\n"
    "v2 = 2\n"    
    ;

    vector<option> a2 = parse_config_file<char>(config_file, desc).options;
    BOOST_REQUIRE(a2.size() == 6);
    check_value(a2[0], "gv1", "0");
    check_value(a2[1], "empty_value", "");
    check_value(a2[2], "plug3", "7");
    check_value(a2[3], "b", "true");
    check_value(a2[4], "m1.v1", "1");
    check_value(a2[5], "m1.v2", "2");
}
like image 713
Ess Gee Avatar asked Feb 26 '13 15:02

Ess Gee


2 Answers

I'd recommend you to surround with try/catch this exception and throw errors only for those fields who are really necessary for your program and can't be empty, otherwise the field will be ignored.

like image 177
Tchesko Avatar answered Oct 17 '22 12:10

Tchesko


No, there is currently no way to handle this within boost::program_options. Having an empty key:value pair in your INI file is the same as specifying the option on the command line without providing the argument. The approache of writing custom validators suggested by ypnos may work but seems impractical as you would need to apply it to every option you expect may be left blank. You'll have to write your own implementation of po::parse_config_file that would ignore lines without a value (unless the corresponding option is marked as zero_token) to get the result you are looking for.

like image 36
McManip Avatar answered Oct 17 '22 12:10

McManip