boost program_options: 从配置文件中读取所需的参数
boost program_options: Read required parameter from config file
我想使用boost_program_options如下:
- 获取可选配置文件的名称作为程序选项
- 从命令行或配置文件读取强制选项
问题是:包含配置文件名的变量在调用 po::notify()
之前不会被填充,并且该函数还会为任何未实现的强制选项抛出异常。因此,如果未在命令行上指定强制选项(使配置文件没有实际意义),则不会读取配置文件。
不优雅的解决方案是在 add_options()
中不将选项标记为强制性的,然后在 'by hand' 中强制执行它们。 boost_program_options 库中有解决这个问题的方法吗?
MWE
bpo-mwe.conf:
db-hostname = foo
db-username = arthurdent
db-password = forty-two
代码:
#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;
}
区分配置文件和命令行参数,不要将两者解析到同一个映射中。
首先分别解析命令行参数,获取配置文件名(如果有的话),然后然后加载文件并将其解析到第二个映射中。
如果一些配置文件值也可以在命令行上提供,那么我个人会对命令行参数进行两次传递,使其成为一个三步过程:
- 解析命令行参数,忽略除
config
选项之外的所有参数
- 读取并解析配置文件
- 并再次传递命令行参数,忽略
config
选项
我只是不使用通知值语义将值放入config_file
。相反,直接从地图使用它:
auto config_file = variable_map.at("config").as<std::string>();
现在您可以按预期在最后执行通知:
#include <boost/program_options.hpp>
#include <fstream>
#include <iomanip>
#include <iostream>
namespace po = boost::program_options;
int main(int argc, char *argv[])
{
po::options_description generic("Generic options");
generic.add_options()
("config,c", po::value<std::string>()->default_value("bpo-mwe.conf"), "configuration file")
;
// Declare a group of options that will be allowed both on command line and
// in config file
struct {
std::string host, user, pass;
} dbconf;
po::options_description main_options("Main options");
main_options.add_options()
("db-hostname", po::value<std::string>(&dbconf.host)->required(), "database service name")
("db-username", po::value<std::string>(&dbconf.user)->required(), "database user name")
("db-password", po::value<std::string>(&dbconf.pass)->required(), "database user password")
;
// 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;
//po::store(po::parse_command_line(argc, argv, desc), vm);
store(po::command_line_parser(argc, argv).options(cmdline_options).run(),
variable_map);
auto config_file = variable_map.at("config").as<std::string>();
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);
}
notify(variable_map);
std::cout << config_file << " was the config file\n";
std::cout << "dbconf: " << std::quoted(dbconf.host) << ", "
<< std::quoted(dbconf.user) << ", "
<< std::quoted(dbconf.pass) << "\n"; // TODO REMOVE FOR PRODUCTION :)
}
打印例如
$ ./sotest
bpo-mwe.conf was the config file
dbconf: "foo", "arthurdent", "forty-two"
$ ./sotest -c other.conf
other.conf was the config file
dbconf: "sbb", "neguheqrag", "sbegl-gjb"
$ ./sotest -c other.conf --db-user PICKME
other.conf was the config file
dbconf: "sbb", "PICKME", "sbegl-gjb"
您可能已经猜到了 other.conf
是由 ROT13 从 bpo-mwe.conf
派生的。
我想使用boost_program_options如下:
- 获取可选配置文件的名称作为程序选项
- 从命令行或配置文件读取强制选项
问题是:包含配置文件名的变量在调用 po::notify()
之前不会被填充,并且该函数还会为任何未实现的强制选项抛出异常。因此,如果未在命令行上指定强制选项(使配置文件没有实际意义),则不会读取配置文件。
不优雅的解决方案是在 add_options()
中不将选项标记为强制性的,然后在 'by hand' 中强制执行它们。 boost_program_options 库中有解决这个问题的方法吗?
MWE
bpo-mwe.conf:
db-hostname = foo
db-username = arthurdent
db-password = forty-two
代码:
#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;
}
区分配置文件和命令行参数,不要将两者解析到同一个映射中。
首先分别解析命令行参数,获取配置文件名(如果有的话),然后然后加载文件并将其解析到第二个映射中。
如果一些配置文件值也可以在命令行上提供,那么我个人会对命令行参数进行两次传递,使其成为一个三步过程:
- 解析命令行参数,忽略除
config
选项之外的所有参数 - 读取并解析配置文件
- 并再次传递命令行参数,忽略
config
选项
我只是不使用通知值语义将值放入config_file
。相反,直接从地图使用它:
auto config_file = variable_map.at("config").as<std::string>();
现在您可以按预期在最后执行通知:
#include <boost/program_options.hpp>
#include <fstream>
#include <iomanip>
#include <iostream>
namespace po = boost::program_options;
int main(int argc, char *argv[])
{
po::options_description generic("Generic options");
generic.add_options()
("config,c", po::value<std::string>()->default_value("bpo-mwe.conf"), "configuration file")
;
// Declare a group of options that will be allowed both on command line and
// in config file
struct {
std::string host, user, pass;
} dbconf;
po::options_description main_options("Main options");
main_options.add_options()
("db-hostname", po::value<std::string>(&dbconf.host)->required(), "database service name")
("db-username", po::value<std::string>(&dbconf.user)->required(), "database user name")
("db-password", po::value<std::string>(&dbconf.pass)->required(), "database user password")
;
// 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;
//po::store(po::parse_command_line(argc, argv, desc), vm);
store(po::command_line_parser(argc, argv).options(cmdline_options).run(),
variable_map);
auto config_file = variable_map.at("config").as<std::string>();
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);
}
notify(variable_map);
std::cout << config_file << " was the config file\n";
std::cout << "dbconf: " << std::quoted(dbconf.host) << ", "
<< std::quoted(dbconf.user) << ", "
<< std::quoted(dbconf.pass) << "\n"; // TODO REMOVE FOR PRODUCTION :)
}
打印例如
$ ./sotest
bpo-mwe.conf was the config file
dbconf: "foo", "arthurdent", "forty-two"
$ ./sotest -c other.conf
other.conf was the config file
dbconf: "sbb", "neguheqrag", "sbegl-gjb"
$ ./sotest -c other.conf --db-user PICKME
other.conf was the config file
dbconf: "sbb", "PICKME", "sbegl-gjb"
您可能已经猜到了 other.conf
是由 ROT13 从 bpo-mwe.conf
派生的。