Libucw contains a parser of command-line options, more versatile and easier to use than the usual getopt() and getopt_long(). It follows the traditional UNIX conventions of option syntax, but the options are defined in a declarative way, similar in spirit to our configuration file parser.
Example
Let us start with a simple example: a program with several options and one mandatory positional argument.
#include <ucw/lib.h> #include <ucw/opt.h>
int english; int sugar; int verbose; char *tea_name;
static struct opt_section options = { OPT_ITEMS { OPT_HELP("A simple tea boiling console."), OPT_HELP("Usage: teapot [options] name-of-the-tea"), OPT_HELP(""), OPT_HELP("Options:"), OPT_HELP_OPTION, OPT_BOOL('e', "english-style", english, 0, "\tEnglish style (with milk)"), OPT_INT('s', "sugar", sugar, OPT_REQUIRED_VALUE, "<spoons>\tAmount of sugar (in teaspoons)"), OPT_INC('v', "verbose", verbose, 0, "\tVerbose (the more -v, the more verbose)"), OPT_STRING(OPT_POSITIONAL(1), NULL, tea_name, OPT_REQUIRED, ""), OPT_END } };
int main(int argc, char **argv) { opt_parse(&options, argv+1); return 0; }
Anatomy of options
Most options have the following properties:
-
Option class defining overall behavior of the option
-
Short name: one character. Set to 0 if the option has no short form. Alternatively, the short name can refer to a positional argument.
-
Long name: an arbitrary string. Set to NULL if the option has no long form.
-
Variable, where the value of the option shall be stored, together with its data type. The type is either one of the conventional types (int, uint, etc.), an extended type providing its own parser function via xtype, or an obsolete user-type defined by cf_user_type.
-
Flags further specifying behavior of the option (whether it is mandatory, whether it carries a value, whether it can be set repeatedly, etc.).
-
Help text, from which the help displayed to the user is constructed.
-
Extra data specific for the particular class.
The help is generated in a three-column format. The first column contains the short names, then come the long names, and finally option descriptions. The help text starts in column 2 (where it can describe the option’s argument); you can use the tab character to advance to the next column. When a newline character appears, the text continues on the next line in column 1.
ucw/opt.h
This header file contains the public interface of the option parser module.
Option classes
Each option belongs to one of the following classes, which define the overall behavior of the option. In most cases, the classes are set automatically by declaration macros.
-
OPT_CL_END: this is not a real option class, but a signal that the list of options ends.
-
OPT_CL_BOOL: a boolean option. If specified without an argument, it sets the corresponding variable to 1 (true). So does an argument of 1, y, yes, or true. Conversely, an argument of 0, n, no, or false sets the variable to 0 (false) and the same happens if the option is given as --no-option with no argument.
-
OPT_CL_STATIC: options of this class just take a value and store it in the variable.
-
OPT_CL_MULTIPLE: collect values from all occurrences of this option in a growing array (see gary.h).
-
OPT_CL_SWITCH: a multiple-choice switch, which sets the variable to a fixed value provided in option definition.
-
OPT_CL_INC: increments the variable (or decrements, if the OPT_NEGATIVE flag is set).
-
OPT_CL_CALL: instead of setting a variable, call a function and pass the value of the option to it.
-
OPT_CL_SECTION: not a real option, but an instruction to insert contents of another list of options.
-
OPT_CL_HELP: no option, just print a help text.
-
OPT_CL_HOOK: no option, but a definition of a hook.
-
OPT_CL_BREAK: when a given option occurs, stop parsing and keep the option in the argument list.
Option definitions
The list of options is represented by struct opt_section, which points to a sequence of `struct opt_item`s.
These structures are seldom used directly — instead, they are produced by declaration macros.
struct opt_section { const struct opt_item * opt; };
A section of option list.
struct opt_item { const char * name; // long name (NULL if none) int letter; // short name (0 if none) void * ptr; // variable to store the value to const char * help; // description in --help (NULL to omit the option from the help) union opt_union { const struct opt_section * section; // subsection for OPT_CL_SECTION int value; // value for OPT_CL_SWITCH void (* call)(const struct opt_item * opt, const char * value, void * data); // function to call for OPT_CL_CALL void (* hook)(const struct opt_item * opt, uint event, const char * value, void * data); // function to call for OPT_CL_HOOK struct cf_user_type * utype; // specification of the user-defined type for CT_USER const struct xtype * xtype; // specification of the extended type for CT_XTYPE } u; u16 flags; // as defined below (for hooks, event mask is stored instead) byte cls; // enum opt_class byte type; // enum cf_type };
A definition of a single option item.
Option flags
Each option can specify a combination of the following flags.
#define OPT_REQUIRED 0x1
The option must be always present.
#define OPT_REQUIRED_VALUE 0x2
The option must have a value.
#define OPT_NO_VALUE 0x4
The option must have no value.
#define OPT_MAYBE_VALUE 0x8
The option may have a value.
#define OPT_NEGATIVE 0x10
Reversing the effect of OPT_INC or saving false into OPT_BOOL.
#define OPT_LAST_ARG 0x40
Stop processing arguments after this line.
#define OPT_SINGLE 0x100
The option must appear at most once.
#define OPT_MULTIPLE 0x200
The option may appear multiple times; will save all the values into a simple list.
#define OPT_BEFORE_CONFIG 0x800
The option may appear before a config file is loaded.
#define OPT_HELP_COL 0x1000
Used for OPT_CL_HELP to signal that tabs switch columns.
#define OPT_VALUE_FLAGS (OPT_REQUIRED_VALUE | OPT_NO_VALUE | OPT_MAYBE_VALUE)
If none of these flags are specified, a default is chosen automatically according to option class:
-
OPT_MAYBE_VALUE for OPT_CL_STATIC
-
OPT_REQUIRED_VALUE for OPT_CL_MULTIPLE
-
OPT_NO_VALUE for OPT_CL_BOOL, OPT_CL_SWITCH and OPT_CL_INC
-
An error is reported in all other cases.
Macros for declaration of options
In most cases, option definitions are built using these macros.
#define OPT_ITEMS .opt = ( struct opt_item[] )
Used inside struct opt_section to start a list of items.
#define OPT_HELP(line) { .help = line, .cls = OPT_CL_HELP }
No option, just a piece of help text.
#define OPT_HELP_COLUMNS(line) { .help = line, .flags = OPT_HELP_COL, .cls = OPT_CL_HELP }
Like OPT_HELP, but the help text uses tab characters to switch columns like help text for ordinary options does.
#define OPT_HELP_OPTION OPT_CALL(0, "help", opt_handle_help, NULL, OPT_BEFORE_CONFIG | OPT_INTERNAL | OPT_NO_VALUE, "\tShow this help")
Standard --help option.
#define OPT_BOOL(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, int *), .help = desc, .flags = fl, .cls = OPT_CL_BOOL, .type = CT_INT }
Boolean option. target should be a variable of type int.
#define OPT_STRING(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, char **), .help = desc, .flags = fl, .cls = OPT_CL_STATIC, .type = CT_STRING }
String option. target should be a variable of type char *.
#define OPT_INT(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, int *), .help = desc, .flags = fl, .cls = OPT_CL_STATIC, .type = CT_INT }
Ordinary integer option. target should be a variable of type int.
#define OPT_UINT(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, uint *), .help = desc, .flags = fl, .cls = OPT_CL_STATIC, .type = CT_INT }
Unsigned integer option. target should be a variable of type uint.
#define OPT_U64(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, u64 *), .help = desc, .flags = fl, .cls = OPT_CL_STATIC, .type = CT_U64 }
64-bit integer option. target should be a variable of type u64.
#define OPT_DOUBLE(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, double *), .help = desc, .flags = fl, .cls = OPT_CL_STATIC, .type = CT_DOUBLE }
Floating-point option. target should be a variable of type double.
#define OPT_IP(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, u32 *), .help = desc, .flags = fl, .cls = OPT_CL_STATIC, .type = CT_IP }
IP address option, currently IPv4 only. target should be a variable of type u32.
#define OPT_STRING_MULTIPLE(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, char ***), .help = desc, .flags = fl, .cls = OPT_CL_MULTIPLE, .type = CT_STRING }
Multi-valued string option. target should be a growing array of `char *`s.
#define OPT_INT_MULTIPLE(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, int **), .help = desc, .flags = fl, .cls = OPT_CL_MULTIPLE, .type = CT_INT }
Multi-valued integer option. target should be a growing array of `int`s.
#define OPT_UINT_MULTIPLE(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, uint **), .help = desc, .flags = fl, .cls = OPT_CL_MULTIPLE, .type = CT_INT }
Multi-valued unsigned integer option. target should be a growing array of `uint`s.
#define OPT_U64_MULTIPLE(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, u64 **), .help = desc, .flags = fl, .cls = OPT_CL_MULTIPLE, .type = CT_U64 }
Multi-valued 64-bit integer option. target should be a growing array of `u64`s.
#define OPT_DOUBLE_MULTIPLE(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, double **), .help = desc, .flags = fl, .cls = OPT_CL_MULTIPLE, .type = CT_DOUBLE }
Multi-valued floating-point option. target should be a growing array of `double`s.
#define OPT_IP_MULTIPLE(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, u32 **), .help = desc, .flags = fl, .cls = OPT_CL_MULTIPLE, .type = CT_IP }
Multi-valued IPv4 address option. target should be a growing array of `u32`s.
#define OPT_SWITCH(shortopt, longopt, target, val, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, int *), .help = desc, .flags = fl, .cls = OPT_CL_SWITCH, .type = CT_LOOKUP, .u.value = val }
Switch option. target should be a variable of type int and it will be set to the value val.
#define OPT_INC(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, int *), .flags = fl, .help = desc, .cls = OPT_CL_INC, .type = CT_INT }
Incrementing option. target should be a variable of type int.
#define OPT_BREAK(shortopt, longopt, fl) { .letter = shortopt, .name = longopt, .flags = fl, .cls = OPT_CL_BREAK }
Breakpoint option. When this option occurs, parsing is terminated and the option is kept in the argument array.
#define OPT_CALL(shortopt, longopt, fn, data, fl, desc) { .letter = shortopt, .name = longopt, .ptr = data, .help = desc, .u.call = fn, .flags = fl, .cls = OPT_CL_CALL, .type = CT_USER }
When this option appears, call the function fn with parameters item, value, data, where item points to the struct opt_item of this option, value contains the current argument of the option (NULL if there is none), and data is specified here.
#define OPT_USER(shortopt, longopt, target, ttype, fl, desc) { .letter = shortopt, .name = longopt, .ptr = &target, .u.utype = &ttype, .flags = fl, .help = desc, .cls = OPT_CL_STATIC, .type = CT_USER }
An option with user-defined syntax. ttype is a cf_user_type describing the syntax, target is a variable of the corresponding type. If the OPT_REQUIRED_VALUE flag is not set, the parser must be able to parse a NULL value.
#define OPT_USER_MULTIPLE(shortopt, longopt, target, ttype, fl, desc) { .letter = shortopt, .name = longopt, .ptr = &target, .u.utype = &ttype, .flags = fl, .help = desc, .cls = OPT_CL_MULTIPLE, .type = CT_USER }
Multi-valued option of user-defined type. target should be a growing array of the right kind of items.
#define OPT_XTYPE(shortopt, longopt, target, ttype, fl, desc) { .letter = shortopt, .name = longopt, .ptr = &target, .u.xtype = &ttype, .flags = fl, .help = desc, .cls = OPT_CL_STATIC, .type = CT_XTYPE }
An option with user-defined syntax. xtype is a xtype describing the syntax, target is a variable of the corresponding type. If the OPT_REQUIRED_VALUE flag is not set, the parser must be able to parse a NULL value.
#define OPT_XTYPE_MULTIPLE(shortopt, longopt, target, ttype, fl, desc) { .letter = shortopt, .name = longopt, .ptr = &target, .u.xtype = &ttype, .flags = fl, .help = desc, .cls = OPT_CL_MULTIPLE, .type = CT_XTYPE }
Multi-valued option of extended type. target should be a growing array of the right kind of items.
#define OPT_SECTION(sec) { .cls = OPT_CL_SECTION, .u.section = &sec }
A sub-section.
#define OPT_HOOK(fn, data, events) { .cls = OPT_CL_HOOK, .u.hook = fn, .flags = events, .ptr = data }
Declares a hook to call upon any event from the specified set.
#define OPT_END { .cls = OPT_CL_END }
A terminator signalling the end of the option list.
Positional arguments
In addition to short and long options, the parser can also process positional arguments, which don’t start with a dash and whose meaning depends solely on their position.
Positional arguments are declared as options with short name OPT_POSITIONAL(n) (where n is the position of the argument, starting with 1) and long name NULL. To accept an arbitrary number of positional arguments, use OPT_POSITIONAL_TAIL instead, which matches all arguments, for which no OPT_POSITIONAL is defined. (In the latter case, you probably want to define the argument as OPT_MULTIPLE or OPT_CALL, so that the values do not overwrite each other.)
Options and positional arguments can be mixed arbitrarily. When a -- appears as an argument, it is understood as a signal that all other arguments are positional.
OPT_REQUIRED can be used with positional arguments, but all required arguments must come before the non-required ones. When OPT_POSITIONAL_TAIL is declared required, it means that it must match at least once.
Ordering of positional arguments within the list of options need not match their positions. Holes in position numbering are inadvisable.
Functions
int opt_parse(const struct opt_section * options, char ** argv);
Parse all arguments, given in a NULL-terminated array of strings.
Typically, this is called from main(argc, argv) as opt_parse(options, argv+1), skipping the 0th argument, which contains program name.
Returns the number of arguments used (which need not be all of them if OPT_LAST_ARG was encountered).
The argument array is left untouched. However, option values are not necessarily copied, the variables set by the parser may point to the argument array.
void opt_failure(const char * mesg, ...) FORMAT_CHECK(printf,1,2) NONRET;
Report parsing failure, suggest --help, and abort the program with exit code 2.
Cooperating with config file parser
Parsing of command-line options and configuration files are usually intertwined in a somewhat tricky way. We want to provide command-line options that control the name of the configuration file, or possibly to override configuration settings from the command line. On the other hand, regular command-line options can refer to values loaded from the program’s configuration.
To achieve this goal, the option parser is able to cooperate with the config file parser. This is enabled by listing the OPT_CONF_OPTIONS macro in the list of command-line options.
The following options are defined for you:
-
-C (--config) to load a specific configuration file. This option suppresses loading of the default configuration, but it can be given multiple times to merge settings from several files.
-
-S (--set) to include a part of configuration inline. For example, you can use -Ssection.item=value to change a single configuration item.
-
--dumpconfig to dump the configuration to standard output and exit. (This is available only if the program is compiled with CONFIG_UCW_DEBUG.)
The default configuration file (as specified by cf_def_file) is loaded as soon as the first option different from -C is encountered, unless a different file has been already loaded. For this reason, -C must be the very first argument given to the program.
This interface supersedes cf_getopt().
extern char *cf_def_file;
The name of the default configuration file (NULL if configuration has been already loaded). It is initialized to CONFIG_UCW_DEFAULT_CONFIG, but you usually want to replace it by your own config file.
extern char *cf_env_file;
Name of environment variable that can override what configuration is loaded. Defaults to the value of the CONFIG_UCW_ENV_VAR_CONFIG compile-time option.
Hooks
You can supply hook functions, which will be called by the parser upon various events. Hooks are declared as option items of class OPT_CL_HOOK, whose flags field specifies a mask of events the hook wants to receive.
Please note that the hook interface is not considered stable yet, so it might change in future versions of libucw.
The following events are defined:
#define OPT_HOOK_BEFORE_ARG 0x1
Call before option parsing
#define OPT_HOOK_BEFORE_VALUE 0x2
Call before value parsing
#define OPT_HOOK_AFTER_VALUE 0x4
Call after value parsing
#define OPT_HOOK_FINAL 0x8
Call just before opt_parse() returns