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
--- /dev/null
+/* 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 <https://www.gnu.org/licenses/>. */
+
+/* Written by Bruno Haible <bruno@clisp.org>. */
+
+#include <config.h>
+
+/* Specification. */
+#include "options.h"
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/* 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;
+}
--- /dev/null
+/* 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 <https://www.gnu.org/licenses/>. */
+
+/* Written by Bruno Haible <bruno@clisp.org>. */
+
+#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
+ <https://www.gnu.org/prep/standards/html_node/Command_002dLine-Interfaces.html>.
+
+ 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 <getopt.h>
+/* Get size_t. */
+#include <stddef.h>
+/* Get countof. */
+#include <stdcountof.h>
+
+#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 */