]> git.ipfire.org Git - thirdparty/gnulib.git/commitdiff
options: New module.
authorBruno Haible <bruno@clisp.org>
Fri, 27 Jun 2025 13:27:24 +0000 (15:27 +0200)
committerBruno Haible <bruno@clisp.org>
Fri, 27 Jun 2025 13:27:24 +0000 (15:27 +0200)
* lib/options.h: New file.
* lib/options.c: New file.
* modules/options: New file.
* doc/glibc-functions/getopt_long.texi: Mention the new module.

ChangeLog
doc/glibc-functions/getopt_long.texi
lib/options.c [new file with mode: 0644]
lib/options.h [new file with mode: 0644]
modules/options [new file with mode: 0644]

index b83e1a74a8bdfd7ab7f86920e7a25d88aba0b6f1..8e4355a079707bdfe7f68bd730a7cebbd16bccfa 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2025-06-27  Bruno Haible  <bruno@clisp.org>
+
+       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  <bruno@clisp.org>
 
        kwset: Improve header file.
index b0220d1c9265bb3c01b99ad977872f4677d41c9d..32b5fb9352a438b109448c3a0e84a2edb7e6477d 100644 (file)
@@ -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 (file)
index 0000000..f981613
--- /dev/null
@@ -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 <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;
+}
diff --git a/lib/options.h b/lib/options.h
new file mode 100644 (file)
index 0000000..18e5908
--- /dev/null
@@ -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 <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 */
diff --git a/modules/options b/modules/options
new file mode 100644 (file)
index 0000000..2e73ce8
--- /dev/null
@@ -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