Use Boost Program Options to parse an arbitrary string
Asked Answered
M

2

8

I want to implement a command-line like interface inside my program. So I receive strings that follow the normal command-line syntax (e.g. "-G foo -dp bar --help"). As I don't want to implement the parser again, I would like to use Boost.

The question is: How can I pass a string to Boost program options instead of a combination of argCount and argValues. Do I need to first transform the text into a number (argCount) and a char* array (argValues) to do it? And if yes... is there an easy way to do this?

Thanks in advance.

Mcclung answered 22/8, 2013 at 11:11 Comment(3)
why would you pass a string? you get the options as char** already in your c++ program?Wares
I use UNIX sockets (asio::local) to pass around a std::string. Now I want to parse this string by using the program options. The problem is that the example only includes po::parse_command_line(ac, av, desc), but I do not have av. I have one complete string that contains the arguments.Mcclung
@Wares Because I'm writing a unit test.Knavery
O
10

One approach is to tokenize std::string into a std::vector<std::string>, then pass the result to Boost.ProgramOption's command_line_parser. The Boost.ProgramOption's documentation briefly covers this approach. Additionally, I use a similar approach in part of this answer.

Here is a minimal complete example:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>

#include <boost/bind.hpp>
#include <boost/program_options.hpp>
#include <boost/tokenizer.hpp>

// copy_if was left out of the C++03 standard, so mimic the C++11
// behavior to support all predicate types.  The alternative is to
// use remove_copy_if, but it only works for adaptable functors.
template <typename InputIterator,
          typename OutputIterator, 
          typename Predicate>
OutputIterator 
copy_if(InputIterator first,
        InputIterator last,
        OutputIterator result,
        Predicate pred)
{
  while(first != last)
  {
    if(pred(*first))
      *result++ = *first;
    ++first;
  }
  return result;
}

/// @brief Tokenize a string.  The tokens will be separated by each non-quoted
///        space or equal character.  Empty tokens are removed.
///
/// @param input The string to tokenize.
///
/// @return Vector of tokens.
std::vector<std::string> tokenize(const std::string& input)
{
  typedef boost::escaped_list_separator<char> separator_type;
  separator_type separator("\\",    // The escape characters.
                           "= ",    // The separator characters.
                           "\"\'"); // The quote characters.

  // Tokenize the intput.
  boost::tokenizer<separator_type> tokens(input, separator);

  // Copy non-empty tokens from the tokenizer into the result.
  std::vector<std::string> result;
  copy_if(tokens.begin(), tokens.end(), std::back_inserter(result), 
          !boost::bind(&std::string::empty, _1));
  return result;
}

int main()
{
  // Variables that will store parsed values.
  std::string address;
  unsigned int port;      

  // Setup options.
  namespace po = boost::program_options;
  po::options_description desc("Options");
  desc.add_options()
    ("address", po::value<std::string>(&address))
    ("port",    po::value<unsigned int>(&port))
    ;

  // Mock up input.
  std::string input = "--address 127.0.0.1 --port 12345";

  // Parse mocked up input.
  po::variables_map vm;
  po::store(po::command_line_parser(tokenize(input))
                .options(desc).run(), vm);
  po::notify(vm);

  // Output.
  std::cout << "address = " << address << "\n"
               "port = " << port << std::endl;
}

Which produces the following output:

address = 127.0.0.1
port = 12345
Official answered 22/8, 2013 at 16:6 Comment(2)
We don't need our own tokenize under linux, use boost split_unix.Tremble
The comment about split_unix should totally be an answer. The accepted answer is also a good one, but split_unix works unless you really need to customize the tokenization, and is already built. Works on windows too. I'll add it as an answer.Fontenot
F
0

boost::program_options has a function named split_unix as @FaceBro pointed out. It works on windows too, so the following is cross-platform, borrowing the accepted answer's main example structure:

int main()
{
  // Variables that will store parsed values.
  std::string address;
  unsigned int port;      

  // Setup options.
  namespace po = boost::program_options;
  po::options_description desc("Options");
  desc.add_options()
    ("address", po::value<std::string>(&address))
    ("port",    po::value<unsigned int>(&port))
    ;

  // Mock up input.
  std::string input = "--address 127.0.0.1 --port 12345";

  // Parse mocked up input.
  po::variables_map vm;
  po::store(po::command_line_parser(po::split_unix(input))
                .options(desc).run(), vm);
  po::notify(vm);

  // Output.
  std::cout << "address = " << address << "\n"
               "port = " << port << std::endl;
}
Fontenot answered 7/1, 2021 at 20:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.