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