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

8

Решение

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
р1 = 123
р2 = 234
р3 = 345
р12 = 678

Этот подход требует итерации по всей карте для определения совпадений с подстановочными знаками, что приводит к сложности O(n), Кроме того, требуется модификация желаемого синтаксиса, где --p1 123 нужно использовать вместо -p1 123, Это ограничение является результатом работы синтаксического анализатора Boost.ProgramOptions по умолчанию, в котором после одного дефиса должен следовать один символ.


Пользовательский Парсер

Альтернативный подход заключается в добавлении пользовательский парсер к command_line_parser, Пользовательский парсер позволит -p1 синтаксис, а также другие распространенные формы, такие как --p1 123 а также -p1=123, Есть несколько вариантов поведения, которые необходимо обработать:

  • Парсер получит один токен за раз. Таким образом, он получит p1 а также 123 на отдельных вызовах. Это парсер ответственность за пару p1 в 123,
  • Boost.ProgramOptions ожидает, что по крайней мере один парсер будет обрабатывать токен. Иначе boost::program_options::unknown_option будет брошен.

Чтобы учесть это поведение, пользовательский анализатор будет управлять состоянием и выполнять кодирование / декодирование:

  • Когда парсер получает p1извлекает 1сохранение состояния в парсере. Кроме того, он кодирует нет операции значение для p,
  • Когда парсер получает 123, он кодирует его вместе с сохраненным состоянием в качестве значения для p,

Таким образом, если парсер получает -p1 а также 1232 значения вставляются в variables_map за p: нет операции значение и 1:123,

{ "п" : [ "нет операции»,
"1: 123"]}

Это кодирование может быть прозрачным для пользователя, предоставляя вспомогательную функцию для преобразования закодированного p вектор в карту. Результатом декодирования будет:

{1: 123}

Вот пример кода:

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

И его использование:

./a.out -p1 123 --p2 234 -p3 = 345 --p12 = 678
р1 = 123
р2 = 234
р3 = 345
р12 = 678

Помимо того, что решение является более крупным, одним недостатком является требование пройти процесс декодирования для получения желаемых значений. Нельзя просто перебрать результаты vm["p"] значимым образом.

11

Другие решения

Других решений пока нет …

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector