From 0d3402b7f5f8649a5f6da491c7da88a7a05d3d3a Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Tue, 5 May 2009 05:39:03 -0700 Subject: [PATCH] Implement new 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 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 | 182 ++++++++++++++++++++++++++++++++++++++ src/libpspp/argv-parser.h | 58 ++++++++++++ src/libpspp/automake.mk | 2 + 3 files changed, 242 insertions(+) create mode 100644 src/libpspp/argv-parser.c create mode 100644 src/libpspp/argv-parser.h diff --git a/src/libpspp/argv-parser.c b/src/libpspp/argv-parser.c new file mode 100644 index 00000000..b3dc4613 --- /dev/null +++ b/src/libpspp/argv-parser.c @@ -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 . */ + +#include + +#include + +#include + +#include +#include + +#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 index 00000000..3406ba21 --- /dev/null +++ b/src/libpspp/argv-parser.h @@ -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 . */ + +#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 +#include + +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 */ diff --git a/src/libpspp/automake.mk b/src/libpspp/automake.mk index 1564569c..3e086471 100644 --- a/src/libpspp/automake.mk +++ b/src/libpspp/automake.mk @@ -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 \ -- 2.30.2