I want to use boost_program_options as follows:
- get name of an optional config file as a program option
- read mandatory options either from command line or the config file
The problem is: The variable containing the config file name is not populated until po::notify()
is called, and that function also throws exceptions for any unfulfilled mandatory options. So if the mandatory options are not specified on the command line (rendering the config file moot), the config file is not read.
The inelegant solution is to not mark the options as mandatory in add_options()
, and enforce them 'by hand' afterwards. Is there a solution to this within the boost_program_options library?
MWE
bpo-mwe.conf:
db-hostname = foo
db-username = arthurdent
db-password = forty-two
Code:
#include <stdexcept>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <string>
#include <boost/program_options.hpp>
// enable/disable required() below
#ifndef WITH_REQUIRED
#define WITH_REQUIRED
#endif
namespace po = boost::program_options;
namespace fs = std::filesystem;
int main(int argc, char *argv[])
{
std::string config_file;
po::options_description generic("Generic options");
generic.add_options()
("config,c", po::value<std::string>(&config_file)->default_value("bpo-mwe.conf"), "configuration file")
;
// Declare a group of options that will be
// allowed both on command line and in
// config file
po::options_description main_options("Main options");
main_options.add_options()
#ifdef WITH_REQUIRED
("db-hostname", po::value<std::string>()->required(), "database service name")
("db-username", po::value<std::string>()->required(), "database user name")
("db-password", po::value<std::string>()->required(), "database user password")
#else
("db-hostname", po::value<std::string>(), "database service name")
("db-username", po::value<std::string>(), "database user name")
("db-password", po::value<std::string>(), "database user password")
#endif
;
// set options allowed on command line
po::options_description cmdline_options;
cmdline_options.add(generic).add(main_options);
// set options allowed in config file
po::options_description config_file_options;
config_file_options.add(main_options);
// set options shown by --help
po::options_description visible("Allowed options");
visible.add(generic).add(main_options);
po::variables_map variable_map;
// store command line options
// Why not po::store?
//po::store(po::parse_command_line(argc, argv, desc), vm);
store(po::command_line_parser(argc, argv).options(cmdline_options).run(), variable_map);
notify(variable_map); // <- here is the problem point
// Problem: config_file is not set until notify() is called, and notify() throws exception for unfulfilled required variables
std::ifstream ifs(config_file.c_str());
if (!ifs)
{
std::cout << "can not open configuration file: " << config_file << "\n";
}
else
{
store(parse_config_file(ifs, config_file_options), variable_map);
notify(variable_map);
}
std::cout << config_file << " was the config file\n";
return 0;
}