Search code examples
c++boostboost-program-options

Multiple repeated sections in a config file


I've a config file format I was hoping to implement with Boost Program Options (as I've used that library before), but I somehow have to implement blocks like this:

label = whatever
depth = 3

start
source = /etc
dest = /tmp/etc/
end

start
source = /usr/local/include
dest = /tmp/include
depth = 1
end

I read in the docs that I can have [sections], so I first wondered about this:

label = whatever
depth = 3

[dir]
source = /etc
dest = /tmp/etc/

[dir]
source = /usr/local/include
dest = /tmp/include
depth = 1

But if I've understood correctly, the dir becomes part of the variable name, so duplicates would not be possible, and this would not work. So then I wondered about moving source to be the section name:

label = whatever
depth = 3

[/etc]
dest = /tmp/etc/

[/usr/local/include]
dest = /tmp/include
depth = 1

Does that seem a reasonable approach? I'm wondering how I iterate through a list of sections, when I don't know the section names in advance?

Or, is there a better way to use the Program Options library to achieve this?


Solution

  • Perhaps you should use Boost property_tree instead of program_options, as your file format appears to be very similar to the Windows INI file format. Boost property_tree has a parser for INI files (and a serializer in case you need that, too).

    Processing your options would then be implemented by traversing the tree. Options not in a section will be under the tree root, and section options will be under a node for that section.

    You can use program_options if you really want to. The key is to pass true for the final argument to parse_config_file, which is allow_unregistered_options:

    #include <iostream>
    #include <sstream>
    #include <boost/program_options.hpp>
    
    static const std::string fileData = // sample input
       "foo=1\n"
       "[bar]\n"
       "foo=a distinct foo\n"
       "[/etc]\n"
       "baz=all\n"
       "baz=multiple\n"
       "baz=values\n"
       "a.baz=appear\n";
    
    int main(int argc, char *argv[]) {
       namespace po = boost::program_options;
    
       std::istringstream is(fileData);
       po::parsed_options parsedOptions = po::parse_config_file(
          is,
          po::options_description(),
          true);                     // <== allow unregistered options
    
       // Print out results.
       for (const auto& option : parsedOptions.options) {
          std::cout << option.string_key << ':';
    
          // Option value is a vector of strings.
          for (const auto& value : option.value)
             std::cout << ' ' << value;
          std::cout << '\n';
       }
    
       return 0;
    }
    

    This outputs:

    $ ./po
    foo: 1
    bar.foo: a distinct foo
    /etc.baz: all
    /etc.baz: multiple
    /etc.baz: values
    /etc.baz: appear
    

    However, note that what you get with this approach is a vector of options and not the map that the typical use of program_options produces. So you may end up processing the parsed_options container into something you can query more easily, and that something may look like a property_tree.

    Here's a similar program that uses property_tree. The input is slightly different because property_tree does not allow duplicate keys.

    #include <iostream>
    #include <sstream>
    #include <boost/property_tree/ptree.hpp>
    #include <boost/property_tree/ini_parser.hpp>
    
    static const std::string fileData = // sample input                             
       "foo=1\n"
       "[bar]\n"
       "foo=a distinct foo\n"
       "[/etc]\n"
       "foo=and another\n"
       "baz=all\n";
    
    static void print_recursive(
       const std::string& prefix,
       const boost::property_tree::ptree& ptree) {
       for (const auto& entry : ptree) {
          const std::string& key = entry.first;
          const boost::property_tree::ptree& value = entry.second;
          if (!value.data().empty())
             std::cout << prefix + key << ": " << value.data() << '\n';
          else
             print_recursive(prefix + key + '.', value);
       }
    }
    
    int main() {
       namespace pt = boost::property_tree;
    
       std::istringstream is(fileData);
       pt::ptree root;
       pt::read_ini(is, root);
    
       print_recursive("", root);
       return 0;
    }