boost :: program_options:带有固定和变量令牌的参数?

时间:2013-03-21 18:01:07

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

是否可以在boost :: program_options中使用此类参数?

program  -p1 123 -p2 234 -p3 345 -p12 678

即,是否可以使用第一个标记(例如-p)后跟一个数字来动态指定参数名称?
我想避免这个:

program  -p 1 123 -p 2 234 -p 3 345 -p 12 678

1 个答案:

答案 0 :(得分:11)

Boost.ProgramOptions不提供直接支持。然而,有两种一般的解决方案,每种解决方案都需要权衡:

  • 通配符选项。
  • 自定义解析器。

通配符选项

如果使用--p代替-p是可以接受的,则可以使用通配符选项。这需要在提取期间迭代variables_map,因为Boost.ProgramOptions不提供在重载的validate()函数中接收键和值的支持。

#include <iostream>
#include <map>
#include <string>
#include <utility>
#include <vector>

#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/program_options.hpp>

typedef std::map<int, int> p_options_type;

/// @brief Extract options from variable map with the a key of
///        <prefix>#*.
p_options_type get_p_options(
  const boost::program_options::variables_map& vm,
  const std::string prefix)
{
  p_options_type p_options;

  const std::size_t prefix_size = prefix.size();

  boost::iterator_range<std::string::const_iterator> range;
  namespace po = boost::program_options;
  BOOST_FOREACH(const po::variables_map::value_type& pair, vm)
  {
    const std::string& key = pair.first;

    // Key is too small to contain prefix and a value, continue to next.
    if (key.size() < (1 + prefix.size())) continue;

    // Create range that partitions key into two parts.  Given a key
    // of "p12" the resulting partitions would be:
    //
    //     ,--------- key.begin           -., prefix = "p"
    //    / ,-------- result.begin        -:, post-prefix = "12"
    //   / /   ,----- key.end, result.end -'
    //  |p|1|2|
    range = boost::make_iterator_range(key.begin() + prefix_size,
                                       key.end());

    // Iterate to next key if the key:
    // - does not start with prefix
    // - contains a non-digit after prefix
    if (!boost::starts_with(key, prefix) || 
        !boost::all(range, boost::is_digit()))
      continue;

    // Create pair and insert into map.
    p_options.insert(
      std::make_pair(
        boost::lexical_cast<int>(boost::copy_range<std::string>(range)),
        pair.second.as<int>())); 
  }
  return p_options;
}

int main(int ac, char* av[])
{
  namespace po = boost::program_options;
  po::options_description desc;
  desc.add_options()
    ("p*", po::value<int>())
    ;

  po::variables_map vm;
  store(po::command_line_parser(ac, av).options(desc).run(), vm);

  BOOST_FOREACH(const p_options_type::value_type& p, get_p_options(vm, "p"))
  {
    std::cout << "p" << p.first << "=" << p.second << std::endl;
  }
}

及其用法:

./a.out --p1 123 --p2 234 --p3=345 --p12=678
p1=123
p2=234
p3=345
p12=678

此方法需要迭代整个地图以识别通配符匹配,从而导致O(n)的复杂性。此外,它需要修改所需的语法,其中--p1 123需要使用而不是-p1 123。此限制是Boost.ProgramOptions的默认解析器行为的结果,其中单个连字符应该跟随单个字符。


自定义分析器

另一种方法是向command_line_parser添加custom parser。自定义解析器将允许-p1语法以及其他常见表单,例如--p1 123-p1=123。有一些行为需要处理:

  • 解析器一次会收到一个令牌。因此,它会在个别调用中收到p1123。解析器有责任将p1123配对。
  • Boost.ProgramOptions期望至少有一个解析器处理令牌。否则将抛出boost::program_options::unknown_option

为了解释这些行为,自定义解析器将管理状态并执行编码/解码:

  • 当解析器收到p1时,它会提取1,并在解析器中存储状态。此外,它还为p编码无操作值。
  • 当解析器收到123时,它会将其与存储状态一起编码为p的值。

因此,如果解析器收到-p1123,则variables_map的{​​{1}}中会插入2个值:无操作值和p

{ "p" : [ "no operation",
          "1:123" ] }

通过提供辅助函数将编码的1:123向量转换为映射,此编码对用户可以是透明的。解码的结果将是:

{ 1 : 123 }

以下是示例代码:

p

及其用法:

./a.out -p1 123 --p2 234 -p3=345 --p12=678
p1=123
p2=234
p3=345
p12=678

除了作为更大的解决方案之外,一个缺点是需要经历解码过程以获得期望的值。我们不能简单地以有意义的方式迭代#include <iostream> #include <map> #include <string> #include <utility> // std::pair, std::make_pair #include <vector> #include <boost/algorithm/string.hpp> #include <boost/foreach.hpp> #include <boost/lexical_cast.hpp> #include <boost/program_options.hpp> typedef std::map<int, int> p_options_type; /// @brief Parser that provides the ability to parse "-p# #" options. /// /// @note The keys and values are passed in separately to the parser. /// Thus, the struct must be stateful. class p_parser { public: explicit p_parser(const std::string& prefix) : prefix_(prefix), hyphen_prefix_("-" + prefix) {} std::pair<std::string, std::string> operator()(const std::string& token) { // To support "-p#=#" syntax, split the token. std::vector<std::string> tokens(2); boost::split(tokens, token, boost::is_any_of("=")); // If the split resulted in two tokens, then key and value were // provided as a single token. if (tokens.size() == 2) parse(tokens.front()); // Parse key. // Parse remaining token. // - If tokens.size() == 2, then the token is the value. // - Otherwise, it is a key. return parse(tokens.back()); } /// @brief Decode a single encoded value. static p_options_type::value_type decode(const std::string& encoded) { // Decode. std::vector<std::string> decoded(field_count_); boost::split(decoded, encoded, boost::is_any_of(delimiter_)); // If size is not equal to the field count, then encoding failed. if (field_count_ != decoded.size()) throw boost::program_options::invalid_option_value(encoded); // Transform. return std::make_pair(boost::lexical_cast<int>(decoded[0]), boost::lexical_cast<int>(decoded[1])); } /// @brief Decode multiple encoded values. static p_options_type decode(const std::vector<std::string>& encoded_values) { p_options_type p_options; BOOST_FOREACH(const std::string& encoded, encoded_values) { // If value is a no-op, then continue to next. if (boost::equals(encoded, noop_)) continue; p_options.insert(decode(encoded)); } return p_options; } private: std::pair<std::string, std::string> parse(const std::string& token) { return key_.empty() ? parse_key(token) : parse_value(token); } /// @brief Parse key portion of option: "p#" std::pair<std::string, std::string> parse_key(const std::string& key) { // Search for the prefix to obtain a range that partitions the key into // three parts. Given --p12, the partitions are: // // ,--------- key.begin -., pre-prefix = "-" // / ,-------- result.begin -:, prefix = "-p" // / / ,----- result.end -:, post-prefix = "12" // / / / ,-- key.end -' // |-|-|p|1|2| // boost::iterator_range<std::string::const_iterator> result = boost::find_first(key, prefix_); // Do not handle the key if: // - Key end is the same as the result end. This occurs when either // either key not found or nothing exists beyond the key (--a or --p) // - The distance from start to prefix start is greater than 2 (---p) // - Non-hyphens exists before prefix (a--p) // - Non-numeric values are after result. if (result.end() == key.end() || distance(key.begin(), result.begin()) > 2 || !boost::all( boost::make_iterator_range(key.begin(), result.begin()), boost::is_any_of("-")) || !boost::all( boost::make_iterator_range(result.end(), key.end()), boost::is_digit())) { // A different parser will handle this token. return make_pair(std::string(), std::string()); } // Otherwise, key contains expected format. key_.assign(result.end(), key.end()); // Return non-empty pair, otherwise Boost.ProgramOptions will // consume treat the next value as the complete value. The // noop entries will be stripped in the decoding process. return make_pair(prefix_, noop_); } /// @brief Parse value portion of option: "#" std::pair<std::string, std::string> parse_value(const std::string& value) { std::pair<std::string, std::string> encoded = make_pair(prefix_, key_ + delimiter_ + value); key_.clear(); return encoded; } private: static const int field_count_ = 2; static const std::string delimiter_; static const std::string noop_; private: const std::string prefix_; const std::string hyphen_prefix_; std::string key_; }; const std::string p_parser::delimiter_ = ":"; const std::string p_parser::noop_ = "noop"; /// @brief Extract and decode options from variable map. p_options_type get_p_options( const boost::program_options::variables_map& vm, const std::string prefix) { return p_parser::decode(vm[prefix].as<std::vector<std::string> >()); } int main(int ac, char* av[]) { const char* p_prefix = "p"; namespace po = boost::program_options; // Define options. po::options_description desc; desc.add_options() (p_prefix, po::value<std::vector<std::string> >()->multitoken()) ; po::variables_map vm; store(po::command_line_parser(ac, av).options(desc) .extra_parser(p_parser(p_prefix)).run() , vm); // Extract -p options. if (vm.count(p_prefix)) { // Print -p options. BOOST_FOREACH(const p_options_type::value_type& p, get_p_options(vm, p_prefix)) { std::cout << "p" << p.first << "=" << p.second << std::endl; } } } 的结果。