1 /* lsx_getopt for SoX
2  *
3  * (c) 2011 Doug Cook and SoX contributors
4  *
5  * This library is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation; either version 2.1 of the License, or (at
8  * your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this library; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19 
20 #include "sox.h"
21 #include <assert.h>
22 #include <stdlib.h>
23 #include <string.h>
24 
25 void
lsx_getopt_init(LSX_PARAM_IN int argc,LSX_PARAM_IN_COUNT (argc)char * const * argv,LSX_PARAM_IN_Z char const * shortopts,LSX_PARAM_IN_OPT lsx_option_t const * longopts,LSX_PARAM_IN lsx_getopt_flags_t flags,LSX_PARAM_IN int first,LSX_PARAM_OUT lsx_getopt_t * state)26 lsx_getopt_init(
27     LSX_PARAM_IN             int argc,                      /* Number of arguments in argv */
28     LSX_PARAM_IN_COUNT(argc) char * const * argv,           /* Array of arguments */
29     LSX_PARAM_IN_Z           char const * shortopts,        /* Short option characters */
30     LSX_PARAM_IN_OPT         lsx_option_t const * longopts, /* Array of long option descriptors */
31     LSX_PARAM_IN             lsx_getopt_flags_t flags,      /* Flags for longonly and opterr */
32     LSX_PARAM_IN             int first,                     /* First argument to check (usually 1) */
33     LSX_PARAM_OUT            lsx_getopt_t * state)          /* State object to initialize */
34 {
35     assert(argc >= 0);
36     assert(argv != NULL);
37     assert(shortopts);
38     assert(first >= 0);
39     assert(first <= argc);
40     assert(state);
41     if (state)
42     {
43         if (argc < 0 ||
44             !argv ||
45             !shortopts ||
46             first < 0 ||
47             first > argc)
48         {
49             memset(state, 0, sizeof(*state));
50         }
51         else
52         {
53             state->argc = argc;
54             state->argv = argv;
55             state->shortopts =
56                 (shortopts[0] == '+' || shortopts[0] == '-') /* Requesting GNU special behavior? */
57                 ? shortopts + 1 /* Ignore request. */
58                 : shortopts; /* No special behavior requested. */
59             state->longopts = longopts;
60             state->flags = flags;
61             state->curpos = NULL;
62             state->ind = first;
63             state->opt = '?';
64             state->arg = NULL;
65             state->lngind = -1;
66         }
67     }
68 }
69 
CheckCurPosEnd(LSX_PARAM_INOUT lsx_getopt_t * state)70 static void CheckCurPosEnd(
71     LSX_PARAM_INOUT lsx_getopt_t * state)
72 {
73     if (!state->curpos[0])
74     {
75         state->curpos = NULL;
76         state->ind++;
77     }
78 }
79 
80 int
lsx_getopt(LSX_PARAM_INOUT lsx_getopt_t * state)81 lsx_getopt(
82     LSX_PARAM_INOUT lsx_getopt_t * state)
83 {
84     int oerr;
85     assert(state);
86     if (!state)
87     {
88         lsx_fail("lsx_getopt called with state=NULL");
89         return -1;
90     }
91 
92     assert(state->argc >= 0);
93     assert(state->argv != NULL);
94     assert(state->shortopts);
95     assert(state->ind >= 0);
96     assert(state->ind <= state->argc + 1);
97 
98     oerr = 0 != (state->flags & lsx_getopt_flag_opterr);
99     state->opt = 0;
100     state->arg = NULL;
101     state->lngind = -1;
102 
103     if (state->argc < 0 ||
104         !state->argv ||
105         !state->shortopts ||
106         state->ind < 0)
107     { /* programmer error */
108         lsx_fail("lsx_getopt called with invalid information");
109         state->curpos = NULL;
110         return -1;
111     }
112     else if (
113         state->argc <= state->ind ||
114         !state->argv[state->ind] ||
115         state->argv[state->ind][0] != '-' ||
116         state->argv[state->ind][1] == '\0')
117     { /* return no more options */
118         state->curpos = NULL;
119         return -1;
120     }
121     else if (state->argv[state->ind][1] == '-' && state->argv[state->ind][2] == '\0')
122     { /* skip "--", return no more options. */
123         state->curpos = NULL;
124         state->ind++;
125         return -1;
126     }
127     else
128     { /* Look for the next option */
129         char const * current = state->argv[state->ind];
130         char const * param = current + 1;
131 
132         if (state->curpos == NULL ||
133             state->curpos <= param ||
134             param + strlen(param) <= state->curpos)
135         { /* Start parsing a new parameter - check for a long option */
136             state->curpos = NULL;
137 
138             if (state->longopts &&
139                 (param[0] == '-' || (state->flags & lsx_getopt_flag_longonly)))
140             {
141                 size_t nameLen;
142                 int doubleDash = param[0] == '-';
143                 if (doubleDash)
144                 {
145                     param++;
146                 }
147 
148                 for (nameLen = 0; param[nameLen] && param[nameLen] != '='; nameLen++)
149                 {}
150 
151                 /* For single-dash, you have to specify at least two letters in the name. */
152                 if (doubleDash || nameLen >= 2)
153                 {
154                     lsx_option_t const * pCur;
155                     lsx_option_t const * pMatch = NULL;
156                     int matches = 0;
157 
158                     for (pCur = state->longopts; pCur->name; pCur++)
159                     {
160                         if (0 == strncmp(pCur->name, param, nameLen))
161                         { /* Prefix match. */
162                             matches++;
163                             pMatch = pCur;
164                             if (nameLen == strlen(pCur->name))
165                             { /* Exact match - no ambiguity, stop search. */
166                                 matches = 1;
167                                 break;
168                             }
169                         }
170                     }
171 
172                     if (matches == 1)
173                     { /* Matched. */
174                         state->ind++;
175 
176                         if (param[nameLen])
177                         { /* --name=value */
178                             if (pMatch->has_arg)
179                             { /* Required or optional arg - done. */
180                                 state->arg = param + nameLen + 1;
181                             }
182                             else
183                             { /* No arg expected. */
184                                 if (oerr)
185                                 {
186                                     lsx_warn("`%s' did not expect an argument from `%s'",
187                                         pMatch->name,
188                                         current);
189                                 }
190                                 return '?';
191                             }
192                         }
193                         else if (pMatch->has_arg == lsx_option_arg_required)
194                         { /* Arg required. */
195                             state->arg = state->argv[state->ind];
196                             state->ind++;
197                             if (state->ind > state->argc)
198                             {
199                                 if (oerr)
200                                 {
201                                     lsx_warn("`%s' requires an argument from `%s'",
202                                         pMatch->name,
203                                         current);
204                                 }
205                                 return state->shortopts[0] == ':' ? ':' : '?'; /* Missing required value. */
206                             }
207                         }
208 
209                         state->lngind = pMatch - state->longopts;
210                         if (pMatch->flag)
211                         {
212                             *pMatch->flag = pMatch->val;
213                             return 0;
214                         }
215                         else
216                         {
217                             return pMatch->val;
218                         }
219                     }
220                     else if (matches == 0 && doubleDash)
221                     { /* No match */
222                         if (oerr)
223                         {
224                             lsx_warn("parameter not recognized from `%s'", current);
225                         }
226                         state->ind++;
227                         return '?';
228                     }
229                     else if (matches > 1)
230                     { /* Ambiguous. */
231                         if (oerr)
232                         {
233                             lsx_warn("parameter `%s' is ambiguous:", current);
234                             for (pCur = state->longopts; pCur->name; pCur++)
235                             {
236                                 if (0 == strncmp(pCur->name, param, nameLen))
237                                 {
238                                     lsx_warn("parameter `%s' could be `--%s'", current, pCur->name);
239                                 }
240                             }
241                         }
242                         state->ind++;
243                         return '?';
244                     }
245                 }
246             }
247 
248             state->curpos = param;
249         }
250 
251         state->opt = state->curpos[0];
252         if (state->opt == ':')
253         { /* ':' is never a valid short option character */
254             if (oerr)
255             {
256                 lsx_warn("option `%c' not recognized", state->opt);
257             }
258             state->curpos++;
259             CheckCurPosEnd(state);
260             return '?'; /* unrecognized option */
261         }
262         else
263         { /* Short option needs to be matched from option list */
264             char const * pShortopt = strchr(state->shortopts, state->opt);
265             state->curpos++;
266 
267             if (!pShortopt)
268             { /* unrecognized option */
269                 if (oerr)
270                 {
271                     lsx_warn("option `%c' not recognized", state->opt);
272                 }
273                 CheckCurPosEnd(state);
274                 return '?';
275             }
276             else if (pShortopt[1] == ':' && state->curpos[0])
277             { /* Return the rest of the parameter as the option's value */
278                 state->arg = state->curpos;
279                 state->curpos = NULL;
280                 state->ind++;
281                 return state->opt;
282             }
283             else if (pShortopt[1] == ':' && pShortopt[2] != ':')
284             { /* Option requires a value */
285                 state->curpos = NULL;
286                 state->ind++;
287                 state->arg = state->argv[state->ind];
288                 state->ind++;
289                 if (state->ind <= state->argc)
290                 { /* A value was present, so we're good. */
291                     return state->opt;
292                 }
293                 else
294                 {  /* Missing required value. */
295                     if (oerr)
296                     {
297                         lsx_warn("option `%c' requires an argument",
298                             state->opt);
299                     }
300                     return state->shortopts[0] == ':' ? ':' : '?';
301                 }
302             }
303             else
304             { /* Option without a value. */
305                 CheckCurPosEnd(state);
306                 return state->opt;
307             }
308         }
309     }
310 }
311 
312 #ifdef TEST_GETOPT
313 
314 #include <stdio.h>
315 
main(int argc,char const * argv[])316 int main(int argc, char const * argv[])
317 {
318     static int help = 0;
319     static lsx_option_t longopts[] =
320     {
321         {"a11",  0, 0, 101},
322         {"a12",  0, 0, 102},
323         {"a122", 0, 0, 103},
324         {"rarg", 1, 0, 104},
325         {"oarg", 2, 0, 105},
326         {"help", 0, &help, 106},
327         {0}
328     };
329 
330     int ch;
331     lsx_getopt_t state;
332     lsx_getopt_init(argc, argv, "abc:d:v::0123456789", longopts, sox_true, 1, &state);
333 
334     while (-1 != (ch = lsx_getopt(&state)))
335     {
336         printf(
337             "H=%d ch=%d, ind=%d opt=%d lng=%d arg=%s\n",
338             help,
339             ch,
340             state.ind,
341             state.opt,
342             state.lngind,
343             state.arg ? state.arg : "NULL");
344     }
345 
346     return 0;
347 }
348 
349 #endif /* TEST_GETOPT */
350