GetPot Version 2.0¶
Input File and Command Line Parser¶
Author: | Frank R. Schaefer |
---|---|
Email: | fschaef@users.sourceforge.net |
Abstract¶
The two basic methods to pass parameters to the main()
-routine are: input
files and command line arguments. For small scale programs these input methods
allow to change parameters without having to recompile or having to create an
input file parser. Even for large programs input files and command line
arguments are a comfortable way to replace annoying graphical user interfaces.
When debugging complicated code, it is essential to be able to isolate code
fragments into a lonely main()
-routine. In order to feed these isolated
code fragments with realistic data, sophisticated command line and input file
parsing becomes indispensable.
GetPot allows to parse input files and the command line in a very efficient manner. GetPot itself is a small piece of code, completely contained in a header file. This way the installation is very easy and platform independent. The present article discusses the C++ implementation of GetPot. However, GetPot has been ported to Python, Java and a Ruby. Also, the code has been organized in a way that facilitates porting GetPot to other languages.
Traditionally, programmers relied on the getopt library [looijaard97getopt] to handle basic command line interpretation. In recognition of the existing getopt library it is brand with an anagram: GetPot. The software as well as this document is distributed under MIT License terms [1] .
Introduction¶
In order to implement a tamperproof command line parsing, one usually has to write a significant amount of code. Using GetPot allows to shrink code fragments such as:
...
int i = 0;
double v = 0;
char* endptr = 0;
...
if( i < argc ) {
v = strtod(argv[i], &endptr);
if( endptr == argv[i] ) // argv[i] was not a number
v = 9.81; // set default value
}
...
to a single line:
const double V = cl.get(i, 9.81);
In fact, GetPot allows you to do much more sophisticated things in not more
than one single program line. Additionally, GetPot provides a parser to handle
input files such as example.pot
webpage = http://getpot.sourceforge.net/GetPot.html
user = 'G. V. V. Bouche' # whitespace requires quotes
clicks = 231 # [1/s]
factor_x = 1.231 # [m/s^2]
[vehicle]
wheel-base = 2.65 # [m]
initial-xyz = '100. 0.1 5.0' # [m]
and read out its contents in the same way the command line was parsed. There are a few special things to know. In some aspects parsing input files differs from parsing the command line.
Installation¶
GetPot is a project hosted at Sourceforge. There is a web page that provides download-able GetPot libraries for different programming languages:
http://getpot.sourceforge.net/
To install GetPot, simply copy the file GetPot somewhere into the file system
where your compiler can find it (/usr/include
or so [2]). As a requirement you
should have installed the STL-Library which usually comes with any C++ compiler
distribution.
The easiest way to get started is to go through the example files and copy/paste the things you need into your own program. This way you learn GetPot in less than 10 minutes. The following text is mainly written, because every library should have a manual. It basically describes what you would understand anyway when you go through the examples.
Overview¶
When an experienced programmer starts to write the main()
-routine in C or
C++ he produces by reflex a certain code fragment. Programmers who tasted
GetOpt write by reflex an extended version such as the following:
#include<iostream>
#include<GetPot>
...
int main(int argc, char** argv)
{
GetPot cl(argc, argv);
...
The first object created in the application is of type GetPot
. It uses
argc
and argv
to build its internal database of command line arguments.
This is all that is required to start doing fancy things. If one wants to parse
an input file, one has to specify the filename to the constructor, i.e.
GetPot ifl("sprites.pot");
...
defines the file ‘sprites.pot’ to be the input file for GetPot’s internal
database. All functions explained in the following text work independently of
the way the database was constructed. They can be applied to any object of type
GetPot
, whether it was build from a command line or an input file. Input
files, however, provide some special features described in section
Input files. Distributions of GetPot versions 1.0 and above contain
a getpot-mode for emacs to highlight GetPot input files.
There are four different types of command line arguments that are known to GetPot:
- Options:
- Arguments like
--help
,-h
, and--force
can be easily checked for existence. Further, an elegant means is provided to parse arguments that follow specific arguments. - Variables:
- Variables can be defined on the command line and read out as numbers, strings or mixed type vectors.
- Flags:
- GetPot checks if an argument, or any option (argument starting with a single ‘-‘) contains a specific letter.
- Nominus Arguments:
- GetPot lets you iterate through arguments that do not start with a minus-sign.
The very basic idea of the GetPot is to specify the expected return type
through a default argument [3]. There are three basic types known to GetPot:
int
, double
, and std::string
. A set of functions of the same name
with an overloaded version for each type is called a function group.
Other Constructors¶
As mentioned in the previous paragraphs, the command line or configuration files are parsed by a call to the constructor of GetPot. There are the two obvious constructors:
-
GetPot
(arg, argv)¶ initiates the parsing of the command line.
-
GetPot
(Filename) initiates the parsing of a configuration file.
To provide the user with more flexibility, GetPot
provides two more
constructors. They determine the way strings are split up into vectors and what
strings determine the start and the end of a comment:
-
GetPot
(arg, argv, FieldSeparator=", ") initiates the parsing of the command line. Arguments are split into vectors using
FieldSeparator
(see section Variables). The default value is ‘,’.
-
GetPot
(Filename, CommentStart="#", CommentEnd="n", FieldSeparator=", ") initiates the parsing of a configuration file. Anything inside
CommentStart
andCommentEnd
is considered to be a comment. Arguments are split into vectors usingFieldSeparator
.
Options¶
The easiest way to check if an argument is specified on the command line is to
use the search()
-function. It returns true in case the option is found and
false in case it was not. In addition to that, GetPot provides a search()
function with a variable argument list allowing to check elegantly for multiple
options that are equivalent. Example:
...
bool be_nice_f = cl.search("--nice");
if( cl.search("--do-nothing") ) exit(0);
if( cl.search(4, "--help", "-h", "--hilfe", "--sos") ) {
// print some information about how the program works
}
...
The ‘4’ as a first argument to search()
indicates that four strings follow
that represent equivalent options. search()
functions belong to a class of
cursor related functions. Cursor related functions are a very convenient means
to parse the command line. In order to understand how they work, one has to
understand how command line arguments are lined up. A command line given as:
> hello aux -recompile ... myinput.txt
is stored in an array as shown in the Command line arguments lined up in the argv-array. A cursor iterates
over the elements.. The search()
functions set the cursor to a certain position in the array. Others increase
the cursor position. The two member functions:
void disable_loop();
void enable_loop();
allow to specify if the cursor is allowed to go back to the beginning, if no
match is found until the end of the array. The default behavior is ‘yes’. In
case one needs to allow multiple occurrence of an option (e.g. as -I
in
gcc), the wrapping has to be turned off to avoid parsing the same option twice.
Before parsing these kinds of options, one has to reset the cursor position by
the function:
void resetcursor();
Arguments that follow arguments¶
When an argument is found by search()
, the cursor is set to its position.
Now, the next()
functions allow to parse through terms that follow. A line
like:
> myprog -size 14.2341 -i fichier.txt
can be parsed using search()
and next()
as follows:
...
if( ! cl.search("--size") ) return;
const double XSize = cl.next(0.);
if( ! cl.search(2, "-i", "--file") ) return;
const std::string infile = cl.next("dummy.txt");
...
Note that the desired return type is specified by the default argument. XSize
is of type double so the default argument for next()
is of type double. If
the argument following ‘-size’ cannot be converted to a double, then the
default value ‘0.’ will be assigned to XSize. Respectively the same thing
happens with infile where ‘dummy.txt’ specifies that a std::string parameter is
required.
The above code may still seem a bit cumbersome. That is why GetPot provides the
follow()
functions. These functions combine the search for an argument with
the search for a following argument. The above code can be rewritten as:
...
const double XSize = cl.follow(0., "--xsize");
const std::string infile = cl.follow("dummy.txt", 2, "-i","--file");
...
The search()
and follow()
functions set the cursor to the next argument that matches, starting from the current cursor position. These functions can also be used to parse arguments that occur multiple times. The following code shows how multiple occurrence of an argument can be parsed.
Since every time when one needs to parse options with multiple occurrence, one
needs to call disable_loop()
and reset_cursor()
, these two functions
are combined into one, for convenience:
void initmultipleoccurrence();
The above code parses command lines like:
> myprog ... -a 3.1 ... -a -8.2 ... -a 0.2 ... -a 1.02 ...
Nominus Followers¶
Two functions support the listing of files or similar command line arguments that start not with a minus, but follow an option:
const vector<string> nominus_followers(const char* Option);
const vector<string> nominus_followers(const unsigned No, ...);
Both functions return a list of strings that follow an option. The second function allows the specification of multiple options, the number of passed options has to be passed as the first argument. The following code fragment:
const vector<string> inputs = cl.nominus_followers("-i");
const vector<string> outputs = cl.nominus_followers(2, "-o", "--output",
"--ausgabe");
when fed with the following command line:
> application -i main.cpp example.cpp audio.cpp -o video.out --output
libAudio.so libKeyBoard.so libVehicle.so --ausgabe Compressed RGB
will produce two vectors inputs and outputs where the first contains the strings ‘main.cpp’. ‘example.cpp’, and ‘audio.cpp’, The second vector will contain ‘video.out’, ‘libAudio.so’, ‘libKeyBoard.so’, ‘libVehicle.so’, ‘Compressed’, and ‘RGB’.
Directly followed options¶
In the previous section, it is discussed how GetPot handles arguments that
follow other arguments on the command line. Popular programs, such as ‘gcc’
parse information that directly follows a string without any whitespace, such
as ‘-I/usr/local/include/GL’. In this case ‘-I’ indicates that a include path
is to be set and the following character chain represents the path to be used.
Of course, GetPot provides an easy means to access these strings: the
direct_follow()
-functions. This is described in the present section.
However, take a look at the tail()
- functions, section Tails.
They are even easier and solve most probably all your demans. For now, here is
an example for using the direct follow functions:
...
vector<string> incl_path;
const std::string path = cl.direct_follow("", "-I");
// read all arguments that start with '-I'
cl.init_multiple_occurrence();
while(path != "" ) {
incl_path.push_back(string(path));
path = cl.direct_follow("", "-I");
}
...
Tails¶
Options that are appended to a starting string such as ‘-I’ or ‘-L’ in a C compiler, can be read very easily using the tails functions, of which there are three:
string_tails
(StartString)¶returning a vector of strings that are appended to any option starting with
StartString
.
int_tails
(StartString, Default = 1)¶returning a vector of integers that are appended to any option starting with
StartString
.
double_tails
(StartString, Default=-1.0)¶returning a vector of double precision float numbers that are appended to any option starting with
StartString
.
The latter two functions require a conversion of a string to a number. This can potentially fail. So, you can provide a default value by which you determine that an error occured. If a value in the returned list is equal to that default value, you assume that the user was not able to specify a decent number. By default, the default value is minus one.
Variables¶
Variables are a very elegant feature to pass arguments to your program. In the style of ‘awk’ one can specify some variables, assign values to them, and read them from inside your program. A ‘variable’ is defined on the command line like:
variable-name '=' value.
Note that no blanks are allowed. Quotes can be used, though, to pass longer
strings or vectors. Variables are accessed through the function call operator.
In the same way as the next()
and follow()
function groups, the
function call operator determines its return type through the default argument.
By a code fragment like:
...
const double initial_velocity = cl("v0", 10.); // [m/s]
const std::string integration_type = cl("integration", "euler");
const int no_samples_per_sec = cl("samples", 100); // [1/s]
...
one can parse a command line like:
> myprog ... v0=14.56 integration=runge-kutta samples=500 ...
GetPot, can do even more. It is able to treat input vectors, of mixed type. This allows to have command line arguments like:
> myprog ... sample-interval='-10. 10. 400' \
color-mode='32 RGB 0.4 0.2 0.5' ...
Vectors variables are also read with the function call operator. However, the
position in the vector has to be specified by an index. The total number of
elements in a vector can be determined through the function
vector_variable_size(VariableName)
. Here is an example of how to parse the
above command line:
...
if( cl.vector_variable_size("sample-interval") < 1 ) {
cerr << "error: sample interval, argument does not fit format:\n";
cerr << " MINIMUM MAXIMUM NO_SAMPLES\n".
cerr << " need at least the minimum !\n";
}
const double Min = cl("sample-interval", 0, 0.); // [s] default 0
const double Max = cl("sample-interval", 1, 10.); // [s] default 10
const int NoSamples = cl("sample-interval", 2, 200); // sample no.
...
const int BPP = cl("color-mode", 64, 0);
const std::string Mode = cl("color-mode", "Grayscale", 1);
if( ! strcmp(Mode,"RGB") && BPP < 24 )
cerr << "RGB not possible with less than 24 bits per pixel.\n";
...
The second argument to the ()
-operator specifies the position in the
vector, the third argument is the default value. In the example above, the
first vector element of ‘sample-interval’ (index 0) defines the minimum
Min
. The second argument the maximum (index 1) and the third element (index
2) the number of samples. Respectively the ‘color-mode’ information is parsed.
Constraints¶
The last section showed how variables could be parsed using the function call operator. Additionally, to the type constraints, the user might specify constraints on the value of a variable. GetPot has its own grammar to specify value constraint which is concise and in probably in most cases more dense than a equivalent C code. Consider for example the constraint:
(!= 0xFF) & ((< 12 % 3) | >= (12 % 4))
which means that thevalue needs to be unequal 0xFF. Also, if it is less than 12 it needs to be divisable by three and if it is greater than 12, it needs to be divisable by 4. For a variable “x” this constraint can be specified with GetPot like
const int x = ifile("x", 0, "(!= 0xFF) & ((< 12 % 3) | >= (12 % 4))");
Handwriting the C++ code results in
const int x = ifile("x", -1);
if ( x == 0xFF ) x = -1;
else if( x < 12 && (x % 3 != 0) ) x = -1;
else if( x >= 12 && (x % 4 != 0) ) x = -1;
which is three lines more than in the above GetPot example.
Condition Operator Meaning > X Value greater than X >= X Value greater or equal X == X Value equal to X != X Value not equal to X <= X Value less or equal X < X Value less than X % X Value divisable by X ! E Expression E is not true ‘string’ Value is equal to string
Note, that ‘bool’ variables cannot be subject to constraints. There range of ‘true’ and ‘false’ is so restricted, that any meaningful condition could equally written by hand (e.f. ‘if x equal false, then exit’). The following operators and delimiters allow to combine multiple expressions:
Binary Operator Usage Meaning E0 | E1 Expression E0 or E1 is true E0 & E1 Expression E0 and E1 is true E0 operator (E1 operator E2) Treat E1 and E2 before E0
The last line stands for ‘grouping’ that allows to cirumvent precedence rules, e.g.:
(% 3 | % 4) & > 12
Checks for ‘divisable by three or divisable by four’ before it checks that the number is greater than 12. This circumvents the mathematical precedence of ‘and’ over ‘or’. This circumvents the mathematical precedence of ‘and’ before ‘or’.
template <class T> T operator()(const StringOrCharP VarName, T Default) const;
template <class T> T get(const StringOrCharP VarName);
template <class T> T get(const StringOrCharP VarName, const char* Constraint);
template <class T> T get(const StringOrCharP VarName, const char* Constraint,
T Default);
Flags¶
Flags are activated by single letters inside an argument or option [4]. GetPot has two functions to access flags:
-
options_contain
(const char* Flags)¶ Checks if any option (argument that does start with a single ‘-‘) contains one of the flags specified in the string
Flags
.
-
argument_contains
(unsigned Idx, const char* Flags)¶ Checks if argument number
Idx
contains one of the flags specified inFlags
.
Here is an example:
...
const bool verbose_f = cl.options_contain("vV");
const bool extract_f = cl.argument_contains(1, "xX");
const bool create_f = cl.argument_contains(1, "cC");
if( create_f && extract_f )
{ cerr << "cannot create and extract at the same time.\n"; exit(-1); }
...
At first, it is checked if any option (argument that starts with a minus) contains a letter ‘v’ or ‘V’. If it does, then the verbose flag will be set. The letters ‘x’, ‘X’, ‘c’, and ‘C’ in the first argument indicate if one uses the program to extract or create a new file. Correspondingly the flags are set.
Nominus Arguments¶
In order to quickly go through the arguments that do not start with a minus,
the next_nominus()
function is provided. Each call to this function returns
the following argument that does not have a minus. If there is no further
argument, this function return ‘0’. Example:
...
vector<string> files;
const std::string nm = cl.next_nominus();
while( nm != 0 ) {
file.push_back(nm);
nm = cl.next_nominus();
}
...
Here the nominus arguments are read one after the other into an array of strings containing some filenames. If this is all one needs, one is better of asking directly for a vector of all nominus arguments:
vector<string> files = cl.nominus_vector();
This line does the same as the slightly more complicated piece of code above.
Direct access to command line arguments¶
The very simplest way to access the argument list one is to use the []
-operator:
while( cl[5] ) cout << cl[5] << endl;
The []
-operator returns a zero pointer in case that one tried to reference
an option that does not exists. Similar to that, the get()
-functions allow
to specify a type through a default argument, so that one writes for example:
const double Acceleration = cl.get(5, 9.81);
to use argument number five as the value of a certain double variable. The high flexibility of GetPot, however, makes these methods seem to be a little outdated. Fixing the position of an argument in a argument list, is a restriction that makes the use of a program unnecessarily difficult. As seen preceeding sections, one can do much better without adding any more programming effort.
Input files¶
Input files are parsed a little differently from the command line. Perhaps the most important feature are the ‘#’-comments that allow to add text into the file without being actually parsed:
# -*- getpot -*- activate emacs 'getpot-mode'
# FILE: "example.pot"
# PURPOSE: some examples how to use GetPot for input file parsing.
#
# (C) 2001 Frank R. Schaefer
#
# License Terms: GNU Lesser GPL, ABSOLUTELY NO WARRANTY
#####################################################################
v0 = 34.2 # [m/s^2] initial speed of point mass
As can be seen in the same example, the variable assignments now allow
whitespace between a left hand value v0
the assignment operator ‘=’ and a
right hand value 34.2.
One thing is crucially different in input files: sections. In input files, the
basic advantage of sections is to reduce the variable length. Imagine, you have
a parameter weight occurring multiple times: for the total vehicle, for each
tire, for a load in the trunk etc. Now in order give each one a unique
identifier one would have to call them total_vehicle_weight
,
vehicle_tires_front_left_weight
and so on. Here is where sections become
handy:
...
[vehicle]
length = 2.65 # [m]
initial-xyz = '100. 0.1 5.0' # [m]
# coefficients of magic formula [Pajeka, et al.]
[vehicle/tires/front]
[./right]
B = 3.7976 C = 1.2 E = -0.5 D = 64322.404
[../left]
B = 3.7976 C = 1.2 E = -0.5 D = 64322.404
[vehicle/tires/rear]
[./right]
B = 3.546 C = 1.135 E = -0.4 D = 59322.32
[../left]
B = 3.546 C = 1.11 E = -0.32 D = 59322.32
[vehicle/chassis] # i.e. vehicle/chassis
Roh = 1.21; # [kg/m^3] density of air
S = 5.14; # [m^2] reference surface
Cd = 0.45; # [1] air drag coefficient
...
In section Variables it was explained how to read out variables. The same functions work, of course, for a database build up on a parsed file. The only difference is that sections produce suffixes in front of variables and options. A database fed with a file as the above one, can be queried as follows:
...
front_tire.B = ifile("vehicle/tires/front/right/B", 0.);
front_tire.C = ifile("vehicle/tires/front/right/C", 0.);
front_tire.D = ifile("vehicle/tires/front/right/D", 0.);
front_tire.E = ifile("vehicle/tires/front/right/E", 0.);
...
Note that there are two special indicators in a section label:
./
Take the actual section and append the following name to it.
../
Go back to the parent section and create a name appended to the name of the parent.
Finally, an empty label like []
, resets the section suffix to nothing. Speaking in name space terminology, we are back to the global name space.
Prefixes¶
When reading out variables, options and flags in a large section tree, it can be very messy if one has to write the full variable name such as:
"vehicle/tires/front/right/B"
Here, the prefix feature comes convinient. It allows to narrow the search space for arguments, variables, options and flags. Only such objects are considered that start with the given prefix. The prefix itself is then cut of the object before investigation. In specific this prefix can be a section name, so that a code fragment like:
...
front_tire.B = ifile("vehicle/tires/front/right/B", 0.);
front_tire.C = ifile("vehicle/tires/front/right/C", 0.);
front_tire.D = ifile("vehicle/tires/front/right/D", 0.);
front_tire.E = ifile("vehicle/tires/front/right/E", 0.);
...
can be rewritten as:
...
ifile.set_prefix("vehicle/tires/front/right/")
front_tire.B = ifile("B", 0.);
front_tire.C = ifile("C", 0.);
front_tire.D = ifile("D", 0.);
front_tire.E = ifile("E", 0.);
...
which is, of course, much more readable. However, one should not forget to set or reset the prefix to the correspondent section (such as “” for the root section).
UFOs - Unidentified Flying Objects¶
Some users tend to pass wrong options to a program on the command line, to define sections in configuration files that nobody cares about (e.g. ‘vehicle/front-tire-groop’) and tiny input errors can prevent a program from functioning. In order to allow the program to tell the jittery user that he did something that nobody understands, GetPot provides UFO detection !
Indeed, these unidentified command line arguments that fly around without ever being processed, these variables in configuration files that do not serve any purpose, command line switches that do not switch anything - these things are easily detected and the programmer can decide what he wants to do about it.
Note
UFO detection is prefix dependent! In case you were using prefixes, you must reset the prefix to the section you are investigating [5].
The following sections discuss functions where the valid options are explicitly passed to the function. However, since version 1.1.5, GetPot traces the access to command line arguments. If it is enough evidence that no function ever read a command line argument, then you might use simply the following functions:
-
unidentified_arguments
()¶
-
unidentified_options
()¶
-
unidentified_variables
()¶
-
unidentified_sections
()¶
-
unidentified_nominuses
()¶
Each of them returns a vector of strings, i.e. the arguments, options, variables, sections or nominuses that are ‘untouched.’ Call these functions at the end of your command line/configuration file treatment.
Unidentified Arguments¶
In order to detect unidentified flying arguments one can uses the following functions:
vector<string> unidentified_arguments(unsigned Number, const char* Known, ...) const;
vector<string> unidentified_arguments(const vector<string>& Knowns) const;
In the first case, Number
specifies the number of known arguments and the
following list of const char*
-pointers specify the list of known arguments.
In the second case, the known arguments are collected in a string vector. This
is particularly useful, in case the known arguments are dynamic (nominus
arguments as filenames, etc.).
Any argument on the command line, that is not contained in the specified list of known arguments is listed in the string vector that is returned by these functions.
Unidentified Options¶
Similarly, unidentified flying options can be detected by the functions:
unidentified_options(unsigned Number, const char* Known, ...) const;
vector<string> unidentified_options(const vector<string>& Knowns) const;
These functions work the same way as the functions for unidentified flying arguments, but only argument that do start with at least one ‘-‘ are considered.
Unidentified Flags¶
Flags, i.e. letters in arguments/options that activate or deactivate certain switches can be checked using the function:
string unidentified_flags(const char* Known, int ArgumentNumber =-1) const;
This function operates in two modes. In first mode, if the second argument is omitted or set to -1, then all options starting with a single ‘-‘ are considered and checked if there is any letter in them that is not in Known. Known is simply a string concatenating all possible flags (such as ‘xcvfjt’ for tar). In the second mode, the argument number ArgumentNumber on the command line is checked for flags (argument 1 for example in tar). The flags that are not contained in Known are listed in the returned string.
Unidentified Variables¶
Unidentified flying variables on the command line or in configuration files may be confusing the same way as arguments, options and flags. Therefore the functions:
vector<string> unidentified_variables(unsigned Number, const char* Known, ...) const;
vector<string> unidentified_variables(const vector<string>& Knowns) const;
provide UFO detection for variables in the usual manner as the described above.
Unidentified Sections¶
Users may even define nonsense sections or mess around with the ../
- and ./
-parts in the section labels. The result is a unreadable configuration file. To detect such mischievous settings the functions:
vector<string> unidentified_sections(unsigned Number, const char* Known, ...) const;
vector<string> unidentified_sections(const vector<string>& Knowns) const;
detect unidentified flying sections.
Unidentified Nominuses¶
The last category of UFOs are nominus arguments that are not recognized as filenames, variables or section names. They are detected by the functions:
vector<string> unidentified_nominuses(unsigned Number, const char* Known, ...) const;
vector<string> unidentified_nominuses(const vector<string>& Knowns) const;
following the usual UFO detection procedure.
Dollar Bracket Expressions¶
Since version 1.0 the GetPot parser provides a sophisticated function to handle several arithmetic and string operations. In some cases this can significantly facilitate the writing of a configuration file. The so called dollar bracket expressions constitute a very simple lisp-like language. Instead of using normal brackets, it uses dollar brackets to embrace an expression. For example:
...
a = ${+ 1 1}
b = ${<-> Phillip Ph F}
...
will result assign “2” to the variable a and “Fillip” to variable b. Dollar bracket expressions can, of course be nested and they allow conditional assignments. However, iteration, or even recursion is purposely not implemented. This is, in order to avoid possible unwanted infinite iterations/recursions caused by the writer of the configuration file [6]. An overview over all operators is provided in table 1. Please, note that you do not have to use any of those to write GetPot configuration files. Simply start using them when you need them.
String operations | |
${string} |
variable replacement |
${:string} |
pure string (no parsing inside) |
${&string1 string2 string3 ...} |
concatenation |
${<-> string original replacement} |
string replacement |
Arithmetic operations | |
${+ arg1 arg2 arg3 ...} |
plus |
${* arg1 arg2 arg3 ...} |
multiplication |
${- arg1 arg2 arg3 ...} |
subtraction |
${/ arg1 arg2 arg3 ...} |
division |
${^ arg1 arg2 arg3 ...} |
power |
Comparisons | |
${== arg0 arg1 arg2 ...} |
equal |
${> arg0 arg1 arg2 ...} |
greater |
${< arg0 arg1 arg2 ...} |
less |
${>= arg0 arg1 arg2 ...} |
greater or equal |
${<= arg0 arg1 arg2 ...} |
less or equal |
Conditions | |
${? arg0 arg1 arg2} |
if-then |
${?? arg0 arg1 arg2 ...} |
choice |
Vector/String subscriptions | |
${@: string index0} |
specific letter in string |
${@: string index0 index1} |
substring in string |
${@ variable index0} |
specific element in vector variable |
${@ variable index0 index1} |
sub-vector in vector variable |
Macros | |
${! string} |
macro expansion |
Table 1: Total set of dollar bracket operators.
String Operations¶
Table string-operations lists the dollar bracket expressions that allow string operations:
${string} |
variable replacement |
${:string} |
pure string (no parsing inside) |
${& string1 string2 string3 ...} |
concatenation |
${<-> string original replacement} |
string replacement |
Table 2: String operations
A dollar bracket expression that only contains a name is treated as variable expansion.
...
name = GetPot
[${name}] # meaning: [GetPot]
...
will set a section label GetPot
.:
...
[Mechanical-Engineering]
boss = Dr.\ Frieda\ LaBlonde
members = 24
professors = 5
[]
x = Mechanical-Engineering
info = '${${x}/boss}: ${${x}/professors}/${${x}/members}'
...
will assign the string "Dr. Frieda La Blonde: 5/24"
to the variable info
. Together with sections, the dollar bracket expressions allow an elegant way to define dictionaries, such as:
...
my-car = Citroen-2CV
[Nicknames]
BMW = Beamer
Mercedez = Grandpa\'s\ Slide
Volkswagen = Beetle
Citroen-2CV = Deuche
[]
my-car = ${Nicknames/${my-car}}
...
uses the section Nicknames
as dictionary. At the end the variable
my-car
will contain the name Deuche
instead of Citroen-2CV
.
Some users might miss the ability to have dollar bracket expressions or white
spaces inside their strings, therefore a feature is provided that enables to
specify pure strings. Anything in between a ${...}
environment is left as
is without any modification. In the following example:
...
info = ${:even expressions like ${my-car} are left as they are}
...
whitespaces and brackets are left as they are. This feature is essential when defining macros Macro Calls.
Strings can be concatenated by the ${& }
-operator as in the following
example:
info = ${& simple concatination without whitespaces results in a mess}
As a result of this statement, info
would contain the string:
"simpleconcatinationwithoutwhitespacesresultsinamess".
in other words: if you want to form a sentence consisting of words separated by whitespaces, you should either use backslashed whitespaces, quotes or ‘pure strings’. As any other normal dollar bracket expression it can contain dollar bracket expressions as part of its arguments such as in the following example:
name = FriedaBoelkenwater
network = neurology.west-wing.gov
contact-info = ${& ${:Email:} ${name} @ ${network}}
where the contact-info
would be:
"Email: FriedaBoelkenwater@neurology.west-wing.gov"
Another important string operation are replacements using the
${<->}
-operator such as in:
OBJECTS = main.o tires.o suspension.o drive-train.o cvi.o steering-system.o
PROGRAM_FILES = ${<-> ${OBJECTS} .o .cpp}
where the extensions ‘.o’ are replaced by extension ‘.cpp’ to get the filenames of the source code.
Arithmetic Operations¶
Table 3 arithmetic-operations lists the dollar bracket expressions that allow
arithmetic operations. The summation operator ${+}
simply adds up all
arguments and returns a string containing the total sum. Similarly, the
multiplication operator multiplies all arguments as returns the total product.
The subtraction operator subtracts all arguments after the first argument from the first argument, i.e.
x: = ${- 100 50 4 2} y = ${/ 12 2 3}
assigns the value 45 to the variable x
. Similarly, the division operator
divides the first argument by all following arguments. Variable y
therefore carries the value 2 after parsing.
${+ arg1 arg2 arg3 ...} |
plus |
${* arg1 arg2 arg3 ...} |
multiplication |
${- arg1 arg2 arg3 ...} |
subtraction |
${/ arg1 arg2 arg3 ...} |
division |
${^ arg1 arg2 arg3 ...} |
power |
Table 3: Arithmetic operations.
Comparison Operators¶
Table 4 comparison-operations lists the dollar bracket expressions that allow
comparisons. These operators will return the number of the first argument for
which the operator is true with respect to argument arg0
. If none matches
‘0’ is returned. Example:
country-id = ${== ${code} .de .fr .at .it .ch .cz}
will fill the variable country-id with 3 in case that code
is ”.at”.
${== arg0 arg1 arg2 ...} |
equal |
${> arg0 arg1 arg2 ...} |
greater |
${< arg0 arg1 arg2 ...} |
less |
${>= arg0 arg1 arg2 ...} |
greater or equal |
${<= arg0 arg1 arg2 ...} |
less or equal |
Table 4: Comparisons.
Conditional Expansion¶
The operator in table 5 allow conditional expansion. The ${? }
-operator
constitutes a if-then statement. If arg0
, usually the result of a
comparison operation, is equal to 1 than arg1
is returned - otherwise
arg2
. The ${?? }
-operator allows to choose from a list of arguments. If
arg0
is ‘1’ than arg1
is returned, if it is ‘2’ than arg2
is
returned, etc.
${? arg0 arg1 arg2} |
if-then |
${?? arg0 arg1 arg2 ...} |
choice |
Table 5: Conditional expansion.
Vector and String Subscription¶
The operator in table 6 allow vector and string subscriptions. A ${@:
}
-operator performs string subscription, a ${@ }
-operator performs vector
subscription. If only one index is specified, than only one specific element is
returned. If two indices are given, the substring/sub-vector from the first to
the last index is returned. If the second index is ‘-1’ it is considered to be
equal to the size of the vector/string.
${@: string index0} |
specific letter in string |
${@: string index0 index1} |
substring in string |
${@ variable index0} |
specific element in vector variable |
${@ variable index0 index1} |
sub-vector in vector variable |
Table 6: Vector and string subscription.
Macro Calls¶
Any string stored in a variable can serve as a macro, so there is no need to
have a macro-definition operator. The pure strings using the ${: }
-operator
come handy, though, since they allow to define dollar bracket expressions that
are not directly parsed. Strings are parsed with the ${! }
-operator.
Defining:
x2 = ${: ${* ${x} ${x}}}
x4 = ${: ${* ${!x2} ${!x2}}}
x6 = ${: ${* ${!x4} ${!x2}}}
sin = ${: ${* ${x}
${+ 1
${/ ${!x2} -6}
${/ ${!x4} 120}
${/ ${!x6} -5040}
}
}
}
makes it possible to compute sinuses inside a GetPot file, for example:
x = 0.212
info = ${!sin}
will assign something that is close to the sinus of 0.212 to the variable info. Keep in mind, that through the huge amount of float-string conversions, back and forth, a lot of precision is lost. Don’t consider this as a disadvantage ! As said before, configuration files are not there to write programs. A configuration file language, therefore, has to be a bit cumbersome, in order to prevent mayhem.
[1] | This means basically, it is for free but it can still be used in commercial products. You should have received a copy of the MIT License along with this library. |
[2] | If you have no root access on your machine, copy it somewhere in your directory tree and specify -I/home/genius/my_include/ when using g++ - supposed that you copied GetPot into /home/genius/my_include/ . |
[3] | Originally, C++ does not allow a function overloading with respect to the return type. The default argument must have the same type as the variable it will be assigned to. Therefore, functions are overloaded with respect to the default argument, which at the same time defines the return type. |
[4] | A popular program accepting single letter options is ‘tar’. With strings like ‘xzvf’ this program allows to state very concisely a desired behavior (extract ‘x’, unzip ‘z’, verbose ‘v’ the following file ‘f’). |
[5] | That means to “” for example, in case you want to refer to the root section |
[6] | The idea behind is that the responsibility for the functioning of an application shall lie on the programmer. He has to make sure that the program, either produces error/warning messages or functions properly. An infinite recursion in the configuration file could not be caught be the application. An undocumented malfunctioning however is not acceptable, since the user has no means to adapt his inputs. The author is well aware that there are major software companies that do not share this philosophy. |
[looijaard97getopt] | Looijaard, F. (1997). getopt - parse command options (enhanced). Man page. Available on most Unix systems. |