Evaluating Program Arguments

ICL’s ICLUtils package contains the program argument evaluation framework, providing the following functionalities:

  • a simple and intuitive string-based description of allowed program arguments

  • an easy to use way to describe program arguments

  • a efficient parser for program arguments, that provides human-readable error messages

  • an automatically supported set of common default program arguments, such as -version or -help

  • a concise method pa that can be use to query whether a specific program argument has been given and what it’s sub arguments where

Program Arguments and Sub-Arguments

In general, we distinguish between the term program argument, sub argument and dangling arguments. E.g. an image type conversion application my-convert could expect an input image file name, an output image file name and an argument -size that always gets an extra sub-argument which defines the target image size. A valid call to the program would then be something like:

my-convert -size 320x240 image.png image.jpg

In this case, we classify the set of all given program arguments as follows:

  • -size as a defined argument (expecting one sub-argument)

  • 320x240 as a sub-argument

  • image.png and image.jpg as dangling arguments

All program arguments are either defined, expecting 1,n or an arbitrary number of sub-arguments, dandling or sub-arguments of defined ones. Furthermore, arguments can be mandatory or optional.

Note

The support for dangling arguments must be enabled explicitly by adding a boolean flag to the call to the program argument initialization function pa_init. Since it turned out that usually applications have no dangling arguments, this feature is deactivated by default in order to avoid that erroneously given program arguments are not detected because they are interpreted as dangling arguments.

Defining allowed program arguments

Program arguments are defined by using the pa_init function. Usually, this function must not be called explicitly because it is called indirectly by the ICLApplication (or short: ICLApp) constructor. Actually the parameters are simply passed to pa_init if an ICLApp is used.

pa_init always receives the main function’s program argument and count and a single definition string:

int main(int n, char **args){
  pa_init(n,args,definition string);
  ..
}

The definition string consists of space separated tokens of single argument definitions. If real space characters are needed, they must be escaped using a trailing backslash character (please note, that one has to use a double backslash within a c++-string to create a backslash character). Each single argument definition token consists of a pipe-symbol separated list of argument name alternatives, followed by an optional parameter list in round braces, e.g.:

pa_init(n,args,"-input|-i(filename) -size(outputsize)");

Each defined argument may have 0, n, or an arbitrary number of sub-arguments. If the argument has no arguments – in this case the empty brace can also be left out – it becomes a flag, that is internally represented by a boolean value that defines whether it was given (then the value is true) or not (the value is false). An arbitrary sub-argument count can be achieved by using the intuitive string “…” as parameter list:

pa_init(n,ppc,"-enable-preprocessing|-pp -input|-i(...) -output(filename)");

In general, each parameter-list is a comma separated list of tokens that can be a type name, a type name followed by “=” and a default value or an integer token, or the special token “…”. Here are some examples:

two integer sub-arguments:

"(int,int)"

five sub-arguments with unspecified types:

"(5)"

four sub-arguments, first of type float, second and third of any type and last of type int:

"(float,2,int)"

two sub-arguments first of type int with default value “4” and second of type Size with default argument “VGA”:

"(int=4,Size=VGA)"

Note

  1. in case of using just a number of sub-arguments, no default value can be given

  2. It is either possible to provide default values for all arguments or for non of them. So far there is no C++-like mechanism for this.

Types

Question:

What types are allowed/supported?

Answer:

All types!

Actually, this sounds more advanced than it actually is. Internally, all sub-arguments are managed as strings. The sub-argument is intended as a type-hint for the program user, and it is used for the generation the program usage output, that is created automatically, if the program is called with a -help argument, or if the given set of program arguments int not compatible with the defined set. Therefore, we strongly recommend to use explicit type names such as “filename” or “format-string” rather than just an integer parameter count.

Mandatory Arguments

Some of your program arguments might be mandatory for you application. If, e.g., no image source is defined, your application cannot work properly. If you access program arguments or sub-arguments, that have no default value and are not given, an exception is thrown. Since in this case, the retrieved error message could be difficult to analyze, certain arguments can be set up to be mandatory. This is done by adding an “[m]”-token (for mandatory) in front of the program argument definition token:

pa_init(n,ppc,"-size(Size=VGA) -pp [m]-input|-i(2)");

Here, the application will not start unless the “-input” argument is given with 2 sub-argument. Those arguments that have default values, cannot be set to mandatory because the default value would never be used in this case. And logically, also flags – arguments without sub-arguments – cannot be set to mandatory.

Accessing Program Arguments

Given program arguments can be retrieved using the pa function that returns an instance of ProgArg, however ProgArg instances is most of the time not used explicitly. Instead, the ProgArg type provides many useful operators that allows for using it in a very convenient manner. The following source code shows some usage examples:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <ICLQt/Common.h>

int main(int n, char **args){
  bool allowDanglingArgs = true;
  pa_init(n,args,"-size|-s(Size=VGA) -index(int) -input|-i(2) -files(...)",
          allowDanglingArgs);

  // extract the first sub-argument of argument
  // '-index' and convert it into an int value
  int i = pa("-index");

  // extract the first sub-argument of argument '-size'
  // alternative names can be used as synonyms
  Size s = pa("-s");

  // extract the 2nd sub-argument of argument '-input'
  std::string text = pa("-input",1);

  // check if argument -size was actually given
  if(pa("-size")){

    // extract the sub-argument as a special type
    // (for the string to type logic, C++'s
    // istream-operator is used!!)
    Size size = pa("-s");
  }

  // read a list of files from the arbitrary sub-argument
  // here, we explicitly instantiate a ProgArg
  ProgArg a = pa("-files");
  for(int i=0;i<a.n();++i){
    std::cout << "file " << i << " " << a[i] << std::endl;
  }

  // list all given arguments and sub-arguments
  std::cout << "all arguments " << std::endl;
  for(unsigned int i=0;i<pa_get_count(false);++i){
    std::cout << pa(i,false) << std::endl;
  }

  // in case of having dangling arguments allowed in
  // pa_init-call: list all dangling arguments
  std::cout << "all dangling arguments " << std::endl;
  for(unsigned int i=0;i<pa_get_count();++i){
    std::cout << pa(i) << std::endl;
  }

  // using ProgArg instances as std::strings is sometimes
  // a bit complicated since the conversion to an
  // std::string is sometimes ambiguous
  struct Test{
    Test(int i){}
    Test(const std::string &s){}
  };

  // Test t(pa("-x")); // ambiguous -> int | std::string

  // Test t((std::string)pa("-x")); // also ambiguous
  // due to different available std::string constructors

  Test t(pa("-size").as<std::string>()); // works, but long code

  Test t2(*pa("-s")); // much shorter, but only for using
  // a program argument as std::string

  // Note:: *pa("-s") will always return the argument as string

}

Calling Programs with given Arguments

Let’s call the resulting program example. Obviously, the program accesses all program arguments, so calling:

./example

fails, giving the error message in line 10 that sub-argument 0 of the defined argument “-input” was tried to access, but is was neither given nor defined by a default argument. In real-life examples, those defined arguments should be declared mandatory.

First of all, let us try to get a list of the allowed program arguments. The framework always provides the program arguments -help and -version. In turn, these cannot be used for own purposes. -help gives a formatted usage output, in this case:

usage:
      example [ARGS]
-size|-s      {optional}
              (Size=VGA)

-index        {optional}
              (int)

-input|-i     {optional}
              (*,*)

-files        {optional}
              (...) [arbitrary subargument count possible]

-help         shows this help text
-version      shows version and copyright
              information

-version provides current version and license information. If an application has some special license, the license text can be set using the pa_set_license function. Optionally the usage information can be explained using an extra help text that can be set using the pa_set_help_text function. A valid (and quite complex) program call could look like:

./example a b c -size SVGA d e -index 7 -i dc 0 -files *.cpp

Here, the tokens “a”, “b”, “c”, “d” and “e” are dangling arguments, because they are neither defined nor associated with sub-arguments to other defined ones. The defined argument “-size” uses “SVGA” as sub-argument, “-index” uses “7” as sub-argument and “-i” gets two sub-arguments “dc” and “0”. Finally “-files” gets a list of sub-arguments expanded by the shell (if there are any .cpp-files in the current directory).

Adding more Descriptions

In some cases, some or all given program arguments need an extra description. This can be given using the pa_explain function.

Note

It is very important that pa_explain is called before pa_init is invoked. Later calls to pa_explain have no effect.

Calling the program

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <ICLQt/Common.h>

int main(int n, char **args){
  pa_set_help_text("This is an example program that has no function\n"
                   "but it demonstrates how to add some program\n"
                   "argument and program description");
  pa_explain("-s","image size use for something");
  pa_explain("-index","some index used for something else");
  pa_explain("-input","input definition, the first first arg defines\n"
                      "the input grabber backend, second arg selects\n"
                      "a certain device from this backend");

  pa_init(n,args,"-size|-s(Size=VGA) -index(int) -input|-i(2)");

}

With:

./example-2 --help

Shows the following usage output:

usage:
      pa2 [ARGS]

      This is an example program that has no function
      but it demonstrates how to add some program
      argument and program description

-size|-s      {optional}
              (Size=VGA)
              image size use for something

-index        {optional}
              (int)
              some index used for something else

-input|-i     {optional}
              (*,*)
              input definition, the first first arg defines
              the input grabber backend, second arg selects
              a certain device from this backend

-help         shows this help text
-version      shows version and copyright
              information