argv-parser: Add assertion to find likely bugs in client code.
[pspp-builds.git] / src / libpspp / argv-parser.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <libpspp/argv-parser.h>
20
21 #include <limits.h>
22
23 #include <libpspp/assertion.h>
24 #include <libpspp/str.h>
25
26 #include "xalloc.h"
27
28 struct argv_option_plus
29   {
30     struct argv_option base;
31     void (*cb) (int id, void *aux);
32     void *aux;
33   };
34
35 struct argv_parser
36   {
37     struct argv_option_plus *options;
38     size_t n_options, allocated_options;
39   };
40
41 /* Creates and returns a new argv_parser that initially is not
42    configured to parse any command-line options. */
43 struct argv_parser *
44 argv_parser_create (void)
45 {
46   struct argv_parser *ap = xzalloc (sizeof *ap);
47   return ap;
48 }
49
50 /* Destroys AP. */
51 void
52 argv_parser_destroy (struct argv_parser *ap)
53 {
54   if (ap != NULL)
55     {
56       free (ap->options);
57       free (ap);
58     }
59 }
60
61 /* Adds the N options in OPTIONS to AP.  When argv_parser_run is
62    later called for AP, each of the options in OPTIONS will be
63    handled by passing the option's 'id' member to CB along with
64    AUX.  For an option that has an argument, the 'optarg' global
65    variable will be set to point to it before calling CB;
66    otherwise 'optarg' will be set to NULL. */
67 void
68 argv_parser_add_options (struct argv_parser *ap,
69                          const struct argv_option *options, size_t n,
70                          void (*cb) (int id, void *aux), void *aux)
71 {
72   const struct argv_option *src;
73   for (src = options; src < &options[n]; src++)
74     {
75       struct argv_option_plus *dst;
76
77       if (ap->n_options >= ap->allocated_options)
78         ap->options = x2nrealloc (ap->options, &ap->allocated_options,
79                                   sizeof *ap->options);
80
81       assert (src->long_name != NULL || src->short_name != 0);
82       dst = &ap->options[ap->n_options++];
83       dst->base = *src;
84       dst->cb = cb;
85       dst->aux = aux;
86     }
87 }
88
89 /* Parses all ARGC command-line arguments in ARGV according to
90    the options configured in AP with argv_parser_add_options.
91    Returns true if all the command-line arguments were parsed
92    successfully, false if there was an error.  Upon failure
93    return, if the external variable 'opterr' is nonzero (which is
94    the default), an error message will also be printed.  Upon
95    successful return, external variable 'optind' will be set to
96    the index of the first non-option argument. */
97 bool
98 argv_parser_run (struct argv_parser *ap, int argc, char **argv)
99 {
100   enum { LONGOPT_VAL_BASE = UCHAR_MAX + 1 };
101   const struct argv_option_plus *shortopt_ptrs[UCHAR_MAX + 1];
102   struct string shortopts;
103   struct option *longopts;
104   size_t n_longopts;
105   bool retval;
106   size_t i;
107
108   memset (shortopt_ptrs, 0, sizeof shortopt_ptrs);
109   ds_init_empty (&shortopts);
110   longopts = xmalloc ((ap->n_options + 1) * sizeof *longopts);
111   n_longopts = 0;
112   for (i = 0; i < ap->n_options; i++)
113     {
114       const struct argv_option_plus *aop = &ap->options[i];
115
116       if (aop->base.long_name != NULL)
117         {
118           struct option *o = &longopts[n_longopts++];
119           o->name = aop->base.long_name;
120           o->has_arg = aop->base.has_arg;
121           o->flag = NULL;
122           o->val = i + LONGOPT_VAL_BASE;
123         }
124
125       if (aop->base.short_name != 0)
126         {
127           unsigned char c = aop->base.short_name;
128           if (shortopt_ptrs[c] == NULL)
129             {
130               shortopt_ptrs[c] = aop;
131               ds_put_char (&shortopts, aop->base.short_name);
132               if (aop->base.has_arg != no_argument)
133                 ds_put_char (&shortopts, ':');
134               if (aop->base.has_arg == optional_argument)
135                 ds_put_char (&shortopts, ':');
136             }
137           else
138             {
139               if (opterr)
140                 fprintf (stderr, "option -%c multiply defined",
141                          aop->base.short_name);
142               retval = false;
143               goto exit;
144             }
145         }
146     }
147   memset (&longopts[n_longopts], 0, sizeof *longopts);
148
149   for (;;)
150     {
151       int indexptr;
152       int c = getopt_long (argc, argv, ds_cstr (&shortopts),
153                            longopts, &indexptr);
154
155       if (c == -1)
156         {
157           retval = true;
158           break;
159         }
160       else if (c == '?')
161         {
162           retval = false;
163           break;
164         }
165       else if (c >= LONGOPT_VAL_BASE && c < LONGOPT_VAL_BASE + n_longopts)
166         {
167           struct argv_option_plus *aop = &ap->options[c - LONGOPT_VAL_BASE];
168           aop->cb (aop->base.id, aop->aux);
169         }
170       else if (c >= SCHAR_MIN && c <= UCHAR_MAX)
171         {
172           const struct argv_option_plus *aop = shortopt_ptrs[(unsigned char) c];
173           aop->cb (aop->base.id, aop->aux);
174         }
175       else
176         NOT_REACHED ();
177     }
178
179 exit:
180   ds_destroy (&shortopts);
181   free (longopts);
182   return retval;
183 }