From: Bruno Haible Date: Fri, 27 Jun 2025 13:27:24 +0000 (+0200) Subject: options: New module. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0d1077abf990afe45e0bc8d16997f3997e0dac51;p=thirdparty%2Fgnulib.git options: New module. * lib/options.h: New file. * lib/options.c: New file. * modules/options: New file. * doc/glibc-functions/getopt_long.texi: Mention the new module. --- diff --git a/ChangeLog b/ChangeLog index b83e1a74a8..8e4355a079 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2025-06-27 Bruno Haible + + options: New module. + * lib/options.h: New file. + * lib/options.c: New file. + * modules/options: New file. + * doc/glibc-functions/getopt_long.texi: Mention the new module. + 2025-06-26 Bruno Haible kwset: Improve header file. diff --git a/doc/glibc-functions/getopt_long.texi b/doc/glibc-functions/getopt_long.texi index b0220d1c92..32b5fb9352 100644 --- a/doc/glibc-functions/getopt_long.texi +++ b/doc/glibc-functions/getopt_long.texi @@ -62,3 +62,106 @@ glibc 2.14. Portability problems not fixed by Gnulib: @itemize @end itemize + +@mindex options +Gnulib provides also a module @code{options}, that +fixes the following shortcomings of the @code{getopt_long} API. + +These shortcomings are best illustrated with an example: + +@example +static struct option const long_options[] = +@{ + @{ "width", required_argument, NULL, 'w' @}, + @{ "help", no_argument, &show_help, 1 @}, + @{ "version", no_argument, &show_version, 1 @}, + @{ NULL, 0, NULL, 0 @} +@}; + +while ((optchar = getopt_long (argc, argv, "w:xhV", long_options, NULL)) + != -1) + switch (optchar) + @{ + case '\0': /* Long option with flag != NULL. */ + break; + case 'w': + set_width (optarg); + break; + case 'x': + do_x = true; + break; + case 'h': + show_help = 1; /* Action code duplication! */ + break; + case 'V': + show_version = 1; /* Action code duplication! */ + break; + default: + usage (EXIT_FAILURE); + @} +@end example + +@itemize +@item +The information whether an option takes a required vs.@: optional argument +needs to be specified twice: +in the @code{option[]} array for the long option +and in the string argument for the short option. +It is too easy to forget to +add the @code{":"} or @code{"::"} part in the string argument +and thus get inconsistent behaviour +between the long option and the short option. +@item +This information needs to be specified twice, but in different ways: +@multitable @columnfractions .3 .3 +@headitem In the array @tab In the string +@item @code{no_argument} @tab @code{""} +@item @code{required_argument} @tab @code{":"} +@item @code{optional_argument} @tab @code{"::"} +@end multitable +@item +For an action that merely sets an @code{int}-typed variable to a value, +you can specify this action in the @code{options[]} array, +and thus omit the handling in the @code{switch} statement. +But this works only for options that are +long options without a corresponding short option. +As soon as the option has a corresponding short option, +you do need to handle it in the @code{switch} statement. +Here again, there is the opportunity for +inconsistent behaviour between the long option and the short option. +@item +The @code{val} field in a @code{struct option} has different meanings, +depending on another field: +If the @code{flag} field is non-NULL, +@code{val} is a value to be stored in a variable. +If the @code{flag} field is NULL, +@code{val} is a key to be returned from @code{getopt_long} +and subject to the @code{switch} statement. +@item +The handling of non-option arguments is specified by +prepending a certain character (@samp{+} or @samp{-}) to the string argument. +This is not one of the usual ways to specify things in an API. +The conventional way in an API is +an argument of @code{enum} type, or a flags word. +@item +The handling of errors consists of two independent flags, +and each of the flags has to be specified in a different way: +one flag is specified by +prepending a certain character (@samp{:}) to the string argument; +the other flag is specified through the global variable @code{opterr}. +@item +The @code{struct option} is a misnomer: +It cannot encode short options. +Therefore, it would have better been called @code{struct long_option}. +@item +The @code{getopt_long} function is expected to +receive the same arguments in each call, in the @code{while} loop. +The effects are undefined if you don't follow this (unwritten!) constraint. +@item +The fifth argument to @code{getopt_long}, @var{indexptr}, is redundant, because +when the @code{flag} is non-NULL, +the @code{switch} statement does not need to handle the option, +and when the @code{flag} is NULL, +@code{getopt_long} returns the value of @code{val}, +as a way to identify which option was seen. +@end itemize diff --git a/lib/options.c b/lib/options.c new file mode 100644 index 0000000000..f981613bf8 --- /dev/null +++ b/lib/options.c @@ -0,0 +1,145 @@ +/* Parsing program options. + Copyright (C) 2025 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* Written by Bruno Haible . */ + +#include + +/* Specification. */ +#include "options.h" + +#include +#include +#include + +/* State for communicating between _gl_start_options and get_next_option. */ +static struct + { + int argc; + char **argv; + const struct program_option *options; + size_t n_options; + struct option *long_options; + char *short_options; + } +state; + +void +_gl_start_options (int argc, char **argv, + const struct program_option *options, size_t n_options, + struct option *long_options, char *short_options, + enum non_option_handling nonopt_handling, + unsigned int error_handling) +{ + /* Construct the long_options array. */ + { + struct option *p = long_options; + + for (size_t i = 0; i < n_options; i++) + if (options[i].name != NULL) + { + if (options[i].key == 0 && options[i].variable == NULL) + fprintf (stderr, + "start_options: warning: Option '--%s' has no action. Use the 'key' or the 'variable' field to specify an action.\n", + options[i].name); + + p->name = options[i].name; + p->has_arg = options[i].has_arg; + if (options[i].key == 0 && options[i].variable != NULL) + { + p->flag = options[i].variable; + p->val = options[i].value; + } + else + { + p->flag = NULL; + p->val = options[i].key; + } + p++; + } + + p->name = NULL; + p->has_arg = 0; + p->flag = NULL; + p->val = 0; + p++; + /* Verify that we haven't exceeded its allocated size. */ + if (!(p - long_options <= _GL_LONG_OPTIONS_SIZE (n_options))) + abort (); + } + + /* Construct the short_options string. */ + { + char *p = short_options; + + if (nonopt_handling == NON_OPTION_TERMINATES_OPTIONS) + *p++ = '+'; + else if (nonopt_handling == PROCESS_NON_OPTIONS) + *p++ = '-'; + + if (error_handling & OPTIONS_MISSING_IS_COLON) + *p++ = ':'; + + for (size_t i = 0; i < n_options; i++) + if (options[i].key != 0 && options[i].key <= CHAR_MAX) + { + *p++ = options[i].key; + if (options[i].has_arg != no_argument) + { + *p++ = ':'; + if (options[i].has_arg == optional_argument) + *p++ = ':'; + } + } + + *p++ = '\0'; + /* Verify that we haven't exceeded its allocated size. */ + if (!(p - short_options <= _GL_SHORT_OPTIONS_SIZE (n_options))) + abort (); + } + + state.argc = argc; + state.argv = argv; + state.options = options; + state.n_options = n_options; + state.long_options = long_options; + state.short_options = short_options; + opterr = (error_handling & OPTIONS_ERRORS_SILENT) == 0; +} + +int +get_next_option (void) +{ + if (state.argv == NULL) + { + fprintf (stderr, "fatal: start_options has not been invoked\n"); + abort (); + } + int ret = getopt_long (state.argc, state.argv, + state.short_options, state.long_options, NULL); + if (ret > 1) + { + const struct program_option *options = state.options; + size_t n_options = state.n_options; + for (size_t i = 0; i < n_options; i++) + if (ret == options[i].key) + { + if (options[i].variable != NULL) + *(options[i].variable) = options[i].value; + } + } + return ret; +} diff --git a/lib/options.h b/lib/options.h new file mode 100644 index 0000000000..18e590804f --- /dev/null +++ b/lib/options.h @@ -0,0 +1,238 @@ +/* Parsing program options. + Copyright (C) 2025 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* Written by Bruno Haible . */ + +#ifndef _OPTIONS_H +#define _OPTIONS_H + +/* This file provides a more convenient API to parsing program options, + based on GNU getopt_long() and thus compatible with the option parsing + conventions for GNU programs + . + + Instead of writing + + static struct option const long_options[] = + { + { "width", required_argument, NULL, 'w' }, + { "help", no_argument, &show_help, 1 }, + { "version", no_argument, &show_version, 1 }, + { NULL, 0, NULL, 0 } + }; + + while ((optchar = getopt_long (argc, argv, "w:xhV", long_options, NULL)) + != -1) + switch (optchar) + { + case '\0': // Long option with flag != NULL. + break; + case 'w': + set_width (optarg); + break; + case 'x': + do_x = true; + break; + case 'h': + show_help = 1; + break; + case 'V': + show_version = 1; + break; + default: + usage (EXIT_FAILURE); + } + + you write + + static struct program_option const options[] = + { + { "width", 'w', required_argument }, + { NULL, 'x', no_argument }, + { "help", 'h', no_argument, &show_help, 1 }, + { "version", 'V', no_argument, &show_version, 1 }, + }; + + start_options (argc, argv, options, MOVE_OPTIONS_FIRST, 0); + while ((optchar = get_next_option ()) != -1) + switch (optchar) + { + case '\0': // Long option with key == 0. + break; + case 'w': + set_width (optarg); + break; + case 'x': + do_x = true; + break; + case 'h': + case 'V': + break; + default: + usage (EXIT_FAILURE); + } + + This API fixes the following shortcomings of the getopt_long() API: + + * The information whether an option takes a required vs. optional argument + needs to be specified twice: in the option[] array for the long option + and in the string argument for the short option. + It is too easy to forget to add the ":" or "::" part in the string + argument and thus get inconsistent behaviour between the long option and + the short option. + + * This information needs to be specified twice, but in different ways: + In the array In the string + ------------ ------------- + no_argument "" + required_argument ":" + optional_argument "::" + + * For an action that merely sets an 'int'-typed variable to a value, you + can specify this action in the options[] array, and thus omit the + handling in the 'switch' statement. But this works only for options + that are long options without a corresponding short option. As soon + as the option has a corresponding short option, you *do* need to handle + it in the 'switch' statement. Here again, there is the opportunity + for inconsistent behaviour between the long option and the short option. + + * The 'val' field in a 'struct option' has different meanings, depending + on another field: If the 'flag' field is non-NULL, 'val' is a value to + be stored in a variable. If the 'flag' field is NULL, 'val' is a key + to be returned from getopt_long() and subject to the 'switch' statement. + + * The handling of non-option arguments is specified by prepending a + certain character ('+' or '-') to the string argument. This is not + one of the usual ways to specify things in an API. The conventional + way in an API is an argument of 'enum' type, or a flags word. + + * The handling of errors consists of two independent flags, and each of + the flags has to be specified in a different way: one flag is specified + by prepending a certain character (':') to the string argument; the + other flag is specified through the global variable 'opterr'. + + * The 'struct option' is a misnomer: It cannot encode short options. + Therefore, it would have better been called 'struct long_option'. + + * The getopt_long() function is expected to receive the same arguments in + each call, in the 'while' loop. The effects are undefined if you + don't follow this (unwritten!) constraint. + + * The fifth argument to getopt_long(), indexptr, is redundant, because + when the 'flag' is non-NULL, the switch statement does not need to + handle the option, and when the 'flag' is NULL, getopt_long returns + the value of 'val', as a way to identify which option was seen. + + It keeps the following properties the getopt_long() API: + + * The programmer writes in actions directly in the main() function. + That is, the actions don't go into separate callback functions + (like with argp). Such callback functions are a fine thing in languages + with nested function (and implicit closures), like Lisp and C++, but not + in C. + + * The option processing does not require dynamic memory allocation. That + is, you don't need to worry about out-of-memory situations here. + */ + +/* Get no_argument, required_argument, optional_argument. */ +#include +/* Get size_t. */ +#include +/* Get countof. */ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Description of an option (long option or short option or both). */ +struct program_option +{ + /* The name of the long option, or NULL for a short-option-only. */ + const char *name; + /* A key that uniquely identifies the option. + If the option has a short option, use the character of that short option. + Otherwise, use an arbitrary unique value > CHAR_MAX. + Don't use '\0' or '\1' here, because they have a special meaning. */ + int key; + + /* One of: no_argument, required_argument, optional_argument. */ + int has_arg; + + /* For an action that consists in assigned a value to an 'int'-typed variable, + put the variable and the value here. + Otherwise, use NULL and 0, or omit these fields. */ + int *variable; + int value; +}; + +/* Handling of non-option arguments. */ +enum non_option_handling { + /* Move options before non-option arguments. + This is suitable for most programs. */ + MOVE_OPTIONS_FIRST, + /* Option processing stops when the first non-option argument is encountered. + This is suitable for programs that pass an entire argument list to another + program (such as 'env'), or for programs that accept normal arguments that + start with '-' (such as 'expr' and 'printf'). */ + NON_OPTION_TERMINATES_OPTIONS, + /* Process non-option arguments as if they were options, associated with the + key '\1'. */ + PROCESS_NON_OPTIONS +}; + +/* Handling of errors: A bit mask. */ +#define OPTIONS_ERRORS_SILENT 0x1 +#define OPTIONS_MISSING_IS_COLON 0x2 + +/* Starts the processing of options. */ +#define start_options(argc, argv, options, nonopt_handling, error_handling) \ + /* Allocate room for the long options and short options. */ \ + struct option _gl_long_options[_GL_LONG_OPTIONS_SIZE (countof (options))]; \ + char _gl_short_options[_GL_SHORT_OPTIONS_SIZE (countof (options))]; \ + _gl_start_options (argc, argv, \ + options, countof (options), \ + _gl_long_options, _gl_short_options, \ + nonopt_handling, error_handling) +#define _GL_LONG_OPTIONS_SIZE(count) ((count) + 1) +#define _GL_SHORT_OPTIONS_SIZE(count) (3 * (count) + 3) + +extern void _gl_start_options (int argc, /*const*/ char **argv, + const struct program_option *options, + size_t n_options, + struct option *long_options, char *short_options, + enum non_option_handling nonopt_handling, + unsigned int error_handling); + +/* Processes the next option (or, if PROCESS_NON_OPTIONS was specified, + non-option). + Requires a prior 'start_options' invocation in the same scope or an outer + scope. + If the option has a 'variable' field, that variable is assigned the 'value' + field. + Returns the key of the option, or '\1' when returning a non-option argument. + If the option lacks an argument, it returns '?'. + If the option is unknown, it returns '?' or (if OPTIONS_MISSING_IS_COLON was + specified) ':'. + If the processing is terminated, it returns -1. */ +extern int get_next_option (void); + +#ifdef __cplusplus +} +#endif + +#endif /* _OPTIONS_H */ diff --git a/modules/options b/modules/options new file mode 100644 index 0000000000..2e73ce886e --- /dev/null +++ b/modules/options @@ -0,0 +1,24 @@ +Description: +Parsing program options. + +Files: +lib/options.h +lib/options.c + +Depends-on: +getopt-gnu +stdcountof-h + +configure.ac: + +Makefile.am: +lib_SOURCES += options.c + +Include: +"options.h" + +License: +GPL + +Maintainer: +all