2.10 Ordered Option Parsing

Sometimes it is useful to know where options appear on the command line. You can tell in which order options (and suboptions) appear by taking advantage of the fact that callbacks (see Option Callbacks) are called in the same order that the corresponding options appear on the command line. However, if you want to determine the ordering of non-option positional arguments as well as options, you must instead use an argument callback, or use the MU_OPT_CONTINUE flag (see Option Parsing Flags).

To use an argument callback, you must use the mu_opt_context_set_arg_callback function.

Function: void mu_opt_context_set_arg_callback (MU_OPT_CONTEXT *context, int callback (const char *arg, void *data, char *err), void *data, int destructor (void *data))

This function sets an argument callback in context. context must not have been created with the MU_OPT_CONTINUE flag (see Option Parsing Flags), and it must never have been passed to mu_parse_opts (see Parsing Options and Environment).

callback will be called for each positional argument found when mu_parse_opts is called. callback may not be NULL. data will be passed to callback as the data argument. When context is destroyed using mu_opt_context_free, destructor will be called with data passed as its data argument.

callback should indicate success by returning zero. If callback fails, it should return nonzero and copy an error string to err (not exceeding MU_OPT_ERR_MAX). See callback error indication for more information.

Note that if either of the flags MU_OPT_PERMUTE or MU_OPT_STOP_AT_ARG are used when the option parsing context is created (see Option Parsing Flags), then the successful return value of mu_parse_opts will not include the positional arguments parsed (see Parsing Options and Environment). This is so that, after shifting the arguments by the return value of mu_parse_opts with mu_shift_args, the remaining arguments will be the positional arguments.

Normally, however, when using an argument callback, you shouldn’t need the return value of mu_parse_opts except to check for errors.

If neither of the flags MU_OPT_PERMUTE nor MU_OPT_STOP_AT_ARG are given, then the return value of mu_parse_opts will include the positional arguments (i.e., a successful return from mu_parse_opts will always return the total number of arguments, options or otherwise). This is because, if neither MU_OPT_PERMUTE nor MU_OPT_STOP_AT_ARG are given, it cannot be guaranteed that all positional arguments will appear after all options. Thus, the return value of mu_parse_opts should not be used to shift the arguments, and should only be used to check for errors.

Here is an example of how to use argument callbacks:

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

/* Callbacks to print a message when we find an option or argument. */

static int print_example(void *data, char *err) {
  puts("Option found: example");
  return 0;
}

static int print_another(void *data, char *err) {
  puts("Option found: another");
  return 0;
}

static int print_argument(const char *arg, void *data, char *err) {
  printf("Argument found: %s\n", arg);
  return 0;
}

int main(int argc, char **argv) {
  const MU_OPT options[] = {
    {
     .short_opt     = "e",
     .long_opt      = "example",
     .has_arg       = MU_OPT_NONE,
     .callback_none = print_example
    },
    {
     .short_opt     = "a",
     .long_opt      = "another",
     .has_arg       = MU_OPT_NONE,
     .callback_none = print_another
    },
    { 0 }
  };
  MU_OPT_CONTEXT *context;
  int ret;

  context = mu_opt_context_xnew(argc, argv, options, 0);
  mu_opt_context_set_arg_callback(context, print_argument,
                                  NULL, NULL);
  ret = mu_parse_opts(context);
  mu_opt_context_xfree(context);
  return MU_OPT_ERR(ret);
}

Here is the output of the example program:

$ ./option-ordered-callback foo -e bar --another baz
-| Argument found: foo
-| Option found: example
-| Argument found: bar
-| Option found: another
-| Argument found: baz
$ ./option-ordered-callback -a foo bar --example
-| Option found: another
-| Argument found: foo
-| Argument found: bar
-| Option found: example

Alternatively, you can also determine the order in which options and positional arguments appear using the MU_OPT_CONTINUE flag. If you use this flag, you should not use the MU_OPT_PERMUTE flag (otherwise, all options will be parsed at once and the MU_OPT_CONTINUE flag is rendered useless). The MU_OPT_STOP_AT_ARG is also useless if you use MU_OPT_CONTINUE, because if you use MU_OPT_STOP_AT_ARG, you might as well just parse the options once and then parse the rest of the arguments, which will only be positional arguments.

Using the MU_OPT_CONTINUE flag, you should parse options (maybe using callbacks if you care about the order of the options themselves), then parse positional arguments, and then options again until all arguments are used up. Note, however, that normally you must not call mu_parse_opts more than once, unless you pass the MU_OPT_CONTINUE flag

All the environment variables will be parsed on the first call of mu_parse_opts. They will not be parsed again in subsequent calls. See Parsing the Environment for more information.

After you parse each non-option argument, you must call mu_opt_context_shift on the option context in order to ensure that mu_parse_opts will not stop at the argument you just parsed.

Function: int mu_opt_context_shift (MU_OPT_CONTEXT *context, int amount)

Update the internal index of context by amount. amount may be negative.

Normally, this function returns zero. However, in the case that the new index would be less that 1, the new index will instead be set to 1 and the amount that could not be shifted will be returned. And in the case that the new index would be greater than or equal to the number of arguments in context, the new index will instead be set to the number of arguments minus one, and again, the amount that could not be shifted is returned.

Here is an example illustrating how to parse options and positional arguments while preserving the order, without using argument callbacks:

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

/* Callbacks to print a message when we find an option. */

static int print_example(void *data, char *err) {
  puts("Option found: example");
  return 0;
}

static int print_another(void *data, char *err) {
  puts("Option found: another");
  return 0;
}

int main(int argc, char **argv) {
  const MU_OPT options[] = {
    {
     .short_opt     = "e",
     .long_opt      = "example",
     .has_arg       = MU_OPT_NONE,
     .callback_none = print_example
    },
    {
     .short_opt     = "a",
     .long_opt      = "another",
     .has_arg       = MU_OPT_NONE,
     .callback_none = print_another
    },
    { 0 }
  };
  MU_OPT_CONTEXT *context;

  context = mu_opt_context_xnew(argc, argv, options, MU_OPT_CONTINUE);
  while (argc > 1) {
    int ret;

    /* Parse options. */
    ret = mu_parse_opts(context);
    if (MU_OPT_ERR(ret))
      return 1;

    /* Shift the arguments (to get rid of the options we just
       parsed). */
    mu_shift_args(&argc, &argv, ret);

    if (argc > 1) {
      /* Print an argument (we don't have to print them all at once
         because if `mu_parse_opts' doesn't find any options, it will
         just return 0). */
      printf("Argument found: %s\n", argv[1]);
      /* Shift away this argument. */
      mu_shift_args(&argc, &argv, 1);
      mu_opt_context_shift(context, 1);
    }
  }
  mu_opt_context_xfree(context);

  return 0;
}

The behavior of the above program is identical to the one using argument callbacks (see argument callback example).