2.6 Parsing Enumerated Arguments to Options

It is often useful to have an option which takes an argument which is a string that is restricted to a set of values. For example, GNU ls (and many other programs) take a --color option, which has an argument that can be ‘always’, ‘auto’, or ‘never’ (in addition to various synonyms). Mu supports similar argument parsing, called enumerated arguments.

Names and values for enumerated types are specified in the enum_values field of the MU_OPT structure (see Option Structure). enum_values is an array of MU_ENUM_VALUE structures as defined below. enum_values is terminated by an element with a name field of NULL.

If the enum_case_match field of the MU_OPT structure is nonzero (see Option Structure), matching is case sensitive. Otherwise, matching is case insensitive.

Like long options and suboptions, abbreviation is allowed when passing enumerated arguments, as long as it is not ambiguous.

Data Type: MU_ENUM_VALUE

Unlike MU_OPT (see Option Structure), this structure is simple and it’s organization is guaranteed. Therefore, you may use positional initializers to initialize this structure. Of course, you can still use designated initializers if you prefer.

const char *name

This field specifies the name to match against when parsing the argument. Like long options, suboptions, and environment variables, name may have aliases separated by ‘|’ (see Aliases for Options and Environment Variables). Alternatively, aliases can be specified by using separate entries with the same value (see below).

Duplicates in this field, either duplicate aliases or duplicates between entries, are not allowed. Note that if enum_case_match is zero, case is not considered. So, if enum_case_match is zero, you cannot have two entries, ‘foo’ and ‘FOO’, nor can you have two aliases specified as ‘foo|FOO’.

int value

This is the value of the enumeration. It is the value passed as the arg parameter of callback_enum (see Option Callbacks), and, if the arg field of the MU_OPT structure is not NULL, it is the value stored in *arg.

The following example illustrates how to use both case insensitive enumerated argument parsing, and case sensitive enumerated argument parsing:

#include <stdio.h>
#include <mu/options.h>
#include <mu/safe.h>            /* For mu_opt_context_x{new,free} */

enum selection { FOO, BAR, BAZ };

int main(int argc, char **argv) {
  enum selection sel;
  int found_sel;
  int ret;
  const MU_ENUM_VALUE enum_table[] = {
    /* Aliases can be specified for enumerated arguments. */
    { "foo|alias-foo", FOO },
    { "bar", BAR },
    /* Aliases can alternatively be specified like this. */
    { "alias-bar", BAR },
    { "baz", BAZ },
    /* This terminates the enumeration specification. */
    { 0 }
  };
  const MU_OPT options[] = {
    {
     .short_opt       = "s",
     .long_opt        = "selection",
     .has_arg         = MU_OPT_REQUIRED,
     .arg_type        = MU_OPT_ENUM,
     /* This indicates that matching should be case insensitive. */
     .enum_case_match = 0,
     .enum_values     = enum_table,
     .found_arg       = &found_sel,
     .arg             = &sel
    },
    {
     .short_opt       = "c",
     .long_opt        = "case-selection",
     .has_arg         = MU_OPT_REQUIRED,
     .arg_type        = MU_OPT_ENUM,
     /* This indicates that matching should be case sensitive. */
     .enum_case_match = 1,
     .enum_values     = enum_table,
     .found_arg       = &found_sel,
     .arg             = &sel
    },
    { 0 }
  };
  MU_OPT_CONTEXT *context;

  /* Parse the options. */
  context = mu_opt_context_xnew(argc, argv, options, MU_OPT_PERMUTE);
  ret = mu_parse_opts(context);
  mu_opt_context_xfree(context);
  if (MU_OPT_ERR(ret))
    return 1;

  if (found_sel) {
    /* Print the selection. */
    fputs("You selected: ", stdout);
    switch (sel) {
    case FOO:
      puts("FOO");
      break;
    case BAR:
      puts("BAR");
      break;
    case BAZ:
      puts("BAZ");
      break;
    default:
      /* This is guaranteed not to happen. */
      puts("an unknown value!");
    }
  }
  else
    puts("You didn't select anything.");

  return 0;
}

Here is the output of the above example, to show exactly how enumerated arguments are parsed:

$ ./option-enum --selection=foo
-| You selected: FOO
$ ./option-enum --selection=alias-foo
-| You selected: FOO
$ ./option-enum --selection=alias-bar
-| You selected: BAR
$ ./option-enum --selection=bAr
-| You selected: BAR
$ ./option-enum --selection=Ba
error→ ./option-enum: 'Ba': argument for '--selection' is ambiguous; possibilities:
error→   bar
error→   baz
$ ./option-enum --selection=qux
error→ ./option-enum: 'qux': invalid argument for '--selection'; must be one of 'foo', 'bar', 'alias-bar', or 'baz'
$ ./option-enum --case-selection=FoO
error→ ./option-enum: 'FoO': invalid argument for '--case-selection'; must be one of 'foo', 'bar', 'alias-bar', or 'baz'
$ ./option-enum --case-selection=foo
-| You selected: FOO