Implement new command-line argument parser.
authorBen Pfaff <blp@gnu.org>
Tue, 5 May 2009 12:39:03 +0000 (05:39 -0700)
committerBen Pfaff <blp@gnu.org>
Sun, 7 Jun 2009 04:11:07 +0000 (21:11 -0700)
glibc has two option parsers, but neither one of them feels quite
right:

  - getopt_long is simple, but not modular, in that there is no
    easy way to make it accept multiple collections of options
    supported by different modules.

  - argp is more sophisticated and more complete, and hence more
    complex.  It still lacks one important feature for
    modularity: there is no straightforward way for option groups
    that are implemented independently to have separate auxiliary
    data,

The parser implemented in this commit is meant to be simple and
modular.  It is based internally on getopt_long.

The initial use for this option parser is for an upcoming commit of a test
program that has some of its own options and some from the model checker,
but it should also be appropriate for PSPP and PSPPIRE if anyone wants to
adapt them to use it.

src/libpspp/argv-parser.c [new file with mode: 0644]
src/libpspp/argv-parser.h [new file with mode: 0644]
src/libpspp/automake.mk

diff --git a/src/libpspp/argv-parser.c b/src/libpspp/argv-parser.c
new file mode 100644 (file)
index 0000000..b3dc461
--- /dev/null
@@ -0,0 +1,182 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 2009 Free Software Foundation, Inc.
+
+   This program 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 program 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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include <libpspp/argv-parser.h>
+
+#include <limits.h>
+
+#include <libpspp/assertion.h>
+#include <libpspp/str.h>
+
+#include "xalloc.h"
+
+struct argv_option_plus
+  {
+    struct argv_option base;
+    void (*cb) (int id, void *aux);
+    void *aux;
+  };
+
+struct argv_parser
+  {
+    struct argv_option_plus *options;
+    size_t n_options, allocated_options;
+  };
+
+/* Creates and returns a new argv_parser that initially is not
+   configured to parse any command-line options. */
+struct argv_parser *
+argv_parser_create (void)
+{
+  struct argv_parser *ap = xzalloc (sizeof *ap);
+  return ap;
+}
+
+/* Destroys AP. */
+void
+argv_parser_destroy (struct argv_parser *ap)
+{
+  if (ap != NULL)
+    {
+      free (ap->options);
+      free (ap);
+    }
+}
+
+/* Adds the N options in OPTIONS to AP.  When argv_parser_run is
+   later called for AP, each of the options in OPTIONS will be
+   handled by passing the option's 'id' member to CB along with
+   AUX.  For an option that has an argument, the 'optarg' global
+   variable will be set to point to it before calling CB;
+   otherwise 'optarg' will be set to NULL. */
+void
+argv_parser_add_options (struct argv_parser *ap,
+                         const struct argv_option *options, size_t n,
+                         void (*cb) (int id, void *aux), void *aux)
+{
+  const struct argv_option *src;
+  for (src = options; src < &options[n]; src++)
+    {
+      struct argv_option_plus *dst;
+
+      if (ap->n_options >= ap->allocated_options)
+        ap->options = x2nrealloc (ap->options, &ap->allocated_options,
+                                  sizeof *ap->options);
+
+      dst = &ap->options[ap->n_options++];
+      dst->base = *src;
+      dst->cb = cb;
+      dst->aux = aux;
+    }
+}
+
+/* Parses all ARGC command-line arguments in ARGV according to
+   the options configured in AP with argv_parser_add_options.
+   Returns true if all the command-line arguments were parsed
+   successfully, false if there was an error.  Upon failure
+   return, if the external variable 'opterr' is nonzero (which is
+   the default), an error message will also be printed.  Upon
+   successful return, external variable 'optind' will be set to
+   the index of the first non-option argument. */
+bool
+argv_parser_run (struct argv_parser *ap, int argc, char **argv)
+{
+  enum { LONGOPT_VAL_BASE = UCHAR_MAX + 1 };
+  const struct argv_option_plus *shortopt_ptrs[UCHAR_MAX + 1];
+  struct string shortopts;
+  struct option *longopts;
+  size_t n_longopts;
+  bool retval;
+  size_t i;
+
+  memset (shortopt_ptrs, 0, sizeof shortopt_ptrs);
+  ds_init_empty (&shortopts);
+  longopts = xmalloc ((ap->n_options + 1) * sizeof *longopts);
+  n_longopts = 0;
+  for (i = 0; i < ap->n_options; i++)
+    {
+      const struct argv_option_plus *aop = &ap->options[i];
+
+      if (aop->base.long_name != NULL)
+        {
+          struct option *o = &longopts[n_longopts++];
+          o->name = aop->base.long_name;
+          o->has_arg = aop->base.has_arg;
+          o->flag = NULL;
+          o->val = i + LONGOPT_VAL_BASE;
+        }
+
+      if (aop->base.short_name != 0)
+        {
+          unsigned char c = aop->base.short_name;
+          if (shortopt_ptrs[c] == NULL)
+            {
+              shortopt_ptrs[c] = aop;
+              ds_put_char (&shortopts, aop->base.short_name);
+              if (aop->base.has_arg != no_argument)
+                ds_put_char (&shortopts, ':');
+              if (aop->base.has_arg == optional_argument)
+                ds_put_char (&shortopts, ':');
+            }
+          else
+            {
+              if (opterr)
+                fprintf (stderr, "option -%c multiply defined",
+                         aop->base.short_name);
+              retval = false;
+              goto exit;
+            }
+        }
+    }
+  memset (&longopts[n_longopts], 0, sizeof *longopts);
+
+  for (;;)
+    {
+      int indexptr;
+      int c = getopt_long (argc, argv, ds_cstr (&shortopts),
+                           longopts, &indexptr);
+
+      if (c == -1)
+        {
+          retval = true;
+          break;
+        }
+      else if (c == '?')
+        {
+          retval = false;
+          break;
+        }
+      else if (c >= LONGOPT_VAL_BASE && c < LONGOPT_VAL_BASE + n_longopts)
+        {
+          struct argv_option_plus *aop = &ap->options[c - LONGOPT_VAL_BASE];
+          aop->cb (aop->base.id, aop->aux);
+        }
+      else if (c >= SCHAR_MIN && c <= UCHAR_MAX)
+        {
+          const struct argv_option_plus *aop = shortopt_ptrs[(unsigned char) c];
+          aop->cb (aop->base.id, aop->aux);
+        }
+      else
+        NOT_REACHED ();
+    }
+
+exit:
+  ds_destroy (&shortopts);
+  free (longopts);
+  return retval;
+}
diff --git a/src/libpspp/argv-parser.h b/src/libpspp/argv-parser.h
new file mode 100644 (file)
index 0000000..3406ba2
--- /dev/null
@@ -0,0 +1,58 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 2009 Free Software Foundation, Inc.
+
+   This program 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 program 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 <http://www.gnu.org/licenses/>. */
+
+#ifndef LIBPSPP_ARGV_PARSER_H
+#define LIBPSPP_ARGV_PARSER_H 1
+
+/* Simple, modular command-line argument parser.
+
+   glibc has two option parsers, but neither one of them feels
+   quite right:
+
+     - getopt_long is simple, but not modular, in that there is
+       no easy way to make it accept multiple collections of
+       options supported by different modules.
+
+     - argp is more sophisticated and more complete, and hence
+       more complex.  It still lacks one important feature for
+       modularity: there is no straightforward way for option
+       groups that are implemented independently to have separate
+       auxiliary data.
+
+   The parser implemented in this file is meant to be simple and
+   modular.  It is based internally on getopt_long. */
+
+#include <getopt.h>
+#include <stdbool.h>
+
+struct argv_option
+  {
+    const char *long_name;  /* Long option name, NULL if none. */
+    int short_name;         /* Short option character, 0 if none. */
+    int has_arg;            /* no_argument, required_argument, or
+                               optional_argument. */
+    int id;                 /* Value passed to callback. */
+  };
+
+struct argv_parser *argv_parser_create (void);
+void argv_parser_destroy (struct argv_parser *);
+
+void argv_parser_add_options (struct argv_parser *,
+                              const struct argv_option *options, size_t n,
+                              void (*cb) (int id, void *aux), void *aux);
+bool argv_parser_run (struct argv_parser *, int argc, char **argv);
+
+#endif /* libpspp/argv-parser.h */
index 1564569cb226e52878ea445fd7d094124efddd7f..3e08647144bd6ccff4cfcc1327a9583af12ee096 100644 (file)
@@ -6,6 +6,8 @@ noinst_LTLIBRARIES += src/libpspp/libpspp.la
 src_libpspp_libpspp_la_SOURCES = \
        src/libpspp/abt.c \
        src/libpspp/abt.h \
+       src/libpspp/argv-parser.c \
+       src/libpspp/argv-parser.h \
        src/libpspp/array.c \
        src/libpspp/array.h \
        src/libpspp/assertion.h \