I have been spending time recently writing command line apps in
C++11. Each time I wanted a way of handling command line arguments
flexibly. I chose to use the boost::program_options library. The
documentation is pretty good but there are some assumptions (aliased
namespace) and the example code is broken up with paragraphs of text
explaining what the code does.
When I start to explore an API to a library I start by writing
tests. After working with the program_options library on a few small
applications the same pattern came up each time so I thought I would
share the results as a complete app, although admittedly the app does
nothing other than accept command line parameters.
The application is also linked up to a continuous integration server
that triggers a build and test on each commit.
To keep the dependencies as low as possible the tests are written
using the boost unit testing framework.
The tests are somewhat artificial in that they test the command line
options results in an application state and not an observable
behavior but are perfectly valid test cases.
Basic use case
app -v --version -username [value] file file
The basic use case offers two variations of switch (-s –switch) a
numeric argument option and a list of files.
== Test: No parameter behavior
So the first test verifies that we can handle (detect) that no command
line parameters were supplied.
#defineBOOST_TEST_MAIN#ifndef MAKEFILE_BUILD
// Work around for xcode's default to always link against dynamic libraries
// and the need to static linking for distribution.
#defineBOOST_TEST_DYN_LINK#endif#defineBOOST_TEST_MODULE DupsTests#include<boost/test/unit_test.hpp>
Application bootstrap
Bootstrapping the app. Main methods need to be as small as possible
and responsible for wiring up the application and passing command line
parameters into the application.
For anything more than this trivial application the application class
would contain the main application algorithm or defer processing to a
collection of other classes.
#pragma once
#include"command_line_argument_parser.hpp"classapplication{public:intrun(intargc,constchar*argv[]){
command_line_argument_parser parser;
parser.parse(argc, argv);return0;}};
The command line processing classes
Class responsible for capturing runtime arguments.
#pragma once
#include<boost/program_options.hpp>#include<vector>usingnamespace std;classarguments{constexprstaticauto username_option_name ="username";constexprstaticauto username_option ="username,u";constexprstaticauto verbose_option_name ="verbose";constexprstaticauto verbose_option ="verbose,v";constexprstaticauto files_option_name ="input-files";
boost::program_options::variables_map variables;friendclass command_line_argument_parser;public:arguments(boost::program_options::variables_map variables):variables(variables){}boolno_arguments(){return variables.size()==0;}boolverbose(){return variables.count(verbose_option_name)>0;}
string username(){return(variables.count(username_option_name)>0)? variables[username_option_name].as<string>():"";}const vector<string>&filenames(){return variables[files_option_name].as<vector<string>>();}};classcommand_line_argument_parser{
boost::program_options::options_description desc;public:command_line_argument_parser(){
desc.add_options()(arguments::verbose_option,"Typical long and short switch argument")(arguments::username_option,boost::program_options::value<std::string>(),"username to use")(arguments::files_option_name,boost::program_options::value<vector<string>>(),"input file");}
arguments parse(intargc,constchar*argv[]){
boost::program_options::variables_map variables;
boost::program_options::positional_options_description p;
p.add(arguments::files_option_name,-1);boost::program_options::store(boost::program_options::command_line_parser(argc, argv).options(desc).positional(p).run(), variables);boost::program_options::notify(variables);returnarguments(variables);}};