Skip to content

Commit 5ac6efe

Browse files
committed
requirements: enforce requirements in adopt_parse
When performing a simple, complete parsing in `adopt_parse`, enforce required arguments that are missing. `adopt_parse` now returns `ADOPT_STATUS_MISSING_ARGUMENT` if any argument is missing, and `adopt_status_fprint` has learned how to print information about missing arguments.
1 parent 33a5114 commit 5ac6efe

File tree

5 files changed

+208
-20
lines changed

5 files changed

+208
-20
lines changed

adopt.c

Lines changed: 131 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -247,30 +247,117 @@ adopt_status_t adopt_parser_next(adopt_opt *opt, adopt_parser *parser)
247247
return parse_arg(opt, parser);
248248
}
249249

250+
INLINE(int) spec_included(const adopt_spec **specs, const adopt_spec *spec)
251+
{
252+
const adopt_spec **i;
253+
254+
for (i = specs; *i; ++i) {
255+
if (spec == *i)
256+
return 1;
257+
}
258+
259+
return 0;
260+
}
261+
262+
INLINE(int) spec_is_choice(const adopt_spec *spec)
263+
{
264+
return ((spec + 1)->type &&
265+
((spec + 1)->usage & ADOPT_USAGE_CHOICE));
266+
}
267+
268+
static adopt_status_t validate_required(
269+
adopt_opt *opt,
270+
const adopt_spec specs[],
271+
const adopt_spec **given_specs)
272+
{
273+
const adopt_spec *spec, *required;
274+
int given;
275+
276+
/*
277+
* Iterate over the possible specs to identify requirements and
278+
* ensure that those have been given on the command-line.
279+
* Note that we can have required *choices*, where one in a
280+
* list of choices must be specified.
281+
*/
282+
for (spec = specs, required = NULL, given = 0; spec->type; ++spec) {
283+
if (!required && (spec->usage & ADOPT_USAGE_REQUIRED)) {
284+
required = spec;
285+
given = 0;
286+
} else if (!required) {
287+
continue;
288+
}
289+
290+
if (!given)
291+
given = spec_included(given_specs, spec);
292+
293+
/*
294+
* Validate the requirement unless we're in a required
295+
* choice. In that case, keep the required state and
296+
* validate at the end of the choice list.
297+
*/
298+
if (!spec_is_choice(spec)) {
299+
if (!given) {
300+
opt->spec = required;
301+
opt->status = ADOPT_STATUS_MISSING_ARGUMENT;
302+
break;
303+
}
304+
305+
required = NULL;
306+
given = 0;
307+
}
308+
}
309+
310+
return opt->status;
311+
}
312+
250313
adopt_status_t adopt_parse(
251314
adopt_opt *opt,
252315
const adopt_spec specs[],
253316
char **args,
254317
size_t args_len)
255318
{
256319
adopt_parser parser;
320+
const adopt_spec **given_specs;
321+
size_t given_idx = 0;
257322

258323
adopt_parser_init(&parser, specs, args, args_len);
259324

325+
given_specs = alloca(sizeof(const adopt_spec *) * (args_len + 1));
326+
260327
while (adopt_parser_next(opt, &parser)) {
261328
if (opt->status != ADOPT_STATUS_OK &&
262-
opt->status != ADOPT_STATUS_DONE) {
263-
break;
264-
}
329+
opt->status != ADOPT_STATUS_DONE)
330+
return opt->status;
331+
332+
given_specs[given_idx++] = opt->spec;
265333
}
266334

267-
return opt->status;
335+
given_specs[given_idx] = NULL;
336+
337+
return validate_required(opt, specs, given_specs);
338+
}
339+
340+
static int spec_name_fprint(FILE *file, const adopt_spec *spec)
341+
{
342+
int error;
343+
344+
if (spec->type == ADOPT_ARG)
345+
error = fprintf(file, "%s", spec->value_name);
346+
else if (spec->type == ADOPT_ARGS)
347+
error = fprintf(file, "%s", spec->value_name);
348+
else if (spec->alias && !(spec->usage & ADOPT_USAGE_SHOW_LONG))
349+
error = fprintf(file, "-%c", spec->alias);
350+
else
351+
error = fprintf(file, "--%s", spec->name);
352+
353+
return error;
268354
}
269355

270356
int adopt_status_fprint(
271357
FILE *file,
272358
const adopt_opt *opt)
273359
{
360+
const adopt_spec *choice;
274361
int error;
275362

276363
switch (opt->status) {
@@ -284,12 +371,46 @@ int adopt_status_fprint(
284371
error = fprintf(file, "Unknown option: %s\n", opt->arg);
285372
break;
286373
case ADOPT_STATUS_MISSING_VALUE:
287-
if (strncmp(opt->arg, "--", 2) == 0)
288-
error = fprintf(file, "Option '%s' requires a value.\n",
289-
opt->spec->name);
290-
else
291-
error = fprintf(file, "Switch '%c' requires a value.\n",
292-
opt->spec->alias);
374+
if ((error = fprintf(file, "Argument '")) < 0 ||
375+
(error = spec_name_fprint(file, opt->spec)) < 0 ||
376+
(error = fprintf(file, "' requires a value.\n")) < 0)
377+
;
378+
break;
379+
case ADOPT_STATUS_MISSING_ARGUMENT:
380+
if (spec_is_choice(opt->spec)) {
381+
int is_choice = 1;
382+
383+
if ((error = fprintf(file, "One argument of")) < 0)
384+
break;
385+
386+
for (choice = opt->spec; is_choice; ++choice) {
387+
is_choice = spec_is_choice(choice);
388+
389+
if (!is_choice)
390+
error = fprintf(file, " or");
391+
else if (choice != opt->spec)
392+
error = fprintf(file, ",");
393+
394+
if ((error < 0) ||
395+
(error = fprintf(file, " '")) < 0 ||
396+
(error = spec_name_fprint(file, choice)) < 0 ||
397+
(error = fprintf(file, "'")) < 0)
398+
break;
399+
400+
if (!spec_is_choice(choice))
401+
break;
402+
}
403+
404+
if ((error < 0) ||
405+
(error = fprintf(file, " is required.\n")) < 0)
406+
break;
407+
} else {
408+
if ((error = fprintf(file, "Argument '")) < 0 ||
409+
(error = spec_name_fprint(file, opt->spec)) < 0 ||
410+
(error = fprintf(file, "' is required.\n")) < 0)
411+
break;
412+
}
413+
293414
break;
294415
default:
295416
error = fprintf(file, "Unknown status: %d\n", opt->status);

adopt.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ typedef enum {
142142
* was provided.
143143
*/
144144
ADOPT_STATUS_MISSING_VALUE = 3,
145+
146+
/** A required argument was not provided. */
147+
ADOPT_STATUS_MISSING_ARGUMENT = 4,
145148
} adopt_status_t;
146149

147150
/** An option provided on the command-line. */

examples/loop.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ adopt_spec opt_specs[] = {
2121
{ ADOPT_BOOL, "verbose", 'v', &verbose, 0,
2222
NULL, "Turn on verbose information", 0 },
2323
{ ADOPT_SWITCH, "quiet", 'q', &volume, 0,
24-
NULL, "Emit no output", 0 },
24+
NULL, "Emit no output", ADOPT_USAGE_REQUIRED },
2525
{ ADOPT_SWITCH, "loud", 'l', &volume, 2,
2626
NULL, "Emit louder than usual output", ADOPT_USAGE_CHOICE },
2727
{ ADOPT_VALUE, "channel", 'c', &channel, 0,

examples/parse.c

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ static char **other = NULL;
1919

2020
adopt_spec opt_specs[] = {
2121
{ ADOPT_BOOL, "verbose", 'v', &verbose, 0,
22-
NULL, "Turn on verbose information", 0 },
22+
NULL, "Turn on verbose information", 0 },
2323
{ ADOPT_SWITCH, "quiet", 'q', &volume, 0,
24-
NULL, "Emit no output", 0 },
24+
NULL, "Emit no output", ADOPT_USAGE_REQUIRED },
2525
{ ADOPT_SWITCH, "loud", 'l', &volume, 2,
2626
NULL, "Emit louder than usual output", ADOPT_USAGE_CHOICE },
2727
{ ADOPT_VALUE, "channel", 'c', &channel, 0,
@@ -55,18 +55,12 @@ int main(int argc, char **argv)
5555
adopt_opt result;
5656
size_t i;
5757

58-
if (adopt_parse(&result, opt_specs, argv + 1, argc - 1) < 0) {
58+
if (adopt_parse(&result, opt_specs, argv + 1, argc - 1) != 0) {
5959
adopt_status_fprint(stderr, &result);
6060
adopt_usage_fprint(stderr, argv[0], opt_specs);
6161
return 129;
6262
}
6363

64-
if (!filename1) {
65-
fprintf(stderr, "filename is required\n");
66-
adopt_usage_fprint(stderr, argv[0], opt_specs);
67-
return 129;
68-
}
69-
7064
printf("verbose: %d\n", verbose);
7165
printf("volume: %s\n", volume_tostr(volume));
7266
printf("channel: %s\n", channel ? channel : "(null)");

tests/adopt.c

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,3 +755,73 @@ void test_adopt__parse_args_implies_literal(void)
755755
cl_assert_equal_s("--bar", argz[2]);
756756
}
757757

758+
void test_adopt__value_required(void)
759+
{
760+
int foo = 0;
761+
char *bar = NULL, **argz = NULL;
762+
adopt_opt result;
763+
764+
adopt_spec specs[] = {
765+
{ ADOPT_SWITCH, "foo", 'f', &foo, 'f' },
766+
{ ADOPT_VALUE, "bar", 0, &bar, 'b' },
767+
{ ADOPT_ARGS, "argz", 0, &argz, 0 },
768+
{ 0 },
769+
};
770+
771+
char *args[] = { "-f", "--bar" };
772+
773+
cl_must_pass(adopt_parse(&result, specs, args, 2));
774+
775+
cl_assert_equal_i(ADOPT_STATUS_MISSING_VALUE, result.status);
776+
}
777+
778+
void test_adopt__required_choice_missing(void)
779+
{
780+
int foo = 0;
781+
char *bar = NULL, **argz = NULL;
782+
adopt_opt result;
783+
784+
adopt_spec specs[] = {
785+
{ ADOPT_SWITCH, "foo", 'f', &foo, 'f', NULL, NULL, ADOPT_USAGE_REQUIRED },
786+
{ ADOPT_VALUE, "bar", 0, &bar, 'b', NULL, NULL, ADOPT_USAGE_CHOICE },
787+
{ ADOPT_ARGS, "argz", 0, &argz, 0, NULL, NULL, 0 },
788+
{ 0 },
789+
};
790+
791+
char *args[] = { "foo", "bar" };
792+
793+
cl_assert_equal_i(ADOPT_STATUS_MISSING_ARGUMENT, adopt_parse(&result, specs, args, 2));
794+
795+
cl_assert_equal_i(ADOPT_STATUS_MISSING_ARGUMENT, result.status);
796+
cl_assert_equal_s("foo", result.spec->name);
797+
cl_assert_equal_i('f', result.spec->alias);
798+
cl_assert_equal_p(NULL, result.arg);
799+
cl_assert_equal_p(NULL, result.value);
800+
cl_assert_equal_i(2, result.args_len);
801+
}
802+
803+
void test_adopt__required_choice_specified(void)
804+
{
805+
int foo = 0;
806+
char *bar = NULL, *baz = NULL, **argz = NULL;
807+
adopt_opt result;
808+
809+
adopt_spec specs[] = {
810+
{ ADOPT_SWITCH, "foo", 'f', &foo, 'f', NULL, NULL, ADOPT_USAGE_REQUIRED },
811+
{ ADOPT_VALUE, "bar", 0, &bar, 'b', NULL, NULL, ADOPT_USAGE_CHOICE },
812+
{ ADOPT_ARG, "baz", 0, &baz, 'z', NULL, NULL, ADOPT_USAGE_REQUIRED },
813+
{ ADOPT_ARGS, "argz", 0, &argz, 0, NULL, NULL, 0 },
814+
{ 0 },
815+
};
816+
817+
char *args[] = { "--bar" };
818+
819+
cl_assert_equal_i(ADOPT_STATUS_MISSING_ARGUMENT, adopt_parse(&result, specs, args, 2));
820+
821+
cl_assert_equal_i(ADOPT_STATUS_MISSING_ARGUMENT, result.status);
822+
cl_assert_equal_s("baz", result.spec->name);
823+
cl_assert_equal_i(0, result.spec->alias);
824+
cl_assert_equal_p(NULL, result.arg);
825+
cl_assert_equal_p(NULL, result.value);
826+
cl_assert_equal_i(0, result.args_len);
827+
}

0 commit comments

Comments
 (0)