繁体   English   中英

boost program_options:从配置文件中读取所需参数

[英]boost program_options: Read required parameter from config file

我想使用 boost_program_options 如下:

  • 获取可选配置文件的名称作为程序选项
  • 从命令行或配置文件读取强制选项

问题是:在调用po::notify()之前,不会填充包含配置文件名的变量,并且 function 也会为任何未实现的强制选项抛出异常。 因此,如果未在命令行上指定强制选项(渲染配置文件没有实际意义),则不会读取配置文件。

不优雅的解决方案是不在add_options()中将选项标记为强制选项,然后“手动”强制执行它们。 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;
}

区分配置文件和命令行 arguments,不要将两者解析为同一个 map。

而是先分别解析命令行 arguments,获取配置文件名(如果有),然后加载文件并将其解析为第二个 map。


如果也可以在命令行上提供一些配置文件值,那么我个人会在命令行 arguments 上进行两次传递,使其成为一个三步过程:

  1. 解析命令行 arguments,忽略除config选项之外的所有内容
  2. 读取并解析配置文件
  3. 并通过命令行 arguments 进行第二次传递,忽略config选项

我根本不会使用通知值语义将值放入config_file 相反,直接从 map 使用它:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM