Home   Intro   Tutorial   argtable.h   arg_rec   Globals   Release Notes  

The Argtable Tutorial

Imagine we have written a program called myprog and we wish to implement the following command line usage syntax for it:
myprog [-tit <title>] grad:gradient [y-int]
   -tit <title>    your title
   grad:gradient   line gradient
   y-int           y-intercept

We will create a single argument table in our program that defines the argument properties, and pass that table along with argc and argv[] to the arg_scanargv() function to do the parsing.

Defining the argument table.

An argument table is just an array of arg_rec structs, with each array element pertaining to a single command line argument. The arg_rec struct is defined in argtable.h as:
typedef enum {arg_int=0,arg_dbl,arg_str,arg_bool,arg_lit} arg_type;
typedef struct
   {
   const char *tagstr;       // argument tag string
   const char *argname;      // argument name string
   arg_type    argtype;      // argument data type
   void       *valueptr;     // ptr to user storage location
   const char *defaultstr;   // default value, as a string
   const char *argdescrip;   // argument description string
   } arg_rec;

Thus we may define our argument table statically in the code as follows:

int main(int argc, char **argv)
   {
   static char str[50];
   static double grad;
   static int c;
   arg_rec argtable[] =
      {
      {"-tit ", "<title>",  arg_str, str,   "noname", "\t\t your title"},
      {"grad:", "gradient", arg_dbl, &grad, NULL,     "\t line gradient"},
      {NULL,    "y-int",    arg_int, &c,    "0",      "\t\t y-intercept"}
      };
   const size_t narg = sizeof(argtable)/sizeof(arg_rec);
   ...
  }

Defining the tables statically is a programming convenience but not a requirement; the table could equally well have been dynamically allocated and initialized at runtime. Notice that I also chose to define the argument table within the main() block because that's the only place where it is used so there is no need to promote it to a higher namespace. However you may define it in the global namespace if you prefer.

Our argument table has three rows, one for each command line argument -tit <title>, grad:gradient, and y-int. The behaviour of the argument parsing is governed entirely by the contents of the various fields (columns) of the argument table. Lets step through each field one by one.

The tag string:
The first field is the argument's tag string. It defines the prefix literal that identifies a tagged argument and should contain at least one non-whitespace character unless the argument is untagged whereupon the field should be NULL. In our example, -tit <title> and grad:gradient are tagged arguments but y-int is untagged so it has a NULL tag string.
The name string:
The second field is the argument's name string. It is not actually used to process the command line arguments, rather it defines the name of the argument as it appears in the description strings generated by the arg_syntax and arg_glossary functions. Those functions automatically substitute any NULL names with the argument's data type enclosed in angled brackets, as in "<int>" or "<string>".
The data type:
The third field is an enumerated type that defines the data type of the command line argument. Possible values are arg_int, arg_dbl, arg_str, arg_bool, and arg_lit. They represent integer, double, string, boolean, and literal arguments respectively. In our example -tit <title> expects <title> to be substituted by a string value, grad:gradient expects gradient to be a double, and y-int is expected to be an integer.
The data pointer:
The fourth field is a pointer-to-void that gives the address of the user-defined program variable used to store the argument's value. A NULL pointer here causes the value to be discarded once is has been scanned. Take care that the data type of of the target memory location matches that specified in the previous column. Arguments of type arg_int, arg_bool, and arg_lit must each point to an integer variable. Those of type arg_dbl must point to a double and those of arg_str must point to a char array. In our example, the string value associated with -tit <title> is written into the char str[50] buffer, the double value associated with grad:gradient is written into double grad, and the integer value associated with y-int is written into int c.
The default value:
The fifth field is a string which contains an optional default value for the argument. It is defined as a string and automatically cast to the appropriate data type at run time. A NULL value indicates no default. In our example, -tit <title> and y-int have default values of "noname" and zero respectively, whereas grad:gradient has no default and is thus regarded as a mandatory argument.
The description string:
The sixth and final field allows the programmer to enter a brief description of the argument. It is these descriptions that are output by the arg_glossary() function. A NULL value causes that entry to be omitted from the glossary output.

Parsing the command line.

Having defined the argument table, we can now use it to parse the command line arguments in argv[]. There are several ways to do this, but the simplest is to use the arg_scanargv function.
int arg_scanargv(int argc,
                 char** argv,
                 arg_rec *argtable,
                 int n,
                 char* CmdLine,
                 char* ErrMsg,
                 char* ErrMark);

It takes as input the command line arguments in argv[] (there are argc of them) and a pointer to the argument table in argtable (which has n rows). It proceeds to scan the argv[] arguments (skipping argv[0]) and tries to resolve them with the entries given in the argument table. If this can be done successfully then all argument values are written into the program variables as designated by the argument table and the function returns 1. If not, the function returns 0 to indicate failure.

The three string pointers CmdLine, ErrMsg, and ErrMark refer to user defined string buffers in which arg_scanargv returns information about the parsing. They are optional parameters in the sense that they may be given as NULL if you do not wish to use them.

CmdLine is always assigned a copy of the orginal command line, concatenated into a single space-separated string.

ErrMsg and ErrMark are only used when arg_scanargv detects a parse error in the command line. In those cases, ErrMsg is assigned an explantory error message string, and ErrMark is assigned a string of tilde characters which have been formatted in such a way as to highlight the exact location of the error in CmdLine when printed one atop the other.

The code fragment below demonstrates the use of arg_scanargv. It presumes that argc, argv, argtable, and narg are as defined in the example above.

{
char cmdline[200], errmsg[200], errmark[200];

if (!arg_scanargv(argc,argv,argtable,narg,cmdline,errmsg,errmark))
   {
   // arg error occurred, print error message and exit
   printf("ERROR: %s\n", cmdline);
   printf("       %s %s\n", errmark, errmsg);
   return 1;
   }

// only get here if the arguments were scanned successfully

}

And here are some examples of the console output that this code produces when arg_scanargv detects a parse error.

$ myprog grad:oops
ERROR: myprog grad:oops
                   ^ invalid grad:gradient argument
$ myprog grad:13 nope
ERROR: myprog grad:13 nope
                      ^^^^ unexpected argument
$ myprog grad:13 99 uh oh
ERROR: myprog grad:13 99 uh oh
                         ^^ unexpected argument

Generating online help.

The arg_syntax() and arg_glossary() functions take an argument table and generate plain text descriptions of its command line syntax as well as descriptions of the individual arguments. These are useful for displaying help text to the user.

const char* arg_syntax(const arg_rec* argtable, int n);
const char* arg_glossary(const arg_rec* argtable, int n, const char* prefix);

The arg_syntax function returns a pointer to an internal string buffer that contains a plain text description of the usage syntax of the argument table it was passed. The string comprises a space separated list of the tag and name strings of each argument table entries concatenated into a single line string. Optional command line arguments are automatically enclosed in square brackets. Calling arg_syntax on our example argument table would return the string:

[-tit <title>] grad:gradient [y-int]

The calling program would ordinarily prepend the program name from argv[0] to this to get the full command line usage syntax.

The arg_glossary function is similar, except it generates a multi-line text string with one argument per line. Each line includes the argument's tag, its name string, and its description string as given in the argument table. Arguments that have a NULL description string are omitted. Each line of the glossary string is prefixed with the string given in the prefix parameter; it useful for indenting each line of the string. Calling arg_glossary with our example argument table results in the following multi-line string:

-tit \<title\> your title
grad:gradient line gradient
y-int y-intercept

Putting it all together.

Lets return to our example program and put it all together in its entirety. Our program, when executed without any arguments (argc==1), will print the argument usage syntax and a glossary on stdout, then exit. When given a valid set of arguments, it will display the resulting argument values as they are stored in the local program variables. Otherwise, it echoes the erroneous command line together with an appropriate error message and terminates with error code 1.

#include "argtable.h"

int main(int argc, char **argv)
   {
   static char str[50];
   static double grad;
   static int c;
   arg_rec argtable[] =
      {
      {"-tit ", "<title>",  arg_str, str,   "noname", "\t\t your title"},
      {"grad:", "gradient", arg_dbl, &grad, NULL,     "\t line gradient"},
      {NULL,    "y-int",    arg_int, &c,    "0",      "\t\t y-intercept"}
      };
   const size_t narg = sizeof(argtable)/sizeof(arg_rec);

   // process the command line args
   if (argc==1)
      {
      // display program usage and exit.
      printf("Usage: %s %s\n", argv[0], arg_syntax(argtable,narg));
      printf("%s\n",arg_glossary(argtable,narg,"  "));
      return 0;
      }
   else
      {
      // scan command line arguments from argv[]
      char cmdline[200], errmsg[200], errmark[200];
      if (!arg_scanargv(argc, argv, argtable, narg, cmdline, errmsg, errmark))
         {
         // arg error occurred, print error message and exit
         printf("ERROR: %s\n", cmdline);
         printf("       %s %s\n", errmark, errmsg);
         return 1;
         }
      }

   // get here only if command line args ok
   printf("title: \"%s\"\n",str);
   printf("gradient: %f\n",grad);
   printf("y-intercept: %d\n",c);

   return 0;
   }

Here are some results of running myprog with various command line arguments.

$ myprog
Usage: myprog [-tit <title>] grad:gradient [y-int]
  -tit <title>     your title
  grad:gradient    line gradient
  y-int            y-intercept

$ myprog grad:10
title: "noname"
gradient: 10.000000
y-intercept: 0

$ myprog 7 grad:1.234
title: "noname"
gradient: 1.234000
y-intercept: 7

$ myprog -tit "hello world" grad:3.33 11
title: "hello world"
gradient: 3.330000
y-intercept: 11


Argtable (http://argtable.sourceforge.net)