2011-03-22 28 views
66

Estoy usando Boost Program Options Library para analizar los argumentos de la línea de comando.Argumentos necesarios y opcionales usando Boost Library Opciones de programa

que tiene los siguientes requisitos:

  1. Once "ayuda" se provea, todas las otras opciones son opcionales;
  2. Una vez que no se proporciona "ayuda", se requieren todas las demás opciones.

¿Cómo puedo hacer frente a esto? Aquí está el código de mi manejo de esto, y encontré que es muy redundante, y creo que debe ser fácil de hacer, ¿verdad?

#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 
} 

Respuesta

84

Me he encontrado con este problema. La clave para una solución es que la función po::store rellena el variables_map, mientras que po::notify genera cualquier error encontrado, por lo que vm se puede utilizar antes de que se envíen notificaciones.

Por lo tanto, según Tim, establezca cada opción como requerida, según desee, pero ejecute po::notify(vm) después de que haya tratado con la opción de ayuda. De esta forma saldrá sin excepciones. Ahora, con las opciones establecidas en obligatorio, una opción faltante provocará una excepción de required_option y utilizando su método get_option_name puede reducir su código de error a un bloque relativamente simple catch.

Como nota adicional, sus variables de opción se configuran directamente a través del mecanismo po::value< -type- >(&var_name), por lo que no tiene que acceder a ellas a través del vm["opt_name"].as< -type- >().

+0

gracias por su respuesta. Creo que funciona como se esperaba También publiqué el programa completo a continuación para las personas que necesitan un buen ejemplo. –

+0

@Peter, me alegro de escucharlo. – rcollyer

+0

Buena idea, eso es inteligente. –

10

puede especificar que una opción es requerido con bastante facilidad [1], por ejemplo ,:

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

pero por lo que sé que no hay manera de representar las relaciones entre las diferentes opciones a la biblioteca program_options .

Una posibilidad es analizar la línea de comando varias veces con diferentes conjuntos de opciones; luego, si ya ha marcado "ayuda", puede analizar de nuevo con las otras tres opciones, todo configurado como se requiere. Sin embargo, no estoy seguro de que considere una mejora sobre lo que tienes.

+0

sí, ur derecho que pudiera poner '-> requerido()', pero entonces el usuario no puede obtener la información de ayuda mediante '--help' (sin proporcionar todas las otras opciones requeridas), porque se requieren otras opciones. –

+0

@Peter Buscaría * solo * ayuda la primera vez, las otras opciones ni siquiera estarían en la lista. Luego, si no pasaron la opción de ayuda, solo entonces ejecutaría el análisis sintáctico de nuevo, esta vez pasando las otras tres opciones, configuradas como requeridas y * not * help. Este enfoque probablemente requeriría un tercer conjunto de opciones, con todas ellas combinadas, para usar para imprimir la información de uso. Estoy bastante seguro de que funcionaría, pero el enfoque de rcollyer es más limpio. –

34

Aquí es el programa completo conforme a las rcollyer y Tim, a quien los créditos van a:

#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:\Documents and Settings\plee\My Documents\Visual Studio 2010\Projects\VCLearning\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:\Documents and Settings\plee\My Documents\Visual Studio 2010\Projects\VCLearning\Debug>boost.exe 
Error: missing required option config 

C:\Documents and Settings\plee\My Documents\Visual Studio 2010\Projects\VCLearning\Debug>boost.exe --host localhost 
Error: missing required option config 

C:\Documents and Settings\plee\My Documents\Visual Studio 2010\Projects\VCLearning\Debug>boost.exe --config . 
Error: missing required option host 

C:\Documents and Settings\plee\My Documents\Visual Studio 2010\Projects\VCLearning\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:\Documents and Settings\plee\My Documents\Visual Studio 2010\Projects\VCLearning\Debug>boost.exe --host 127.0.0.1 --port 31528 --config . 
host: 127.0.0.1 
port: 31528 
config: . 

C:\Documents and Settings\plee\My Documents\Visual Studio 2010\Projects\VCLearning\Debug>boost.exe -h 127.0.0.1 -p 31528 -c . 
host: 127.0.0.1 
port: 31528 
config: . 
*/ 
+3

Debería capturar 'boost :: program_options :: required_option' para que pueda manejar la falta de una opción requerida directamente en lugar de que sea atrapada por' std :: exception'. – rcollyer

+0

El puerto debe ser de tipo sin signo. – g33kz0r

+2

Deberías coger boost :: program_options :: error esto solo. – CreativeMind

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; 
     } 
    } 
+0

Esa es ciertamente una alternativa interesante. Pero, te obliga a repetir el código de manejo de ayuda, y aunque pequeño, tendería a evitarlo. – rcollyer

+0

uh no. No es así como usas excepciones. –

Cuestiones relacionadas