Erforderliche und optionale Argumente mit Boost Library-Programmoptionen

83

Ich verwende die Boost-Programmoptionsbibliothek, um die Befehlszeilenargumente zu analysieren.

Ich habe folgende Anforderungen:

  1. Sobald "Hilfe" bereitgestellt wird, sind alle anderen Optionen optional.
  2. Sobald "Hilfe" nicht bereitgestellt wird, sind alle anderen Optionen erforderlich.

Wie kann ich damit umgehen? Hier ist der Code, der damit umgeht, und ich fand, dass er sehr redundant ist, und ich denke, es muss einfach sein, oder?

#include <boost/program_options.hpp>
#include <iostream>
#include <sstream>
namespace po = boost::program_options;

bool process_command_line(int argc, char** argv,
                          std::string& host,
                          std::string& port,
                          std::string& configDir)
{
    int iport;

    try
    {
        po::options_description desc("Program Usage", 1024, 512);
        desc.add_options()
          ("help",     "produce help message")
          ("host,h",   po::value<std::string>(&host),      "set the host server")
          ("port,p",   po::value<int>(&iport),             "set the server port")
          ("config,c", po::value<std::string>(&configDir), "set the config path")
        ;

        po::variables_map vm;
        po::store(po::parse_command_line(argc, argv, desc), vm);
        po::notify(vm);

        if (vm.count("help"))
        {
            std::cout << desc << "\n";
            return false;
        }

        // There must be an easy way to handle the relationship between the
        // option "help" and "host"-"port"-"config"
        if (vm.count("host"))
        {
            std::cout << "host:   " << vm["host"].as<std::string>() << "\n";
        }
        else
        {
            std::cout << "\"host\" is required!" << "\n";
            return false;
        }

        if (vm.count("port"))
        {
            std::cout << "port:   " << vm["port"].as<int>() << "\n";
        }
        else
        {
            std::cout << "\"port\" is required!" << "\n";
            return false;
        }

        if (vm.count("config"))
        {
            std::cout << "config: " << vm["config"].as<std::string>() << "\n";
        }
        else
        {
            std::cout << "\"config\" is required!" << "\n";
            return false;
        }
    }
    catch(std::exception& e)
    {
        std::cerr << "Error: " << e.what() << "\n";
        return false;
    }
    catch(...)
    {
        std::cerr << "Unknown error!" << "\n";
        return false;
    }

    std::stringstream ss;
    ss << iport;
    port = ss.str();

    return true;
}

int main(int argc, char** argv)
{
  std::string host;
  std::string port;
  std::string configDir;

  bool result = process_command_line(argc, argv, host, port, configDir);
  if (!result)
      return 1;

  // Do the main routine here
}
Peter Lee
quelle

Antworten:

103

Ich bin selbst auf dieses Problem gestoßen. Der Schlüssel zu einer Lösung ist , dass die Funktion po::storefüllt die variables_mapwährend po::notifywirft tretene Fehler, so vmkann vor der Übermittlung an irgendwelche Mitteilungen verwendet werden.

Stellen Sie gemäß Tim jede Option wie erforderlich auf "Erforderlich" ein, führen po::notify(vm) Sie sie jedoch aus, nachdem Sie sich mit der Hilfeoption befasst haben. Auf diese Weise wird es ohne Ausnahmen beendet. Wenn die Optionen auf "Erforderlich" gesetzt sind, wird durch eine fehlende Option eine required_optionAusnahme ausgelöst, und mit ihrer get_option_nameMethode können Sie Ihren Fehlercode auf einen relativ einfachen catchBlock reduzieren .

Als zusätzliche Anmerkung werden Ihre Optionsvariablen direkt über den po::value< -type- >( &var_name )Mechanismus festgelegt, sodass Sie nicht über sie zugreifen müssen vm["opt_name"].as< -type- >().

Ein Codebeispiel finden Sie in Peters Antwort

rcollyer
quelle
Danke für deine Antwort. Ich denke es funktioniert wie erwartet. Ich habe auch das komplette Programm unten für die Leute gepostet, die ein gutes Beispiel brauchen.
Peter Lee
5
Hervorragende Lösung! Die offizielle Dokumentation sollte dies anhand eines Beispiels verdeutlichen.
Russoue
@rcollyer Könnten Sie bitte ein voll funktionsfähiges Beispiel liefern?
Jonas Stein
@ JonasStein Ich könnte, aber Peter scheint in Ordnung zu sein. Lassen Sie mich wissen, wenn das nicht ausreicht.
Collyer
1
@rcollyer Die sx-Website verbindet die beiden Antworten nicht visuell, daher habe ich das verpasst. Ich habe eine Notiz hinzugefügt. Bitte kehren Sie zurück, wenn Sie damit nicht vertraut sind.
Jonas Stein
46

Hier ist das komplette Programm von rcollyer und Tim, an die die Credits gehen:

#include <boost/program_options.hpp>
#include <iostream>
#include <sstream>
namespace po = boost::program_options;

bool process_command_line(int argc, char** argv,
                          std::string& host,
                          std::string& port,
                          std::string& configDir)
{
    int iport;

    try
    {
        po::options_description desc("Program Usage", 1024, 512);
        desc.add_options()
          ("help",     "produce help message")
          ("host,h",   po::value<std::string>(&host)->required(),      "set the host server")
          ("port,p",   po::value<int>(&iport)->required(),             "set the server port")
          ("config,c", po::value<std::string>(&configDir)->required(), "set the config path")
        ;

        po::variables_map vm;
        po::store(po::parse_command_line(argc, argv, desc), vm);

        if (vm.count("help"))
        {
            std::cout << desc << "\n";
            return false;
        }

        // There must be an easy way to handle the relationship between the
        // option "help" and "host"-"port"-"config"
        // Yes, the magic is putting the po::notify after "help" option check
        po::notify(vm);
    }
    catch(std::exception& e)
    {
        std::cerr << "Error: " << e.what() << "\n";
        return false;
    }
    catch(...)
    {
        std::cerr << "Unknown error!" << "\n";
        return false;
    }

    std::stringstream ss;
    ss << iport;
    port = ss.str();

    return true;
}

int main(int argc, char** argv)
{
  std::string host;
  std::string port;
  std::string configDir;

  bool result = process_command_line(argc, argv, host, port, configDir);
  if (!result)
      return 1;

  // else
  std::cout << "host:\t"   << host      << "\n";
  std::cout << "port:\t"   << port      << "\n";
  std::cout << "config:\t" << configDir << "\n";

  // Do the main routine here
}

/* Sample output:

C:\Debug>boost.exe --help
Program Usage:
  --help                produce help message
  -h [ --host ] arg     set the host server
  -p [ --port ] arg     set the server port
  -c [ --config ] arg   set the config path


C:\Debug>boost.exe
Error: missing required option config

C:\Debug>boost.exe --host localhost
Error: missing required option config

C:\Debug>boost.exe --config .
Error: missing required option host

C:\Debug>boost.exe --config . --help
Program Usage:
  --help                produce help message
  -h [ --host ] arg     set the host server
  -p [ --port ] arg     set the server port
  -c [ --config ] arg   set the config path


C:\Debug>boost.exe --host 127.0.0.1 --port 31528 --config .
host:   127.0.0.1
port:   31528
config: .

C:\Debug>boost.exe -h 127.0.0.1 -p 31528 -c .
host:   127.0.0.1
port:   31528
config: .
*/
Peter Lee
quelle
3
Sie sollten fangen, boost::program_options::required_optiondamit Sie das Fehlen einer erforderlichen Option direkt behandeln können, anstatt sie von fangen zu lassen std::exception.
Rcollyer
Der Port sollte vom Typ ohne Vorzeichen sein.
g33kz0r
2
Sie sollten boost :: program_options :: error nur dies abfangen.
CreativeMind
13

Sie können angeben, dass eine Option leicht genug erforderlich ist [ 1 ], z.

..., value<string>()->required(), ...

Soweit ich weiß, gibt es jedoch keine Möglichkeit, Beziehungen zwischen verschiedenen Optionen in der Bibliothek program_options darzustellen.

Eine Möglichkeit besteht darin, die Befehlszeile mehrmals mit verschiedenen Optionssätzen zu analysieren. Wenn Sie bereits nach "Hilfe" gesucht haben, können Sie die drei anderen Optionen nach Bedarf erneut analysieren. Ich bin mir nicht sicher, ob ich das für eine Verbesserung gegenüber dem halten würde, was Sie haben.

Tim Sylvester
quelle
2
Ja, Ihr Recht, das ich setzen könnte ->required(), aber dann kann der Benutzer die Hilfeinformationen nicht abrufen --help(ohne alle anderen erforderlichen Optionen anzugeben), da andere Optionen erforderlich sind.
Peter Lee
@Peter Du würdest nur beim ersten Mal nach Hilfe suchen , die anderen Optionen wären nicht einmal in der Liste. Wenn sie dann die Hilfeoption nicht übergeben haben, würden Sie die Analyse nur dann erneut ausführen, diesmal die anderen drei Optionen übergeben, auf erforderlich gesetzt und nicht helfen. Dieser Ansatz würde wahrscheinlich einen dritten Satz von Optionen erfordern, die alle kombiniert sind, um die Nutzungsinformationen zu drucken. Ich bin mir ziemlich sicher, dass es funktionieren würde, aber der Ansatz von rcollyer ist sauberer.
Tim Sylvester
1
    std::string conn_mngr_id;
    std::string conn_mngr_channel;
    int32_t priority;
    int32_t timeout;

    boost::program_options::options_description p_opts_desc("Program options");
    boost::program_options::variables_map p_opts_vm;

    try {

        p_opts_desc.add_options()
            ("help,h", "produce help message")
            ("id,i", boost::program_options::value<std::string>(&conn_mngr_id)->required(), "Id used to connect to ConnectionManager")
            ("channel,c", boost::program_options::value<std::string>(&conn_mngr_channel)->required(), "Channel to attach with ConnectionManager")
            ("priority,p", boost::program_options::value<int>(&priority)->default_value(1), "Channel to attach with ConnectionManager")
            ("timeout,t", boost::program_options::value<int>(&timeout)->default_value(15000), "Channel to attach with ConnectionManager")
        ;

        boost::program_options::store(boost::program_options::parse_command_line(argc, argv, p_opts_desc), p_opts_vm);

        boost::program_options::notify(p_opts_vm);

        if (p_opts_vm.count("help")) {
            std::cout << p_opts_desc << std::endl;
            return 1;
        }

    } catch (const boost::program_options::required_option & e) {
        if (p_opts_vm.count("help")) {
            std::cout << p_opts_desc << std::endl;
            return 1;
        } else {
            throw e;
        }
    }
Edgard Lima
quelle
Das ist sicherlich eine interessante Alternative. Aber es zwingt Sie, die Hilfe beim Umgang mit Code zu wiederholen, und obwohl es klein ist, würde ich es eher vermeiden.
Collyer