提高:: program_options:参数具有固定和可变令牌? [英] boost::program_options: parameters with a fixed and a variable token?

查看:149
本文介绍了提高:: program_options:参数具有固定和可变令牌?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否有可能使用这种具有升压:: program_options的参数?

 程序-p1 123 -p2 234 -p3 345 -p12 678

即,是否有可能与第一令牌指定参数名称(例如, -p ),其次是一个数字,动态?结果
我想避免这种情况:

 程序-p 1 123 -p 2 234 -p 3 -p 345 678 12


解决方案

Boost.ProgramOptions不提供这种直接的支持。尽管如此,一般有两种解决方案,每个人都有自己的权衡:


  • 通配符选项。

  • 自定义解析器。


通配符选项

如果它是一个可以接受的使用 - P 而不是 -p ,然后一个通配符选项可用过的。这需要提取期间通过 variables_map 迭代,因为Boost.ProgramOptions不提供支持接收无论是在重载确认键和值()功能。

 的#include<&iostream的GT;
#包括LT&;地图和GT;
#包括LT&;串GT;
#包括LT&;实用>
#包括LT&;矢量>#包括LT&;提升/算法/ string.hpp>
#包括LT&;升压/ foreach.hpp>
#包括LT&;升压/ lexical_cast.hpp>
#包括LT&;升压/ program_options.hpp>的typedef的std ::地图< INT,INT> p_options_type;///从变图@brief提取选项用的关键
///< preFIX>#*。
p_options_type get_p_options(
  常量的boost :: program_options :: variables_map&安培; VM,
  常量标准::字符串preFIX)
{
  p_options_type p_options;  常量的std ::为size_t prefix_size = prefix.size();  提高:: iterator_range的<的std ::字符串::为const_iterator>范围;
  命名空间PO =的boost :: program_options;
  BOOST_FOREACH(常量PO :: variables_map :: VALUE_TYPE&安培;对,VM)
  {
    常量标准::字符串&安培;关键= pair.first;    //关键是太小,无法包含preFIX和值,继续下一个。
    如果(key.size()≤(1 + prefix.size()))继续;    //创建一个分区键分为两部分范围。给定一个键
    的P12//所产生的分区将是:
    //
    //,--------- key.begin - ,preFIX =p
    // /,-------- result.begin - :,后preFIX =12
    // / /,----- key.end,result.end -
    // | P | 1 | 2 |
    范围=的boost :: make_iterator_range(key.begin()+ prefix_size,
                                       key.end());    //迭代到如果该键下一个关键:
    // - 不以preFIX启动
    // - 包含后preFIX非数字
    如果(!提振:: STARTS_WITH(键,preFIX)||
        !提振::所有(范围,提升:: is_digit()))
      继续;    //创建对,并插入到地图。
    p_options.insert(
      的std :: make_pair(
        提高:: lexical_cast的< INT>(升压:: copy_range<标准::字符串>(范围))
        pair.second.as&所述; INT>()));
  }
  返回p_options;
}INT主(INT交流,字符* AV [])
{
  命名空间PO =的boost :: program_options;
  PO :: options_description递减;
  desc.add_options()
    (P *,PO :​​:值< INT>())
    ;  PO :: variables_map VM;
  存储(PO :: command_line_parser(AC,AV)可供选项(DESC).RUN(),VM);  BOOST_FOREACH(常量p_options_type :: VALUE_TYPE&安培; P,get_p_options(VM,P))
  {
    性病::法院LT&;< 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的默认解析器行为,其中一个连字符,预计后跟一个字符。


自定义解析器

的另一种方法是添加一个<一个href=\"http://www.boost.org/doc/libs/1_48_0/doc/html/boost/program_options/basic_command_line_parser.html#id1101702-bb\"相对=nofollow>自定义分析器到 command_line_parser 。自定义解析器将允许 -p1 语法,以及其他常见形式,如 - P1 123 -p1 = 123 。有迹象表明,需要处理几行为:


  • 系统分析器将在同一时间收到一个令牌。因此,将获得 P1 123 个人调用。这是解析器责任配对 P1 123

  • Boost.ProgramOptions预计至少有一个解析器来处理令牌。否则 的boost :: program_options :: unknown_option 将被抛出。

要考虑这些行为,自定义解析器将管理国家和执行编码/解码:


  • 当解析器收到 P1 ,它提取 1 ,存储解析器状态。另外,EN codeS中的无操作的价值为 P

  • 当解析器收到 123 ,它连接codeS它旁边的存储状态值 P

因此​​,如果解析器收到 -p1 123 2的值插入 variables_map p :在无操作的价值和 1:123


{P:无操作
          1:123]}

此编码可以通过提供一个辅助函数的EN codeD P 矢量变换成一个图是对用户透明。解码的结果将是:

 {1:123} 

下面是例子code:

 的#include&LT;&iostream的GT;
#包括LT&;地图和GT;
#包括LT&;串GT;
#包括LT&;实用&GT; //的std ::对,性病:: make_pair
#包括LT&;矢量&GT;#包括LT&;提升/算法/ string.hpp&GT;
#包括LT&;升压/ foreach.hpp&GT;
#包括LT&;升压/ lexical_cast.hpp&GT;
#包括LT&;升压/ program_options.hpp&GT;的typedef的std ::地图&LT; INT,INT&GT; p_options_type;/// @brief分析器,它提供了解析-p ##选项的能力。
///
/// @note键和值在分别传递到解析器。
///因此,结构必须是有状态的。
类p_parser
{
上市:  明确的
  p_parser(常量标准::字符串&放大器; preFIX)
    :$ P $ _ PFIX(preFIX)
      hyphen_ $ P $ _ PFIX( - + preFIX)
  {}  性病::对&LT;的std ::字符串,性病::字符串&GT;运算符()(常量标准::字符串&安培;令牌)
  {
    //为了支持-p#=#语法,分裂令牌。
    的std ::矢量&lt;标准::字符串&GT;令牌(2);
    提高::分(令牌,令牌,提振:: is_any_of(=));    //如果分割产生了两个记号,​​那么键和值分别为
    //作为一个单一的令牌提供。
    如果(tokens.size()== 2)
      解析(tokens.front()); //解析键。    //解析剩余令牌。
    // - 如果tokens.size()== 2,则该标记是值。
    // - 否则,它是一个关键。
    返回解析(tokens.back());
  }  /// @brief德code单一连接codeD值。
  静态p_options_type :: VALUE_TYPE德code(常量标准::字符串&安培;连接codeD)
  {
    //德code。
    的std ::矢量&lt;标准::字符串&GT;德codeD(field_count_);
    提高::分(德codeD,带codeD,提振:: is_any_of(delimiter_));    //如果大小不等于所述场计数,然后编码失败。
    如果(field_count_!=去coded.size())
      扔的boost :: program_options :: invalid_option_value(EN codeD);    // 转变。
    返回的std :: make_pair(升压:: lexical_cast的&LT; INT&GT;(DE codeD [0]),
                          提高:: lexical_cast的&LT; INT&GT;(DE codeD [1]));
  }  /// @brief德code多恩codeD值。
  静态p_options_type德code(常量的std ::矢量&lt;标准::字符串&GT;&安培;连接coded_values​​)
  {
    p_options_type p_options;
    BOOST_FOREACH(常量标准::字符串&安培;连接codeD,带coded_values​​)
    {
      //如果值是一个空操作,然后继续下一个。
      如果(的boost ::等号(EN codeD,noop_))继续;
      p_options.insert(德code(EN codeD));
    }
    返回p_options;
  }私人的:  性病::对&LT;的std ::字符串,性病::字符串&GT;解析(常量标准::字符串&安培;令牌)
  {
    返回key_.empty()? parse_key(令牌)
                        :parse_value(标记);
  }  /// @brief解析选项的关键部分:P#
  性病::对&LT;的std ::字符串,性病::字符串&GT; parse_key(常量标准::字符串&放大器;键)
  {
    //搜索为preFIX以获得划分的关键成范围
    // 三个部分。鉴于--p12,该分区是:
    //
    //,--------- key.begin - ,pre-preFIX = -
    // /,-------- result.begin - :, preFIX =-p
    // / /,----- result.end - :,后preFIX =12
    // / / / - key.end -
    // | - | - | P | 1 | 2 |
    //
    提高:: iterator_range的&LT;的std ::字符串::为const_iterator&GT;结果=
      提高:: find_first(键,prefix_);    //不要处理如果密钥:
    // - 关键端是相同的结果结束。发生这种情况时,无论
    //或者钥匙找不到或存在超出键没有(或--a --P)
    // - 从开始到preFIX开始的距离大于2( - P)
    // - 非连字符之前preFIX存在(A - P)
    // - 非数值是结果之后。
    如果(result.end()== key.end()||
        距离(key.begin(),result.begin())&GT; 2 ||
        !提振::所有(
          升压:: make_iterator_range(key.begin(),result.begin()),
          升压:: is_any_of( - ))||
        !提振::所有(
          升压:: make_iterator_range(result.end(),key.end()),
          提高:: is_digit()))
    {
      //一个不同的解析器将处理此令牌。
      返回make_pair(标准::字符串(),标准::字符串());
    }    //否则,键包含预期的格式。
    key_.assign(result.end(),key.end());    //返回非空对,否则会Boost.ProgramOptions
    //消耗对待下一个值作为完整的价值。该
    //空操作条目将在解码过程中被剥离。
    返回make_pair(prefix_,noop_);
  }  选项​​/// @brief解析值部分:#
  性病::对&LT;的std ::字符串,性病::字符串&GT; parse_value(常量标准::字符串&安培;价值)
  {
    性病::对&LT;的std ::字符串,性病::字符串&GT; EN codeD =
      make_pair(prefix_,KEY_ + delimiter_ +值);
    key_.clear();
    返回连接codeD;
  }私人的:
  静态const int的field_count_ = 2;
  静态常量为std :: string delimiter_;
  静态常量为std :: string noop_;
私人的:
  常量标准::字符串prefix_;
  常量标准::字符串hyphen_ prefix_;
  标准::字符串KEY_;
};常量标准::字符串p_parser :: delimiter_ =;
常量标准::字符串p_parser :: noop_ =空操作;///从变图@brief提取物和去code选项。
p_options_type get_p_options(
  常量的boost :: program_options :: variables_map&安培; VM,
  常量标准::字符串preFIX)
{
  返回p_parser ::德code(VM [preFIX]。作为与LT;的std ::矢量&lt;标准::字符串&GT;&GT;());
}INT主(INT交流,字符* AV [])
{
  为const char * P_ preFIX =P;
  命名空间PO =的boost :: program_options;  //定义选项。
  PO :: options_description递减;
  desc.add_options()
    (P_ preFIX,PO :​​:值&LT;的std ::矢量&lt;标准::字符串&GT;&GT;() - GT;多令牌())
    ;  PO :: variables_map VM;
  存储(PO :: command_line_parser(AC,AV)可供选项(DESC)
          .extra_parser(p_parser(P_ preFIX))。运行()
       ,VM);  //提取-p选项。
  如果(vm.count(P_ preFIX))
  {
    //打印-p选项。
    BOOST_FOREACH(常量p_options_type :: VALUE_TYPE&安培; P,
                  get_p_options(VM,P_ preFIX))
    {
      性病::法院LT&;&LT; P和下;&下; p.first&LT;&LT; =&所述;&下; p.second&LT;&LT;的std :: ENDL;
    }
  }
}

和其用法:

 ./ a.out的-p1 123 --p2 234 -p3 = 345 = --p12 678
P1 = 123
P2 = 234
P3 = 345
P12 = 678

除了是一个较大的溶液,一个缺点是需要经过解码处理,以获得所需的值的要求。不能简单地遍历 VM的结果[P] 以有意义的方式。

Is it possible to use parameters of this kind with boost::program_options?

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

i.e., is it possible to specify the parameter name with a first token (e.g. -p) followed by a number, dynamically?
I would like to avoid this:

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

解决方案

Boost.ProgramOptions does not provide direct support for this. Nevertheless, there are two general solutions that each have their trade-offs:

  • Wildcard options.
  • Custom parser.

Wildcard Options

If it is an acceptable to use --p instead of -p, then a wildcard option can be used. This requires iterating through the variables_map during extraction, as Boost.ProgramOptions does not provide support receiving both the key and value in an overloaded validate() function.

#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;
  }
}

And its usage:

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

This approach requires iterating over the entire map to identify wildcard matches, resulting in a complexity of O(n). Additionally, it requires a modification to the desired syntax, where --p1 123 needs to be use instead of -p1 123. This limitation is the result Boost.ProgramOptions's default parser behavior, where a single hyphen is expected to be followed by a single character.


Custom Parser

The alternative approach is to add a custom parser to the command_line_parser. A custom parser will allow -p1 syntax, as well as other common forms, such as --p1 123 and -p1=123. There are a few behaviors that need to be handled:

  • A parser will receive a single token at a time. Thus, it will receive p1 and 123 on individual invocations. It is the parsers responsibility to pair p1 to 123.
  • Boost.ProgramOptions expects at least one parser to handle a token. Otherwise boost::program_options::unknown_option will be thrown.

To account for these behaviors, the custom parser will manage state and perform encoding/decoding:

  • When the parser receives p1, it extracts 1, storing state in the parser. Additionally, it encodes a no operation value for p.
  • When the parser receives 123, it encodes it alongside the stored state as value for p.

Thus, if the parser receives -p1 and 123, 2 values are inserted into the variables_map for p: the no operation value and 1:123.

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

This encoding can be transparent to the user by providing a helper function to transform the encoded p vector into a map. The result of decoding would be:

{ 1 : 123 }

Here is the example code:

#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;
    }
  }
}

And its usage:

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

Aside from the being a larger solution, one drawback is the requirement to go through the decoding process to obtain the desired values. One cannot simply iterate over the results of vm["p"] in a meaningful way.

这篇关于提高:: program_options:参数具有固定和可变令牌?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆