【问题标题】:boost program_options: Read required parameter from config fileboost program_options:从配置文件中读取所需参数
【发布时间】:2022-01-02 20:05:42
【问题描述】:

我想使用 boost_program_options 如下:

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

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

不优雅的解决方案是不在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;
}

【问题讨论】:

    标签: c++ boost boost-program-options


    【解决方案1】:

    我只是使用通知值语义将值放入config_file。相反,直接从地图中使用它:

    auto config_file = variable_map.at("config").as<std::string>();
    

    现在您可以按预期在最后执行通知:

    Live On Coliru

    #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 派生而来的。

    【讨论】:

      【解决方案2】:

      区分配置文件和命令行参数,不要将两者解析到同一个映射中。

      而是先分别解析命令行参数,获取配置文件名(如果有的话)然后然后加载文件并解析到第二个映射中。


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

      1. 解析命令行参数,忽略除config 选项以外的所有参数
      2. 读取并解析配置文件
      3. 并再次传递命令行参数,忽略 config 选项

      【讨论】:

      • 这是一种方法,但是当您想要进行验证(唯一/强制选项)或累积(多值/多令牌选项)时,它不会变得笨拙吗?我认为可以期望一个 Program Options 框架将抽象出源代码并提供声明性选项规范。
      • @sehe 我通常在程序选项之上构建一种“配置”框架,所以像这样使用多次传递也不错。现在我想起来了,我从来没有根据需要标记的论点。我创建的框架之后有自己的验证。快速搜索出现了this old answer,这可能是 OP 的更好解决方案。这就是您在回答中所做的。 :)
      猜你喜欢
      • 1970-01-01
      • 2019-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-27
      • 1970-01-01
      • 2011-11-15
      相关资源
      最近更新 更多