Argument Parsing with popt

Gnome uses a powerful option-parsing library called popt. popt handles all the default Gnome options---to see the default options, pass the --help option to any Gnome application. You can add a "popt table" with your custom options. To do so, replace gnome_init() with the gnome_init_with_popt_table() variant (Figure 3).

#include <libgnomeui/gnome-init.h>

int gnome_init_with_popt_table(const char* app_id, const char* app_version, int argc, char** argv, const struct poptOption* options, int flags, poptContext* return_ctx);

Figure 3. Init with Argument Parsing

A popt table is simply an array of struct poptOption, defined as follows:


struct poptOption {
  const char* longName;
  char shortName;         
  int argInfo;          
  void* arg;              
  int val;                
  char* descrip;         
  char* argDescrip;       
};

The first two components are the long and short names for the option; for example, "help" and 'h' would correspond to command-line options --help and -h. These can be NULL and '\0' respectively, if you want only one name for the option.

The arginfo member tells what kind of table entry this is. Here are the possible values:

The meaning of arg depends on the arginfo member. For options that take an argument, arg should point to a variable of the argument type. popt will fill the pointed-to variable with the argument. For POPT_ARG_NONE, *arg is set to TRUE if the option is found on the command line. In all cases, arg may be NULL, causing popt to ignore it.

For POPT_ARG_INCLUDE_TABLE, arg points to the table to include; for POPT_ARG_CALLBACK, it points to the callback to invoke; for POPT_ARG_INTL_DOMAIN it should be the translation domain string.

The val member serves as an identifier for each option. Typically it isn't that useful in Gnome applications, but if you use a callback it will be available in the callback. If you aren't going to use it, set it to 0.

The final two members are used to automatically generate output for the --help option. descrip describes an option; argDescrip describes the argument to that option, if applicable. For example, the help for the --display option looks like this:


  --display=DISPLAY                X display to use

Here argDescrip is "DISPLAY" and descrip is "X display to use." Remember to mark these two strings for translation.

descrip has a slightly different meaning for POPT_ARG_INCLUDE_TABLE; in this case it titles a "group" of options in the help output. For example, "Help options" in the following output:


Help options
  -?, --help                   Show this help message
  --usage                      Display brief usage message

If you place an entry of type POPT_ARG_CALLBACK at the beginning of a popt table, a user-defined callback will be invoked with information about each option found on the command line. Here is the type your callback is expected to have:


typedef void (*poptCallbackType)(poptContext con, 
                                 enum poptCallbackReason reason,        
                                 const struct poptOption* opt, 
                                 const char* arg, 
                                 void* data);

The opaque poptContext object contains all of popt's state. This makes it possible to use popt more than once in the same program, or parse more than one set of options simultaneously. You can also extract information about the current parsing state from the poptContext, using functions provided by popt.

Possible poptCallbackReason values are:

Your callback is called once for each option found on the command line with POPT_CALLBACK_REASON_OPTION as the reason argument. If you request, it can also be called before and after argument parsing. In these cases reason will be POPT_CALLBACK_REASON_PRE or POPT_CALLBACK_REASON_POST. To specify that you want your callback to be called before or after parsing, you have to combine a pair of flags with POPT_ARG_CALLBACK. For example, the following struct poptOption initializer specifies a callback to be invoked both before and after argument parsing:


{ NULL, '\0', POPT_ARG_CALLBACK|POPT_CBFLAG_PRE|POPT_CBFLAG_POST,
  &parse_an_arg_callback, 0, NULL}

The opt argument to the callback is the struct poptOption corresponding to the most recently-seen command line option (you can access the val member of this struct to determine which option you are looking at). The arg argument is the text of any argument passed to the command line option; the data argument is the callback data, given as the descrip member of the struct poptOption which specified the callback.

The flags argument to gnome_init_with_popt_table() can basically be ignored in a Gnome context; the available flags are not very useful.

If you pass a non-NULL pointer for return_ctx, the final argument to gnome_init_with_popt_table(), the current context will be returned; you can use this to extract the non-option components of the command line, such as filenames. This is done with the function poptGetArgs()---here's an example:


  char** args;
  poptContext ctx;
  int i;

  bindtextdomain (PACKAGE, GNOMELOCALEDIR);
  textdomain (PACKAGE);

  gnome_init_with_popt_table(APPNAME, VERSION, argc, argv, 
                             options, 0, &ctx);

  args = poptGetArgs(ctx);

  if (args != NULL)
    {
      i = 0;
      while (args[i] != NULL) 
        {
          /* Do something with each argument */
          ++i;
        }
    }

  poptFreeContext(ctx);

Notice that you must free the poptContext if you ask for it; however, if you pass NULL for return_ctx the library will free it for you. Also keep in mind that poptGetArgs() will return NULL if there are no arguments on the command line.

Argument Parsing in GnomeHello

The GnomeHello application outputs the following if you invoke it with the --help option:


$ ./hello --help
Usage: hello [OPTION...]

GNOME Options
  --disable-sound             Disable sound server usage
  --enable-sound              Enable sound server usage
  --espeaker=HOSTNAME:PORT    Host:port on which the sound server to use is
                              running

Help options
  -?, --help                  Show this help message
  --usage                     Display brief usage message

GTK options
  --gdk-debug=FLAGS           Gdk debugging flags to set
  --gdk-no-debug=FLAGS        Gdk debugging flags to unset
  --display=DISPLAY           X display to use
  --sync                      Make X calls synchronous
  --no-xshm                   Don't use X shared memory extension
  --name=NAME                 Program name as used by the window manager
  --class=CLASS               Program class as used by the window manager
  --gxid_host=HOST
  --gxid_port=PORT
  --xim-preedit=STYLE
  --xim-status=STYLE
  --gtk-debug=FLAGS           Gtk+ debugging flags to set
  --gtk-no-debug=FLAGS        Gtk+ debugging flags to unset
  --g-fatal-warnings          Make all warnings fatal
  --gtk-module=MODULE         Load an additional Gtk module

GNOME GUI options
  -V, --version

Help options
  -?, --help                  Show this help message
  --usage                     Display brief usage message

Session management options
  --sm-client-id=ID           Specify session management ID
  --sm-config-prefix=PREFIX   Specify prefix of saved configuration
  --sm-disable                Disable connection to session manager

GnomeHello options
  -g, --greet                 Say hello to specific people listed on the
                              command line
  -m, --message=MESSAGE       Specify a message other than "Hello, World!"
  --geometry=GEOMETRY         Specify the geometry of the main window
$ 

Almost all of these options are common to all Gnome applications; only the last three, labelled "GnomeHello options," are specific to GnomeHello. The --greet or -g option turns on "greet mode"; GnomeHello will expect a list of names on the command line, and create a dialog to say hello to each person named. The --message option expects a string argument which replaces the usual "Hello, World!" message; the --geometry option expects a standard X geometry string, specifying the position and size of the main application window.

Here are the variables and popt table GnomeHello uses to do its argument parsing:


static int greet_mode = FALSE;
static char* message  = NULL;
static char* geometry = NULL;

struct poptOption options[] = {
  {
    "greet",
    'g',
    POPT_ARG_NONE,
    &greet_mode,
    0,
    N_("Say hello to specific people listed on the command line"),
    NULL
  },
  { 
    "message",
    'm',
    POPT_ARG_STRING,
    &message,
    0,
    N_("Specify a message other than \"Hello, World!\""),
    N_("MESSAGE")
  },
  { 
    "geometry",
    '\0',
    POPT_ARG_STRING,
    &geometry,
    0,
    N_("Specify the geometry of the main window"),
    N_("GEOMETRY")
  },
  {
    NULL,
    '\0',
    0,
    NULL,
    0,
    NULL,
    NULL
  }
};


And here's the first part of main(), where GnomeHello checks that the arguments are properly combined and assembles a list of people to greet:


  GtkWidget* app;
  
  poptContext pctx;

  char** args;
  int i;

  GSList* greet = NULL;

  GnomeClient* client;

  bindtextdomain(PACKAGE, GNOMELOCALEDIR);  
  textdomain(PACKAGE);

  gnome_init_with_popt_table(PACKAGE, VERSION, argc, argv, 
                             options, 0, &pctx);  

  /* Argument parsing */

  args = poptGetArgs(pctx);

  if (greet_mode && args)
    {
      i = 0;
      while (args[i] != NULL) 
        {
          greet = g_slist_prepend(greet, args[i]);
          ++i;
        }
      /* Put them in order */
      greet = g_slist_reverse(greet); 
    }
  else if (greet_mode && args == NULL)
    {
      g_error(_("You must specify someone to greet."));
    }
  else if (args != NULL)
    {
      g_error(_("Command line arguments are only allowed with --greet."));
    }
  else
    { 
      g_assert(!greet_mode && args == NULL);
    }

  poptFreeContext(pctx);


Again, complete source for GnomeHello is included in Appendix E.