Search code examples
c++boostconfigcommand-line-argumentsboost-program-options

How to use options for both command line and hierarchical config file with Boost Program_Options


Imagine I have a program that takes the following command-line option:

$ prog1 --foo 1

I also have a configuration file that is in this hierarchical format, with the foo parameter within the prog1 scope:

[prog1]
foo = 42

My understanding of Boost::program_options is that the command-line option is defined similar to:

description.add_options()
    ("foo", "Set foo value");

However I want the value in the config file to be a default value (as an outcome of calling store() twice, first with the command-line description, then the config file description), able to be overriden by the command-line option, but I believe the config file option has to be declared as:

description.add_options()
    ("prog1.foo", "Set foo value");

Note the use of prog1.foo here, especially the prog1. prefix.

So my question is, if you use hierarchical config-file options, how do you associate these with command line options? I.e. how do I make --foo and prog1.foo represent the same configuration value in the Storage object?


Solution

  • I've learned enough to answer my own question. Hopefully this will help someone one day, because the Boost::Program_Options documentation does not cover this.

    If you create a hierarchical config option like this (this is documented):

        config_only_opts.add_options()
            ("section.option", po::bool_switch(), "Some option");
    

    And pass it to both parse_command_line() and parse_config_file().

    Then the config file would look like this:

    [section]
    option=1
    

    And the command-line option would look like this (this is not documented):

    --section.option
    

    So the hierarchical options translate into dotted command-line options. Therefore I'd expect this:

    [one.two]
    three=4
    

    To be equivalant to the following config option:

    --one.two.three=4
    

    Defaults that are applied to the command-line or config file options apply in the way you'd expect, depending on the order that store() is called.

    If you want to associate an arbitrary command-line option, such as --foo, with a hierarchical (or flat) config file value like bar.baz, then you can register the former with parse_command_line() and the latter with parse_config_file(), but reference the same storage variable for each:

        bool foo;
    
        po::options_description cmdline_opts;
        cmdline_only_opts.add_options()
            ("foo", po::value<bool>(&foo), "Foo")
            ;
    
        po::options_description config_file_opts;
        config_only_opts.add_options()
            ("bar.baz", po::value<bool>(&foo))
            ;
    
        po::variables_map vm;
        po::store(po::parse_command_line(argc, argv, cmdline_opts), vm);
        po::store(po::parse_config_file("config.file", config_file_opts, true /* allow unregistered */), vm);
        po::notify(vm);
    

    Now you can use --foo or

    [bar]
    baz=X
    

    to set the variable foo. The only caveat to be careful about is that you can no longer use vm[XXX].as<T>() where "XXX" is "foo" or "bar.baz" to access the value - just use the referenced storage variable instead.