Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add a description to boost::program_options' positional options?

I would like to make a positional, list program option with boost_program_options that do not allow named program options (like --files).

I have the following snippet of code:

#include <boost/program_options.hpp>
#include <iostream>
#include <string>
#include <vector>

namespace po = boost::program_options;

int main(int argc, const char* argv[]) {
  po::options_description desc("Allowed options");
  desc.add_options()("help", "produce help message")
  ( "files", po::value<std::vector<std::string>>()->required(), "list of files");

  po::positional_options_description pos;
  pos.add("files", -1);

  po::variables_map vm;
  try {
    po::store(po::command_line_parser(argc, argv).options(desc).positional(pos).run(), vm);
    po::notify(vm);
  } catch(const po::error& e) {
    std::cerr << "Couldn't parse command line arguments properly:\n";
    std::cerr << e.what() << '\n' << '\n';
    std::cerr << desc << '\n';
    return 1;
  }

  if(vm.count("help") || !vm.count("files")) {
    std::cout << desc << "\n";
    return 1;
  }
}

The problem is that I can read files list as positional arguments lists as follows:

./a.out file1 file2 file3

but unfortunately like this as well ( which I would like to disable )

./a.out --files file1 file2 file3

The problem is also with the help which yields:

./a.out
Couldn't parse command line arguments properly:
the option '--files' is required but missing

Allowed options:
  --help                produce help message
  --files arg           list of files

So my desired scenario would be more like (os similar):

./a.out
Couldn't parse command line arguments properly:
[FILES ...] is required but missing

Allowed options:
  --help                produce help message
  --optionx             some random option used in future
  [FILE ...]            list of files

After I remove files options from desc.add_option()(...) it stop working so I believe I need it there.

like image 599
Patryk Avatar asked Oct 08 '16 12:10

Patryk


1 Answers

As to the question posed in the title, "How to add a description to boost::program_options' positional options?", there's no functionality provided for this in the library. You need to handle that part yourself.

As for the body of the question... it's possible, but in a slightly round-about way.

The positional options map each position to a name, and the names need to exist. From what I can tell in the code (cmdline.cpp), the unregistered flag won't be set for arguments that are positional. [1], [2]

So, to do what you want, we can do the following:

  • Hide the --files option from showing up in the help. You will need to display appropriate help for the positional options yourself, but this is no different than before.
  • Add our own validation between parsing and storing of the parsed options to the variables_map.

Hiding --files from help

Here we take advantage of the the fact that we can create composite options_description using the add(...) member function:

po::options_description desc_1;
// ...
po::options_description desc_2;
// ...
po::options_description desc_composite;
desc_composite.add(desc_1).add(desc_2);

We can therefore place our files option into a hidden options_description, and create a composite that we will use only for the parsing stage. (see code below)

Preventing explicit --files

We need to intercept the list of options between parsing and storing them into the variables_map.

The run() method of command_line_parser returns an instance of basic_parsed_options, whose member options holds a vector of basic_options. There is an element for each parsed argument, and any positional options are enumerated starting from 0, any non-positional options have position -1. We can use this to perform our own validation and raise an error when we see --files as an explicit (non-positional) argument.

Example Source Code

See on Coliru

#include <boost/program_options.hpp>
#include <iostream>
#include <string>
#include <vector>

namespace po = boost::program_options;

int main(int argc, const char* argv[])
{
    std::vector<std::string> file_names;

    po::options_description desc("Allowed options");
    desc.add_options()
        ("help", "produce help message")
        ("test", "test option");

    std::string const FILES_KEY("files");

    // Hide the `files` options in a separate description
    po::options_description desc_hidden("Hidden options");
    desc_hidden.add_options()
        (FILES_KEY.c_str(), po::value(&file_names)->required(), "list of files");

    // This description is used for parsing and validation
    po::options_description cmdline_options;
    cmdline_options.add(desc).add(desc_hidden);

    // And this one to display help
    po::options_description visible_options;
    visible_options.add(desc);

    po::positional_options_description pos;
    pos.add(FILES_KEY.c_str(), -1);

    po::variables_map vm;
    try {
        // Only parse the options, so we can catch the explicit `--files`
        auto parsed = po::command_line_parser(argc, argv)
            .options(cmdline_options)
            .positional(pos)
            .run();

        // Make sure there were no non-positional `files` options
        for (auto const& opt : parsed.options) {
            if ((opt.position_key == -1) && (opt.string_key == FILES_KEY)) {
                throw po::unknown_option(FILES_KEY);
            }
        }

        po::store(parsed, vm);
        po::notify(vm);
    } catch(const po::error& e) {
        std::cerr << "Couldn't parse command line arguments properly:\n";
        std::cerr << e.what() << '\n' << '\n';
        std::cerr << visible_options << '\n';
        return 1;
    }

    if (vm.count("help") || !vm.count("files")) {
        std::cout << desc << "\n";
        return 1;
    }

    if (!file_names.empty()) {
        std::cout << "Files: \n";
        for (auto const& file_name : file_names) {
            std::cout << " * " << file_name << "\n";
        }
    }
}

Test Output

Valid options:

>example a b c --test d e
Files:
 * a
 * b
 * c
 * d
 * e

Invalid options:

>example a b c --files d e
Couldn't parse command line arguments properly:
unrecognised option 'files'


Allowed options:
  --help                 produce help message
  --test                 test option
like image 173
Dan Mašek Avatar answered Nov 06 '22 14:11

Dan Mašek