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