Skip to content

Commit 05edb37

Browse files
committed
parse: allow gnu getopt_long style behavior
GNU `getopt_long` allows for arguments and options to be intermixed, for example "foo bar.c --help". This questionable behavior can now be opted in to. We do this by sorting the given arguments when we see the first non-option (in this example "bar.c") and shuffling options back to the front of the argument list.
1 parent a60edfb commit 05edb37

File tree

5 files changed

+321
-54
lines changed

5 files changed

+321
-54
lines changed

adopt.c

Lines changed: 201 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <stdlib.h>
1010
#include <string.h>
1111
#include <stdio.h>
12+
#include <limits.h>
1213
#include <assert.h>
1314

1415
#include "adopt.h"
@@ -26,57 +27,87 @@
2627
# define INLINE(type) static inline type
2728
#endif
2829

29-
#define spec_is_named_type(x) \
30+
#define spec_is_option_type(x) \
3031
((x)->type == ADOPT_TYPE_BOOL || \
3132
(x)->type == ADOPT_TYPE_SWITCH || \
3233
(x)->type == ADOPT_TYPE_VALUE)
3334

34-
INLINE(const adopt_spec *) spec_byname(
35-
adopt_parser *parser,
36-
const char *name,
37-
size_t namelen,
38-
int *is_converse)
35+
INLINE(const adopt_spec *) spec_for_long(
36+
int *is_negated,
37+
int *has_value,
38+
const char **value,
39+
const adopt_parser *parser,
40+
const char *arg)
3941
{
4042
const adopt_spec *spec;
43+
char *eql;
44+
size_t eql_pos;
4145

42-
*is_converse = 0;
46+
arg += 2;
47+
eql = strrchr(arg, '=');
48+
eql_pos = (eql = strrchr(arg, '=')) ? (size_t)(eql - arg) : strlen(arg);
4349

4450
for (spec = parser->specs; spec->type; ++spec) {
45-
if (spec->type == ADOPT_TYPE_LITERAL && namelen == 0)
51+
/* Handle -- (everything after this is literal) */
52+
if (spec->type == ADOPT_TYPE_LITERAL && arg[0] == '\0')
4653
return spec;
4754

48-
/* Handle "no-" prefix for boolean types */
55+
/* Handle --no-option arguments for bool types */
4956
if (spec->type == ADOPT_TYPE_BOOL &&
50-
strlen(spec->name) + 3 == namelen &&
51-
strncmp(name, "no-", 3) == 0 &&
52-
strncmp(name + 3, spec->name, namelen) == 0) {
53-
*is_converse = 1;
57+
strncmp(arg, "no-", 3) == 0 &&
58+
strcmp(arg + 3, spec->name) == 0) {
59+
*is_negated = 1;
5460
return spec;
5561
}
5662

57-
if (spec_is_named_type(spec) &&
63+
/* Handle the typical --option arguments */
64+
if (spec_is_option_type(spec) &&
5865
spec->name &&
59-
strlen(spec->name) == namelen &&
60-
strncmp(name, spec->name, namelen) == 0)
66+
strcmp(arg, spec->name) == 0)
67+
return spec;
68+
69+
/* Handle --option=value arguments */
70+
if (spec->type == ADOPT_TYPE_VALUE &&
71+
eql &&
72+
strncmp(arg, spec->name, eql_pos) == 0 &&
73+
spec->name[eql_pos] == '\0') {
74+
*has_value = 1;
75+
*value = arg[eql_pos + 1] ? &arg[eql_pos + 1] : NULL;
6176
return spec;
77+
}
6278
}
6379

6480
return NULL;
6581
}
6682

67-
INLINE(const adopt_spec *) spec_byalias(adopt_parser *parser, char alias)
83+
INLINE(const adopt_spec *) spec_for_short(
84+
const char **value,
85+
const adopt_parser *parser,
86+
const char *arg)
6887
{
6988
const adopt_spec *spec;
7089

7190
for (spec = parser->specs; spec->type; ++spec) {
72-
if (spec_is_named_type(spec) && alias == spec->alias)
91+
/* Handle -svalue short options with a value */
92+
if (spec->type == ADOPT_TYPE_VALUE &&
93+
arg[1] == spec->alias &&
94+
arg[2] != '\0') {
95+
*value = &arg[2];
7396
return spec;
97+
}
98+
99+
/* Handle typical -s short options */
100+
if (arg[1] == spec->alias &&
101+
arg[2] == '\0') {
102+
*value = NULL;
103+
return spec;
104+
}
74105
}
75106

76107
return NULL;
77108
}
78109

79-
INLINE(const adopt_spec *) spec_nextarg(adopt_parser *parser)
110+
INLINE(const adopt_spec *) spec_for_arg(adopt_parser *parser)
80111
{
81112
const adopt_spec *spec;
82113
size_t args = 0;
@@ -127,15 +158,13 @@ INLINE(void) consume_choices(const adopt_spec *spec, adopt_parser *parser)
127158
static adopt_status_t parse_long(adopt_opt *opt, adopt_parser *parser)
128159
{
129160
const adopt_spec *spec;
130-
char *arg = parser->args[parser->idx++], *name = arg + 2, *eql;
131-
int converse = 0;
132-
size_t namelen;
133-
134-
namelen = (eql = strrchr(arg, '=')) ? (size_t)(eql - name) : strlen(name);
161+
char *arg = parser->args[parser->idx++];
162+
const char *value = NULL;
163+
int is_negated = 0, has_value = 0;
135164

136165
opt->arg = arg;
137166

138-
if ((spec = spec_byname(parser, name, namelen, &converse)) == NULL) {
167+
if ((spec = spec_for_long(&is_negated, &has_value, &value, parser, arg)) == NULL) {
139168
opt->spec = NULL;
140169
opt->status = ADOPT_STATUS_UNKNOWN_OPTION;
141170
goto done;
@@ -147,16 +176,18 @@ static adopt_status_t parse_long(adopt_opt *opt, adopt_parser *parser)
147176
if (spec->type == ADOPT_TYPE_LITERAL)
148177
parser->in_literal = 1;
149178

179+
/* --bool or --no-bool */
150180
else if (spec->type == ADOPT_TYPE_BOOL && spec->value)
151-
*((int *)spec->value) = !converse;
181+
*((int *)spec->value) = !is_negated;
152182

183+
/* --switch */
153184
else if (spec->type == ADOPT_TYPE_SWITCH && spec->value)
154185
*((int *)spec->value) = spec->switch_value;
155186

156187
/* Parse values as "--foo=bar" or "--foo bar" */
157188
else if (spec->type == ADOPT_TYPE_VALUE) {
158-
if (eql && *(eql+1))
159-
opt->value = eql + 1;
189+
if (has_value)
190+
opt->value = (char *)value;
160191
else if ((parser->idx + 1) <= parser->args_len)
161192
opt->value = parser->args[parser->idx++];
162193

@@ -181,11 +212,12 @@ static adopt_status_t parse_long(adopt_opt *opt, adopt_parser *parser)
181212
static adopt_status_t parse_short(adopt_opt *opt, adopt_parser *parser)
182213
{
183214
const adopt_spec *spec;
184-
char *arg = parser->args[parser->idx++], alias = *(arg + 1);
215+
char *arg = parser->args[parser->idx++];
216+
const char *value;
185217

186218
opt->arg = arg;
187219

188-
if ((spec = spec_byalias(parser, alias)) == NULL) {
220+
if ((spec = spec_for_short(&value, parser, arg)) == NULL) {
189221
opt->spec = NULL;
190222
opt->status = ADOPT_STATUS_UNKNOWN_OPTION;
191223
goto done;
@@ -201,8 +233,8 @@ static adopt_status_t parse_short(adopt_opt *opt, adopt_parser *parser)
201233

202234
/* Parse values as "-ifoo" or "-i foo" */
203235
if (spec->type == ADOPT_TYPE_VALUE) {
204-
if (strlen(arg) > 2)
205-
opt->value = arg + 2;
236+
if (value)
237+
opt->value = (char *)value;
206238
else if ((parser->idx + 1) <= parser->args_len)
207239
opt->value = parser->args[parser->idx++];
208240

@@ -224,7 +256,7 @@ static adopt_status_t parse_short(adopt_opt *opt, adopt_parser *parser)
224256

225257
static adopt_status_t parse_arg(adopt_opt *opt, adopt_parser *parser)
226258
{
227-
const adopt_spec *spec = spec_nextarg(parser);
259+
const adopt_spec *spec = spec_for_arg(parser);
228260

229261
opt->spec = spec;
230262
opt->arg = parser->args[parser->idx];
@@ -236,7 +268,10 @@ static adopt_status_t parse_arg(adopt_opt *opt, adopt_parser *parser)
236268
if (spec->value)
237269
*((char ***)spec->value) = &parser->args[parser->idx];
238270

239-
/* Args consume all the remaining arguments. */
271+
/*
272+
* We have started a list of arguments; the remainder of
273+
* given arguments need not be examined.
274+
*/
240275
parser->in_args = (parser->args_len - parser->idx);
241276
parser->idx = parser->args_len;
242277
opt->args_len = parser->in_args;
@@ -252,11 +287,32 @@ static adopt_status_t parse_arg(adopt_opt *opt, adopt_parser *parser)
252287
return opt->status;
253288
}
254289

290+
static int support_gnu_style(unsigned int flags)
291+
{
292+
if ((flags & ADOPT_PARSE_FORCE_GNU) != 0)
293+
return 1;
294+
295+
if ((flags & ADOPT_PARSE_GNU) == 0)
296+
return 0;
297+
298+
/* TODO: Windows */
299+
#if defined(_WIN32) && defined(UNICODE)
300+
if (_wgetenv(L"POSIXLY_CORRECT") != NULL)
301+
return 0;
302+
#else
303+
if (getenv("POSIXLY_CORRECT") != NULL)
304+
return 0;
305+
#endif
306+
307+
return 1;
308+
}
309+
255310
void adopt_parser_init(
256311
adopt_parser *parser,
257312
const adopt_spec specs[],
258313
char **args,
259-
size_t args_len)
314+
size_t args_len,
315+
unsigned int flags)
260316
{
261317
assert(parser);
262318

@@ -265,6 +321,103 @@ void adopt_parser_init(
265321
parser->specs = specs;
266322
parser->args = args;
267323
parser->args_len = args_len;
324+
parser->flags = flags;
325+
326+
parser->needs_sort = support_gnu_style(flags);
327+
}
328+
329+
INLINE(const adopt_spec *) spec_for_sort(
330+
int *needs_value,
331+
const adopt_parser *parser,
332+
const char *arg)
333+
{
334+
int is_negated, has_value = 0;
335+
const char *value;
336+
const adopt_spec *spec = NULL;
337+
338+
*needs_value = 0;
339+
340+
if (strncmp(arg, "--", 2) == 0) {
341+
spec = spec_for_long(&is_negated, &has_value, &value, parser, arg);
342+
*needs_value = !has_value;
343+
}
344+
345+
else if (strncmp(arg, "-", 1) == 0) {
346+
spec = spec_for_short(&value, parser, arg);
347+
*needs_value = (value == NULL);
348+
}
349+
350+
return spec;
351+
}
352+
353+
/*
354+
* Some parsers allow for handling arguments like "file1 --help file2";
355+
* this is done by re-sorting the arguments in-place; emulate that.
356+
*/
357+
static int sort_gnu_style(adopt_parser *parser)
358+
{
359+
size_t i, j, insert_idx = parser->idx, offset;
360+
const adopt_spec *spec;
361+
char *option, *value;
362+
int needs_value, changed = 0;
363+
364+
parser->needs_sort = 0;
365+
366+
for (i = parser->idx; i < parser->args_len; i++) {
367+
spec = spec_for_sort(&needs_value, parser, parser->args[i]);
368+
369+
/* Not a "-" or "--" prefixed option. No change. */
370+
if (!spec)
371+
continue;
372+
373+
/* A "--" alone means remaining args are literal. */
374+
if (spec->type == ADOPT_TYPE_LITERAL)
375+
break;
376+
377+
option = parser->args[i];
378+
379+
/*
380+
* If the argument is a value type and doesn't already
381+
* have a value (eg "--foo=bar" or "-fbar") then we need
382+
* to copy the next argument as its value.
383+
*/
384+
if (spec->type == ADOPT_TYPE_VALUE && needs_value) {
385+
/*
386+
* A required value is not provided; set parser
387+
* index to this value so that we fail on it.
388+
*/
389+
if (i + 1 >= parser->args_len) {
390+
parser->idx = i;
391+
return 1;
392+
}
393+
394+
value = parser->args[i + 1];
395+
offset = 1;
396+
} else {
397+
value = NULL;
398+
offset = 0;
399+
}
400+
401+
/* Caller error if args[0] is an option. */
402+
if (i == 0)
403+
return 0;
404+
405+
/* Shift args up one (or two) and insert the option */
406+
for (j = i; j > insert_idx; j--)
407+
parser->args[j + offset] = parser->args[j - 1];
408+
409+
parser->args[insert_idx] = option;
410+
411+
if (value)
412+
parser->args[insert_idx + 1] = value;
413+
414+
insert_idx += (1 + offset);
415+
i += offset;
416+
417+
changed = 1;
418+
}
419+
420+
return changed;
268421
}
269422

270423
adopt_status_t adopt_parser_next(adopt_opt *opt, adopt_parser *parser)
@@ -278,19 +431,26 @@ adopt_status_t adopt_parser_next(adopt_opt *opt, adopt_parser *parser)
278431
return ADOPT_STATUS_DONE;
279432
}
280433

281-
/* Handle arguments in long form, those beginning with "--" */
434+
/* Handle options in long form, those beginning with "--" */
282435
if (strncmp(parser->args[parser->idx], "--", 2) == 0 &&
283436
!parser->in_literal)
284437
return parse_long(opt, parser);
285438

286-
/* Handle arguments in short form, those beginning with "-" */
439+
/* Handle options in short form, those beginning with "-" */
287440
else if (strncmp(parser->args[parser->idx], "-", 1) == 0 &&
288441
!parser->in_literal)
289442
return parse_short(opt, parser);
290443

291-
/* Handle "free" arguments, those without a dash */
292-
else
293-
return parse_arg(opt, parser);
444+
/*
445+
* We've reached the first "bare" argument. In POSIX mode, all
446+
* remaining items on the command line are arguments. In GNU
447+
* mode, there may be long or short options after this. Sort any
448+
* options up to this position then re-parse the current position.
449+
*/
450+
if (parser->needs_sort && sort_gnu_style(parser))
451+
return adopt_parser_next(opt, parser);
452+
453+
return parse_arg(opt, parser);
294454
}
295455

296456
INLINE(int) spec_included(const adopt_spec **specs, const adopt_spec *spec)
@@ -361,7 +521,7 @@ adopt_status_t adopt_parse(
361521
const adopt_spec **given_specs;
362522
size_t given_idx = 0;
363523

364-
adopt_parser_init(&parser, specs, args, args_len);
524+
adopt_parser_init(&parser, specs, args, args_len, flags);
365525

366526
given_specs = alloca(sizeof(const adopt_spec *) * (args_len + 1));
367527

0 commit comments

Comments
 (0)