Implement new command-line argument parser.
[pspp-builds.git] / src / libpspp / argv-parser.c
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;
+}