5

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?

Darren Cook
  • 27,837
  • 13
  • 117
  • 217

2 Answers2

6

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;
}
rhashimoto
  • 15,650
  • 2
  • 52
  • 80
  • Thanks, that sounds like it will work. I'm just cautious to use another library, when I'm familiar with Program Options. Is what I'm trying to do not possible with the Program Options library then? (the INI style format I suggested in my question was mainly because Program Options offers that... though, of course, it is also nice and readable for most users) – Darren Cook Apr 24 '14 at 06:58
  • @DarrenCook, see edit for parsing with `program_options`. This is not how `program_options` is typically used, however, so it may be different from your prior experience with it. – rhashimoto Apr 25 '14 at 00:08
  • Thanks. I thought I'd try property_tree on your example data: `namespace pt = boost::property_tree;pt::ptree settings;pt::read_ini(fileData,settings);` I get a run-time error: "terminate called after throwing an instance of 'boost::exception_detail::clone_impl >'" – Darren Cook Apr 27 '14 at 02:28
  • The unfriendly error (with what looks like valid input, to me) is a shame, as I agree with you that the PropertyTree code feels cleaner for what I'm trying to do. – Darren Cook Apr 27 '14 at 02:37
  • @DarrenCook I think the exception you got is just because `property_tree` doesn't allow duplicate keys as `program_options` does so it was informing you of a syntax error. I added a sample `property_tree` program to my answer that roughly does the same thing. In practice, you may not need to traverse the tree but can just query with `get()` or `get_optional()` on the root. Also, recursive traversal is overkill for .ini files because they only allow two levels (unlike JSON and XML which `property_tree` also supports). – rhashimoto Apr 27 '14 at 04:28
  • Thanks again for the excellent help! It turns out duplicate keys give a different error message: "duplicate key name". The error I got was from trying to use a `string` instead of a `istringstream`: I mis-read the docs, and the string parameter version of `read_ini()` is for a filename! – Darren Cook Apr 27 '14 at 06:03
0

Don't know about the Boost Programs Options library. But the INI file format has a built-in support in the Win32 API.

In order to read all sections in the INI file without knowing their names you can use MSDN Win32 GetPrivateProfileSectionNames

In order to read all keys in a section you can use MSDN Win32 GetPrivateProfileSection

This API is said to be only for compatibility and in the background it can map INI file keys to the registry.

In practice it should work fine (it always did). You can even have UNICODE INI files assuming they have the BOM mark and you call the ..W versions of the API.

Suitability of the config file format and it's location (e.g. if it can be stored in registry) depends on your deployment scenarios

xmojmr
  • 8,073
  • 5
  • 31
  • 54