NSF Postdoctoral Research
A set of C++ code developed by Andrew E. Slaughter
|
The UserOptions class is a wrapper of the Boost.Program_options library, a link to the documetation is included below. The code in this example explores all of the functionality that was built into this wrapper class to make using the Boost class a bit easier. The UserOptions only implements a portion of what is possible with the Boost code, but it was designed in such a fashion that the program options may be manipulated with the Boost libraries to achieve custom option handling without changing the source code of the class, just the program using it.
Boost.Program_options:
http://www.boost.org/doc/libs/1_49_0/doc/html/program_options.html
The test program begins by including the necessary file and using the related namespace for the common library. The main program then begins, the main program requires the argc
and argv
inputs to which the command line inputs are captured.
// Standard include #include <vector> // Include my common library, which contains the UserOptions class #include "common/include.h" using namespace SlaughterCommon; // The main function for test_user_options.cpp int main(int argc, char* argv[]){
The first instance of the UserOptions class for this example is referred to as the "master" class, since all functionality will stem from this class. First, the main
instance is created which will include some general options as evidenced by the header.
Then a title is added, this title will be printed prior to the list of variables when the --help
is used. Thus, the help flag must be added. The add_flag
is used for the help because it doesn't require an associated value, simply the presence of --help
on the command line triggers the behavior. The --help
flag is automatically handled by the UserOptions class to display the options list when this flag is added.
Next, another trigger is added (--verbose
) which is used by the test program to toggle the display of the variables when the program is executed.
Finally, an option is specified that defines a default coinfiguration file, this options is automatically handled by the UserOptions class. More details of the configuration file is given here: Configuration Files .
// Define the master class for storing and accessing options UserOptions main("General Options"); // Add an extra title to the help output main.add_title("\nThis is a test program, the options that are accessible\nfrom the command line are listed below.\n\nFor example:\n./test_user_options --value=5\n\n"); // Add a help option main.add_flag("help,h", "If you need help"); // Add a verbose options that will all the results when used main.add_flag("verbose", "Display's the values of all the variables"); // Assign a configuration file, including a default file main.add_option<string>("config", "../data/test_user_options/test_user_options.cfg", "Specify a configuration file"); // Some value that you may want to used main.add_option<double>("value,v", 0, "The value of some importart variable");
After the main
instance, two more instances of the UserOptions are created that contain a number of options. The "Advanced" options include notation that is used by the configuration file (Configuration Files). Additionally, the input
and output
options utilize positional arguments, these function exactly as described in the Boost documentation.
// Define another instance of UserOptions that includes io info UserOptions io("Input/Ouput Options"); io.add_option<string>("input,i", "input.txt", "The input filename", 1); io.add_option<string>("output,o", "default.txt", "The output file for widget document", 1); io.add_option<std::vector<string> >("file-list","A list of files", 3); // Define some advanced options (the configuration file sets some of these) UserOptions adv("Advanced Options"); main.add_option<double>("advanced-value", 0, "The value of some really importart variable"); adv.add_option<string>("input.path","The path to the input file"); adv.add_option<string>("input.name","The name of the input file, w/o the extension"); adv.add_option<string>("input.ext","The extension to the input file"); adv.add_option<std::vector<int> >("many,m", "Many of these are allowed"); adv.add_option<std::vector<double> >("multi","This input can contain multiple values"); std::vector<int> def; def.push_back(5); def.push_back(6); adv.add_option<std::vector<int> >("many-multi", def, "You can combine behavior and list default(s)", "[5,6]");
The next instance of the class will be used to add a few hidden options that will not be visible with the general --help
, but a flag is added --show-hidden
that causes the hidden options to display. The code for displaying is included in the program itself.
// Add some hidden options UserOptions hide("Hidden Options"); hide.add_option<int>("big-red-button", 0, "Don't change this this value!"); hide.add_flag("show-hidden","If you know about it then you know what it does"); hide.hidden = true; // this hides the hide instance
At this point all of the desired options are defined, but they are not ready for use. To make them available and functional, first the additional instances are grouped to the "master" instance with the add
function. Then the options are made ready by apply the command line arguments with the apply_options
function.
// Group the various classes and apply the command line inputs main.add(io).add(adv).add(hide); main.apply_options(argc, argv);
The remainder of the program simply implements a few types of display to demonstrate how the options are used, see Using the Test Program .
// If --show-hidden is used display the available hidden options if (main.get_flag("show-hidden")){ std::cout << hide.opt_list << std::endl; return 0; } // If --verbose is used, show all the values bool display = main.get_flag("verbose"); if (display){ // Show the main options printf("config: %s\n", (main.get<string>("config")).c_str()); printf("value: %f\n", main.get<double>("value")); // Show the input/output options printf("input: %s\n", (main.get<string>("input")).c_str()); printf("output: %s\n", (main.get<string>("output")).c_str()); if(main.exist("file-list")){ std::vector<string> vec0 = main.get<std::vector<string> >("file-list"); for(int i = 0; i < vec0.size(); i++){ printf("file-list[%d] = %s\n", i, vec0[i].c_str()); } } // Show the advanced options printf("advanced-value: %f\n", main.get<double>("advanced-value")); printf("input.path: %s\n", (main.get<string>("input.path")).c_str()); printf("input.name: %s\n", (main.get<string>("input.name")).c_str()); printf("input.ext: %s\n", (main.get<string>("input.ext")).c_str()); printf("bit-red-button: %d\n", main.get<int>("big-red-button")); if (main.exist("many")){ std::vector<int> vec1 = main.get<std::vector<int> >("many"); for(int i = 0; i < vec1.size(); i++){ printf("many[%d] = %d\n", i, vec1[i]); } } if (main.exist("multi")){ std::vector<double> vec2 = main.get<std::vector<double> >("multi"); for(int i = 0; i < vec2.size(); i++){ printf("multi[%d] = %f\n", i, vec2[i]); } } if (main.exist("many-multi")){ std::vector<int> vec3 = main.get<std::vector<int> >("many-multi"); for(int i = 0; i < vec3.size(); i++){ printf("many-multi[%d] = %d\n", i, vec3[i]); } } } else { printf("The program worked great!\n"); } return 0; }
The command line syntax is as follows. For options defined with a short-hand flag, those with a "," followed by a single character (e.g., the value
option in the example program) the following syntax is supported. If a short-hand is not given, only the --
commands are valid.
$ ./test_user_options --value=5 $ ./test_user_options --value 5 $ ./test_user_options -v 5
To display the list of available options simply use the --help
option as follows.
$ ./test_user_options --help $ ./test_user_options -h
To display a list of all the values of all the options, use the --verbose
flag. This flag can be anywhere if additional options are set.
$ ./test_user_options --verbose
To display the hidden options use the --show-hidden
flag, not that this behavior is not part of the class, just the example.
$ ./test_user_options --show-hidden
An example configuration file is included in the data/test_user_options
directory. This file is shown below. Notice the use of [input] label. This causes all options below it to have the
input
. prefix. Thus, the options, when defined, must have this syntax as shown in the advanced options of the example program.
advanced-value=360 [input] path=/this/is/my/path/ name=myfile ext=.txt
In all cases the values from the command-line overwrite values in the configuration file. For example the following changes the value for --advanced-value
from the value of 360 defined in the file.
$ ./test_user_options --advanced-value=180 --verbose
The use of positional options is used to make setting the input and output files easier, that is without the inclusion of the --input
and --output
flags or thier short-hands. The user should refer to the Boost documentation for more help with positional options. The following options demonstrate how they are used in this program.
The basic usage, more advanced usage exists, sets the first input that does not have a flag is set to the input and the second without a flag to the output.
$ ./test_user_options in.dat --advanced-value=180 --verbose out.dat $ ./test_user_options in.dat out.dat --advanced-value=180 --verbose $ ./test_user_options in.dat --advanced-value=180 out.dat --verbose
The existance of an option may be tested using exist
member function as follows. This allows the user to test if an option is specified This is useful if no default value is given, which causes an error when the get
function is used.
if (main.exist("many")){ std::vector<int> vec = main.get<std::vector<int> >("many"); for(int i = 0; i < vec.size(); i++){ printf("many[%d] = %d\n", i, vec[i]); } }
It is possible to store multiple instances of an option, this is accomplished by using a std::vector
as the option type, as shown below and in the advanced options group in the example.
adv.add_option<std::vector<int> >("many,m", "Many of these are allowed");
Run the following to store multiple integers in the "many" option.
$ ./test_user_options --many 1 --many 2 --verbose
Multiple values for a single option may also be specified. This is accomplished with the same syntax as above.
adv.add_option<std::vector<double> >("multi","This input can contain multiple values");
This vector then stores the value(s) specified after the option flag, which is illustrated with the following command.
$ ./test_user_options --multi 1 12 1.2 --verbose
It is possible to define default values, but an additional argument is required. This argument provides the text to display when the --help
command is given. Failure to include this value will produce an error message from Boost. For example:
std::vector<int> def; def.push_back(5); def.push_back(6); adv.add_option<std::vector<int> >("many-multi", def, "You can combine behavior and list default(s)", "[5,6]");
The two methods methoded above for defining multipe values are interchangeable or may be used in combination, as illustred with the "many-multi" option in the test program.
./test_user_options --verbose --many-multi 10 11 12 --many-multi 20 21 22 --value 1 --many 1 --many 2 --multi 1 1.1 1.2 --multi 2 2.1 2.2
The multitoken behavior is built-in to the positional arguments, however the user must specify how many values are expected, as shown in the following code and command-line execution. For this reason, the apply_multitoken_option
does not allow positional agruments.
io.add_option<std::vector<string> >("file-list","A list of files", 3);
$ ./test_user_options --verbose input.txt output.txt file1.txt file2.txt
// Standard include #include <vector> // Include my common library, which contains the UserOptions class #include "common/include.h" using namespace SlaughterCommon; // The main function for test_user_options.cpp int main(int argc, char* argv[]){ // Define the master class for storing and accessing options UserOptions main("General Options"); // Add an extra title to the help output main.add_title("\nThis is a test program, the options that are accessible\nfrom the command line are listed below.\n\nFor example:\n./test_user_options --value=5\n\n"); // Add a help option main.add_flag("help,h", "If you need help"); // Add a verbose options that will all the results when used main.add_flag("verbose", "Display's the values of all the variables"); // Assign a configuration file, including a default file main.add_option<string>("config", "../data/test_user_options/test_user_options.cfg", "Specify a configuration file"); // Some value that you may want to used main.add_option<double>("value,v", 0, "The value of some importart variable"); // Define another instance of UserOptions that includes io info UserOptions io("Input/Ouput Options"); io.add_option<string>("input,i", "input.txt", "The input filename", 1); io.add_option<string>("output,o", "default.txt", "The output file for widget document", 1); io.add_option<std::vector<string> >("file-list","A list of files", 3); // Define some advanced options (the configuration file sets some of these) UserOptions adv("Advanced Options"); main.add_option<double>("advanced-value", 0, "The value of some really importart variable"); adv.add_option<string>("input.path","The path to the input file"); adv.add_option<string>("input.name","The name of the input file, w/o the extension"); adv.add_option<string>("input.ext","The extension to the input file"); adv.add_option<std::vector<int> >("many,m", "Many of these are allowed"); adv.add_option<std::vector<double> >("multi","This input can contain multiple values"); std::vector<int> def; def.push_back(5); def.push_back(6); adv.add_option<std::vector<int> >("many-multi", def, "You can combine behavior and list default(s)", "[5,6]"); // Add some hidden options UserOptions hide("Hidden Options"); hide.add_option<int>("big-red-button", 0, "Don't change this this value!"); hide.add_flag("show-hidden","If you know about it then you know what it does"); hide.hidden = true; // this hides the hide instance // Group the various classes and apply the command line inputs main.add(io).add(adv).add(hide); main.apply_options(argc, argv); // If --show-hidden is used display the available hidden options if (main.get_flag("show-hidden")){ std::cout << hide.opt_list << std::endl; return 0; } // If --verbose is used, show all the values bool display = main.get_flag("verbose"); if (display){ // Show the main options printf("config: %s\n", (main.get<string>("config")).c_str()); printf("value: %f\n", main.get<double>("value")); // Show the input/output options printf("input: %s\n", (main.get<string>("input")).c_str()); printf("output: %s\n", (main.get<string>("output")).c_str()); if(main.exist("file-list")){ std::vector<string> vec0 = main.get<std::vector<string> >("file-list"); for(int i = 0; i < vec0.size(); i++){ printf("file-list[%d] = %s\n", i, vec0[i].c_str()); } } // Show the advanced options printf("advanced-value: %f\n", main.get<double>("advanced-value")); printf("input.path: %s\n", (main.get<string>("input.path")).c_str()); printf("input.name: %s\n", (main.get<string>("input.name")).c_str()); printf("input.ext: %s\n", (main.get<string>("input.ext")).c_str()); printf("bit-red-button: %d\n", main.get<int>("big-red-button")); if (main.exist("many")){ std::vector<int> vec1 = main.get<std::vector<int> >("many"); for(int i = 0; i < vec1.size(); i++){ printf("many[%d] = %d\n", i, vec1[i]); } } if (main.exist("multi")){ std::vector<double> vec2 = main.get<std::vector<double> >("multi"); for(int i = 0; i < vec2.size(); i++){ printf("multi[%d] = %f\n", i, vec2[i]); } } if (main.exist("many-multi")){ std::vector<int> vec3 = main.get<std::vector<int> >("many-multi"); for(int i = 0; i < vec3.size(); i++){ printf("many-multi[%d] = %d\n", i, vec3[i]); } } } else { printf("The program worked great!\n"); } return 0; }