Haciendo programas con opciones de consola con C++ y Boost

Cuando uno ya lleva unos años programando y se pone a ver sus códigos viejos generalmente piensa “joder, qué ***** más fea”. Mientras vamos adquiriendo más experiencia y vamos participando en proyectos más complejos, se nos va haciendo necesario el programar “como se debe”. Una de las cosas importantes al programar bien es hacer programas que sean fácil de ejecutar y que puedan configurarse mediante varias opciones sin tener que compilarse de nuevo. La idea es conseguir algo así:

programa --opción1 valor1 --opción2 valor2

Para eso, cuando trabajamos en C o C++ tenemos las variables argc y argv que contienen la cantidad de argumentos de consola y los argumentos mismos respectivamente. Con eso podríamos programar una recepción de argumentos usando esas dos variables. Pero, con tantos años que llevamos programando… ¿no se le habrá ocurrido a alguien una forma estándar de hacer eso mismo para no andar reinventando la rueda?

Boost

Boost es un conjunto de bibliotecas para C++ que abordan una cantidad inmensa de áreas, desde álgebra lineal hasta expresiones regulares, pasando por procesamiento de imágenes y multithreading [1][2]. No voy a entrar en detalles de todo lo que puede hacer Boost, ya que no lo sé y estaría vacuneando [3] si les inventara. Me voy a enfocar en este asunto particular de hacer un programa con opciones usando un módulo de Boost.

Primero hay que instalar Boost. Para eso, lo más sencillo es instalarlo como un paquete de consola. En Fedora, es llegar y correr lo siguiente:

sudo yum install boost-devel

De Boost nos interesa usar el módulo Program Options, que nos deja, valga la redun-dun-dancia, añadirle opciones a nuestros programas.

A codear

Para incluir el módulo en nuestro programa de C++, escribimos:

#include <boost/program_options.hpp>

Y declaramos nuestro espacio de nombres en una variable, para que sea más fácil de localizar:

namespace po = boost::program_options;

Ya dentro del código, generamos un objeto de tipo options_description, que guardará la información de nuestras opciones, los valores que reciben y sus descripciones.

po::options_description desc("Nuestras fabulosas opciones de programa");

Luego, con add_options vamos registrando las opciones que se van a ejecutar. Cada nombre de la opción luego será llamable en la consola de la forma –nombre. Para este ejemplo usaremos una instrucción –help que mostrará los comandos, otra instrucción –name que recibirá un nombre como string y una instrucción –age que recibirá un entero, para ejemplificar los tipos de datos.

desc.add_options()
    ("help", "Imprime este mensaje de ayuda.")
    ("name", po::value<string>(), "Un nombre como string.")
    ("age", po::value<int>(), "Una edad como entero.");

Cuando leamos las instrucciones de consola, se generará un objeto variables_map que consistirá en pares clave–>valor. Con store guardamos lo que se lee desde consola mediante parse_command_line usando la descripción en el mapa de variables. En caso de que tengamos una función que se desencadene ante la presencia de una cierta opción, usamos notify para ejecutarla.

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

Para verificar si una opción fue ingresada (y posiblemente, acceder a su valor si es que tiene) usamos la instrucción count con el nombre que le asignamos a la opción. Por ejemplo, para ver si se ingresó la opción –help y mostrar el mensaje de descripción de las opciones, usamos lo siguiente:

if vm.count("help")
    cout << desc << endl;

Si recibimos algún valor junto a las opciones, llamamos al mapa de variables mediante el nombre de la opción y especificamos el tipo de variable que esperamos recibir. Por ejemplo, para recibir el nombre sería algo como:

cout << vm["name"].as<string>() << endl;

Ahora, para juntar todo, aquí hay un código que recibe la instrucción –help para mostrar la lista de opciones, o las opciones –name o –age para leer una string o un int respectivamente:

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

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

int main( int argc, char** argv )	{
  po::options_description desc("Nuestras fabulosas opciones de programa");

  desc.add_options()
    ("help", "Imprime este mensaje de ayuda.")
    ("name", po::value<string>(), "Un nombre como string.")
    ("age", po::value<int>(), "Una edad como entero.");

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

  if(vm.count("help"))
    cout << desc << endl;
  else{
    if(vm.count("name")){
      cout << "Nombre ingresado: " << vm["name"].as<string>() << endl;
    }
    if(vm.count("age")){
      cout << "Edad ingresada: " << vm["age"].as<int>() << endl;
    }
  }
}

Compilé el programa como “test” y al correrlo con distintas opciones obtuve los siguientes resultados:

[bruno@marelle build]$ ./test --help
Nuestras fabulosas opciones de programa:
  --help                Imprime este mensaje de ayuda.
  --name arg            Un nombre como string.
  --age arg             Una edad como entero.

[bruno@marelle build]$ ./test --name Brunaldo
Nombre ingresado: Brunaldo
[bruno@marelle build]$ ./test --name Brunaldo --age 23
Nombre ingresado: Brunaldo
Edad ingresada: 23
[bruno@marelle build]$ ./test --age 23
Edad ingresada: 23

Este es un código bastante simple. Si quieren agregar más detalles, como bloques try/catch, pueden ver el tutorial oficial en [4]. (Eso suena coqueto)

Para compilar

Para compilar el código anterior, hay que linkear el módulo que usamos de Boost. Para eso usan la opción -lboost_program_options con su compilador regalón.

c++ ../src/test.cpp -lboost_program_options

Si les gusta compilar con CMake, pueden agregar lo siguiente a su archivo CMakeLists.txt:

include_directories(${Boost_INCLUDE_DIR})
find_package(
    Boost 1.40
    COMPONENTS
	program_options
    REQUIRED)
add_executable(
    test
    src/test.cpp)
target_link_libraries(
    test
    ${Boost_LIBRARIES})

En vez de ${Boost_LIBRARIES} pueden usar ${Boost_PROGRAM_OPTIONS_LIBRARY} para linkear este módulo en específico, pero así se aseguran de poder linkear cualquier otro módulo de Boost que le vayan a agregar a su programa.

Hasta aquí llega el tutorial. Ahora, a programar, carajo.

Referencias

  1. Boost en Wikipedia: https://en.wikipedia.org/wiki/Boost_%28C%2B%2B_libraries%29
  2. Sitio de Boost: http://www.boost.org/
  3. Vacuna: http://www.mainframe.cl/diccionario/diccionario.php?palabra=vacuna&accion=Buscar
  4. Tutorial oficial de boost::program_options: http://theboostcpplibraries.com/boost.program_options
Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s