b3dc46134a81653a9698269a7da8b0ceec033058
[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       dst = &ap->options[ap->n_options++];
82       dst->base = *src;
83       dst->cb = cb;
84       dst->aux = aux;
85     }
86 }
87
88 /* Parses all ARGC command-line arguments in ARGV according to
89    the options configured in AP with argv_parser_add_options.
90    Returns true if all the command-line arguments were parsed
91    successfully, false if there was an error.  Upon failure
92    return, if the external variable 'opterr' is nonzero (which is
93    the default), an error message will also be printed.  Upon
94    successful return, external variable 'optind' will be set to
95    the index of the first non-option argument. */
96 bool
97 argv_parser_run (struct argv_parser *ap, int argc, char **argv)
98 {
99   enum { LONGOPT_VAL_BASE = UCHAR_MAX + 1 };
100   const struct argv_option_plus *shortopt_ptrs[UCHAR_MAX + 1];
101   struct string shortopts;
102   struct option *longopts;
103   size_t n_longopts;
104   bool retval;
105   size_t i;
106
107   memset (shortopt_ptrs, 0, sizeof shortopt_ptrs);
108   ds_init_empty (&shortopts);
109   longopts = xmalloc ((ap->n_options + 1) * sizeof *longopts);
110   n_longopts = 0;
111   for (i = 0; i < ap->n_options; i++)
112     {
113       const struct argv_option_plus *aop = &ap->options[i];
114
115       if (aop->base.long_name != NULL)
116         {
117           struct option *o = &longopts[n_longopts++];
118           o->name = aop->base.long_name;
119           o->has_arg = aop->base.has_arg;
120           o->flag = NULL;
121           o->val = i + LONGOPT_VAL_BASE;
122         }
123
124       if (aop->base.short_name != 0)
125         {
126           unsigned char c = aop->base.short_name;
127           if (shortopt_ptrs[c] == NULL)
128             {
129               shortopt_ptrs[c] = aop;
130               ds_put_char (&shortopts, aop->base.short_name);
131               if (aop->base.has_arg != no_argument)
132                 ds_put_char (&shortopts, ':');
133               if (aop->base.has_arg == optional_argument)
134                 ds_put_char (&shortopts, ':');
135             }
136           else
137             {
138               if (opterr)
139                 fprintf (stderr, "option -%c multiply defined",
140                          aop->base.short_name);
141               retval = false;
142               goto exit;
143             }
144         }
145     }
146   memset (&longopts[n_longopts], 0, sizeof *longopts);
147
148   for (;;)
149     {
150       int indexptr;
151       int c = getopt_long (argc, argv, ds_cstr (&shortopts),
152                            longopts, &indexptr);
153
154       if (c == -1)
155         {
156           retval = true;
157           break;
158         }
159       else if (c == '?')
160         {
161           retval = false;
162           break;
163         }
164       else if (c >= LONGOPT_VAL_BASE && c < LONGOPT_VAL_BASE + n_longopts)
165         {
166           struct argv_option_plus *aop = &ap->options[c - LONGOPT_VAL_BASE];
167           aop->cb (aop->base.id, aop->aux);
168         }
169       else if (c >= SCHAR_MIN && c <= UCHAR_MAX)
170         {
171           const struct argv_option_plus *aop = shortopt_ptrs[(unsigned char) c];
172           aop->cb (aop->base.id, aop->aux);
173         }
174       else
175         NOT_REACHED ();
176     }
177
178 exit:
179   ds_destroy (&shortopts);
180   free (longopts);
181   return retval;
182 }