Wymagane i opcjonalne argumenty przy użyciu opcji programu Boost Library

83

Używam biblioteki opcji programu doładowania, aby przeanalizować argumenty wiersza polecenia.

Mam następujące wymagania:

  1. Po udostępnieniu „pomocy” wszystkie inne opcje są opcjonalne;
  2. Jeśli „pomoc” nie jest dostępna, wymagane są wszystkie inne opcje.

Jak sobie z tym radzę? Oto mój kod, który to obsługuje i stwierdziłem, że jest on bardzo zbędny i myślę, że musi być to łatwe do zrobienia, prawda?

#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
źródło

Odpowiedzi:

103

Sam napotkałem ten problem. Kluczem do rozwiązania jest to, że funkcja po::storezapełnia variables_mappodczas gdy po::notifywywołuje wszelkie napotkane błędy, więc vmmożna jej użyć przed wysłaniem jakichkolwiek powiadomień.

Tak więc, zgodnie z Timem , ustaw każdą opcję na wymaganą, zgodnie z wymaganiami, ale uruchom po::notify(vm) po rozwiązaniu opcji pomocy. W ten sposób wyjdzie bez żadnych wyjątków. Teraz, gdy opcje są ustawione na wymagane, brakująca opcja spowoduje required_optionwyrzucenie wyjątku i używając jej get_option_namemetody, możesz zredukować kod błędu do stosunkowo prostegocatch bloku.

Jako dodatkowa uwaga, twoje zmienne opcji są ustawiane bezpośrednio przez po::value< -type- >( &var_name )mechanizm, więc nie musisz mieć do nich dostępu vm["opt_name"].as< -type- >().

Przykład kodu znajduje się w odpowiedzi Petersa

rcollyer
źródło
dzięki za odpowiedź. Myślę, że działa zgodnie z oczekiwaniami. Poniżej zamieściłem również pełny program dla osób, które potrzebują dobrego przykładu.
Peter Lee
5
Doskonałe rozwiązanie! Oficjalna dokumentacja powinna wyjaśniać to na przykładzie.
russoue
@rcollyer czy możesz podać pełny przykład roboczy?
Jonas Stein
@JonasStein Mógłbym, ale Peter's wydaje się być w porządku. Daj mi znać, jeśli to nie wystarczy.
rcollyer
1
@rcollyer Strona internetowa sx nie łączy wizualnie dwóch odpowiedzi, więc to przegapiłem. Dodałem notatkę. Cofnij się, jeśli nie czujesz się z tym dobrze.
Jonas Stein
46

Oto kompletny program według rcollyera i Tima, do których należą napisy:

#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
źródło
3
Powinieneś złapać boost::program_options::required_option, abyś mógł bezpośrednio obsłużyć brak wymaganej opcji, zamiast dać się złapać std::exception.
rcollyer
Port powinien być typu unsigned.
g33kz0r
2
Powinieneś złapać boost :: program_options :: error tylko to.
CreativeMind
13

Możesz określić, że opcja jest wymagana dość łatwo [ 1 ], np .:

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

ale o ile wiem, nie ma sposobu na przedstawienie relacji między różnymi opcjami w bibliotece program_options.

Jedną z możliwości jest wielokrotne parsowanie wiersza poleceń z różnymi zestawami opcji, a następnie, jeśli sprawdziłeś już „pomoc”, możesz ponownie przeanalizować trzy inne opcje, wszystkie ustawione zgodnie z wymaganiami. Nie jestem jednak pewien, czy uważam to za poprawę w stosunku do tego, co masz.

Tim Sylvester
źródło
2
tak, masz rację, którą mógłbym umieścić ->required(), ale wtedy użytkownik nie może uzyskać informacji pomocy --help(bez podania wszystkich innych wymaganych opcji), ponieważ wymagane są inne opcje.
Peter Lee
@Peter Za pierwszym razem szukałbyś tylko pomocy, innych opcji nie byłoby nawet na liście. Następnie, jeśli nie przejdą do opcji pomocy, dopiero wtedy uruchomisz analizę ponownie, tym razem przekazując pozostałe trzy opcje, ustawioną na wymagane, a nie na pomoc. Podejście to prawdopodobnie wymagałoby trzeciego zestawu opcji, ze wszystkimi połączonymi, w celu wydrukowania informacji o użytkowaniu. Jestem prawie pewien, że to zadziała, ale podejście rcollyera jest czystsze.
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
źródło
To z pewnością interesująca alternatywa. Ale zmusza cię to do powtórzenia pomocy w obsłudze kodu i chociaż jest mały, starałbym się go unikać.
rcollyer