c++ boost::program_options: convert program_options/variables_map back to command line

319 Views Asked by At

I need to pass some specific arguments from one programm to another using boost::program_options and boost::process. Here is a simple example. In this example I need to pass all args stored in vm_slave to child process, but in common case I wanna pass one or more specific args from vm_slave.

#include <iostream>

#include <boost/process.hpp>
#include <boost/program_options.hpp>

using namespace std;
namespace po = boost::program_options;


int main(int argc, char* argv[]) {
    po::options_description tester_options("Tester options");
    po::options_description slave_options("Slave options");

    tester_options.add_options()
        ("help,h", "Show help")
        ("iter,i", po::value<short>()->default_value(1), "TODO")
        ("modules,m", po::value<std::vector<string>>()->multitoken(), "TODO");

    slave_options.add_options()
        ("size,s", po::value<size_t>()->required(), "TODO")
        ("threads,t", po::value<short>()->default_value(1), "TODO");

    po::variables_map vm;
    po::variables_map vm_slave;

    auto p0 = po::command_line_parser(argc, argv).options(tester_options).allow_unregistered().run();
    auto p1 = po::command_line_parser(argc, argv).options(slave_options).allow_unregistered().run();

    po::store(p0, vm);
    po::store(p1, vm);
    po::store(p1, vm_slave);

    // Do some stuff such as write help if needed
    // ...

    // I need call child process with all (or specific) args from vm_slave
    boost::process::ipstream pipe;
    boost::process::child cp(
        "slave" /* + vm_slave args */,
        boost::process::std_err > pipe,
        boost::process::std_out > pipe
    );

    cp.wait();

    return 0;
}

Of course I can do something like this:

ostringstream args;
for (const auto& arg : p1.options) {
    if (vm_slave.count(arg.string_key) == 0)
        continue;

    args << arg.string_key << " ";
    for (const auto& val : arg.value)
        args << val << " ";
}
string cmd_args = args.str();

But in this case some args stored in vm_slave by default is lost.

Or I can do this:

ostringstream args;
for (const auto& arg : vm_slave) {
    args << arg.first << " ";

    const auto& any_val = arg.second.value();
    if (boost::any_cast<size_t>(any_val))
        args << to_string(boost::any_cast<size_t>(any_val));
    else if (boost::any_cast<short>(any_val))
        args << to_string(boost::any_cast<size_t>(any_val));
    // And more and more casts...

    args << " ";
}

But now we have many any_cast...

If I just pass argv to child process, the child might fail because extra args presented (args like iter not intended for this application).

All this attempts seem bad to me.

What is the proper way to convert parsed arguments back to command line?

1

There are 1 best solutions below

0
On

There is no "proper way" - as composing command lines is not a feature of the library (neither is writing config-files).

I would use the parsed options. Unless you need to interpret the options you don't have to notify/store into a variable map at all:

std::vector<std::string> passthrough;
for (auto& opt : p1.options) {
    if (opt.unregistered || opt.string_key.empty())
        continue;

    assert(p1.description == &slave_options);

    auto& tok = opt.original_tokens;
    fmt::print("passing through {}: {}\n", opt.string_key, tok);
    passthrough.insert(passthrough.end(), tok.begin(), tok.end());
}

Demoing, using printf instead of slave:

if (vm.count("help")) {
    std::cout << tester_options << "\n\n" << slave_options << "\n";
} else {
    // demo using printf '- %q\n'
    passthrough.insert(passthrough.begin(), " - %q\n");
    bp::child cp("/usr/bin/printf", passthrough);

    cp.wait();
}

See it Compiler Explorer

  1. running ./sotest -i 1000 -m some modules to follow -h

    Tester options:
      -h [ --help ]          Show help
      -i [ --iter ] arg (=1) TODO
      -m [ --modules ] arg   TODO
    
    
    Slave options:
      -s [ --size ] arg         TODO
      -t [ --threads ] arg (=1) TODO
    
  2. running ./sotest -i 1000 -m some modules to follow -s=89 -t42

    passing through size: ["-s=89"]
    passing through threads: ["-t42"]
     - '-s=89'
     - -t42
    
  3. running ./sotest -i 1000 -m some modules to follow --size 89 --threads=42

    passing through size: ["--size", "89"]
    passing through threads: ["--threads=42"]
     - --size
     - 89
     - '--threads=42'
    
  4. running ./sotest -i 1000 -m some modules to follow -s 89 -t 42

    passing through size: ["-s", "89"]
    passing through threads: ["-t", "42"]
     - -s
     - 89
     - -t
     - 42