Option callbacks are useful when you have more advanced option parsing
needs. Each option argument type has a different callback. There are
also callbacks for options which don’t take arguments:
callback_none
for non-negatable options, and
callback_negatable
for negatable options (see Negatable Options). All callback names mentioned are members of the MU_OPT
structure.
For a callback that is called when a positional argument is seen,
use mu_opt_context_set_arg_callback
(see Ordered Option Parsing).
The callback names and prototypes for each argument type are listed
below (although MU_OPT_NONE
is not a type, and should be passed
in the has_arg
field of the MU_OPT
structure, not the
arg_type
field):
MU_OPT_NONE
If the negatable
field is zero (see Option Structure):
int (*callback_none) (void *data, char *err)
Otherwise, if negatable
is nonzero:
int (*callback_negatable) (int value, void *data, char *err)
MU_OPT_BOOL
int (*callback_bool) (int has_arg, int arg, void *data, char *err)
MU_OPT_INT
int (*callback_int) (int has_arg, long arg, void *data, char *err)
MU_OPT_FLOAT
int (*callback_float) (int has_arg, double arg, void *data, char *err)
MU_OPT_STRING
int (*callback_string) (int has_arg, const char *arg, void *data, char *err)
MU_OPT_FILE
int (*callback_file) (int has_arg, const char *filename, FILE *file, void *data, char *err)
MU_OPT_DIRECTORY
int (*callback_directory) (int has_arg, const char *dirname, DIR *directory, void *data, char *err)
MU_OPT_ENUM
int (*callback_enum) (int has_arg, int arg, void *data, char *err)
MU_OPT_SUBOPT
int (*callback_subopt) (int has_arg, void *data, char *err)
A callback will be called as soon as an option is found, so callbacks
are guaranteed to be called in the same order as the options appear on
the command line. This means that if an option takes suboptions as
arguments, the callback for the main option will be called before the
callbacks for the suboptions (see Parsing Suboptions). The
has_arg parameter will be passed as 1
if the option has an
argument, or 0
if the option doesn’t have an argument (except for
callback_none
which doesn’t have a has_arg parameter).
If the option has an argument, arg will be set to that argument,
except in the case of suboptions (see Parsing Suboptions). In the
case of callaback_file
and callback_directory
,
filename or dirname will be set to the name of the file or
directory respectively.4 For
callback_negatable
, value will be zero if the option was
negated, or nonzero if it wasn’t (see Negatable Options).
If you need to provide extra information to a callback, provide it in
the cb_data
field of the MU_OPT
structure. This will then
be passed as the data parameter to a callback. Note: a callback
should not free this parameter even if it is dynamically
allocated. In the case that cb_data
is dynamically allocated
and/or contains dynamically allocated data, you should also set the
cb_data_destructor
field to a function which will free all
dynamically allocated data in cb_data
.
When you call mu_opt_context_free
(see Parsing Options and Environment) or mu_subopt_context_free
(see Parsing Suboptions), each cb_data_destructor
field is called with the
corresponding cb_data
in order to free that data. If an error
occurs while freeing callback data (for example, an error closing a
file), cb_data_destructor
should return nonzero. Otherwise,
cb_data_destructor
should return zero.
For callback_file
and callback_directory
, the file
or directory argument will be closed after the callback
returns if you leave the arg
field of the MU_OPT
structure
as NULL
(see Option Structure). So you must not close the
file or directory argument in the callback.
You also must not use the cb_data
field to get the
opened file/directory. For example, the following code is wrong:
#include <stdio.h> #include <string.h> #include <errno.h> #include <mu/options.h> #include <mu/safe.h> /* For mu_opt_context_x{new,free} */ int file_callback(int has_arg, const char *filename, FILE *file, void *data, char *err) { /* Make sure the file is named "foo". */ if (strcmp(filename, "foo")) { snprintf(err, MU_OPT_ERR_MAX, "file is not named \"foo\""); return 1; } /* It is named "foo"; return `file' in `*data'. This is WRONG! Do not do this! */ *(FILE **)data = file; return 0; } int main(int argc, char **argv) { FILE *file = NULL; char buf[256]; size_t size; int ret; const MU_OPT options[] = { { .short_opt = "f", .long_opt = "file", .has_arg = MU_OPT_REQUIRED, .arg_type = MU_OPT_FILE, .file_mode = "r", .callback_file = file_callback, .cb_data = &file }, { 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 (!file) { /* We weren't passed the `-f' option. */ return 0; } /* Read the file. This invokes UNDEFINED BEHAVIOR because `file' was already closed by `mu_parse_opts'! */ size = fread(buf, sizeof(*buf), sizeof(buf), file); if (ferror(file)) { fprintf(stderr, "%s: cannot read foo: %s\n", argv[0], strerror(errno)); return 1; } fclose(file); /* Print the contents of the file to standard output. */ fwrite(buf, sizeof(*buf), size, stdout); return 0; }
When this program is run, it invokes undefined behavior. The correct way
to do this is to not use the cb_data
field, and instead
set the arg
field to &file
. This way, mu_parse_opts
will not close the file or directory after the callback returns.
If a callback needs to indicate an error (if its argument is in the
wrong format, for example), it should return nonzero. Otherwise, on
success, it should return 0
. If a callback returns nonzero, you
must write an error string to err which will then be used by
mu_parse_opts
to print an error message. You must not write more
than MU_OPT_ERR_MAX
characters to err (including the
terminating null byte). However, if you write exactly
MU_OPT_ERR_MAX
bytes to err, you need not terminate
err with a null byte.
Below is an example of how to use callbacks. Of course, this trivial example would be better expressed using enumerated argument parsing (see Parsing Enumerated Arguments to Options).
#include <stdio.h> #include <string.h> #include <mu/options.h> #include <mu/safe.h> /* For mu_opt_context_x{new,free} */ enum selection { FOO, BAR, BAZ }; /* Parse a selection. `has_arg' will always be true because the option takes a required argument. */ int parse_selection(int has_arg, const char *arg, void *data, char *err) { enum selection sel; if (!strcmp(arg, "foo")) sel = FOO; else if (!strcmp(arg, "bar")) sel = BAR; else if (!strcmp(arg, "baz")) sel = BAZ; else { /* `err' will be used by `mu_parse_opts' to print an error message. */ snprintf(err, MU_OPT_ERR_MAX, "invalid selection: %s", arg); /* Indicate to `mu_parse_opts' that an error occured by returning a nonzero value. */ return 1; } /* Store the selection in `*data'. */ *(enum selection *)data = sel; /* Success! */ return 0; } int main(int argc, char **argv) { enum selection sel; int found_sel; int ret; const MU_OPT options[] = { { .short_opt = "s", .long_opt = "selection", .has_arg = MU_OPT_REQUIRED, .arg_type = MU_OPT_STRING, .found_arg = &found_sel, .callback_string = parse_selection, .cb_data = &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; /* `mu_parse_opts' will print an error message for us */ 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: puts("an unknown value!"); /* This should not happen */ } } else puts("You didn't select anything."); return 0; }
Here is what the output of the example program looks like:
$ ./option-callback -| You didn't select anything. $ ./option-callback -s foo -| You selected: FOO $ ./option-callback --selection=bar -| You selected: BAR $ ./option-callback -s qux error→ ./option-callback: invalid selection: qux $ ./option-callback -s error→ ./option-callback: '-s': option requires argument
However, for callback_file
,
filename might be ‘<stdin>’ or ‘<stdout>’ when
file is standard input or standard output
respectively. See Option Argument Types.