Skip to content

Commit 29b6220

Browse files
committed
allow for accumulators
An "accumulator" allows a user to specify the same argument multiple times to increase its value. For example, you may want to turn on verbose mode with "-v", and very verbose mode with "-vv", and insanely verbose mode with "-vvv", etc.
1 parent 47e5022 commit 29b6220

File tree

4 files changed

+141
-14
lines changed

4 files changed

+141
-14
lines changed

README.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@ Types of options
1515
----------------
1616

1717
* Boolean values are options that do not take a value, and are
18-
either set or unset, for example `-v` or `--verbose`. Booleans
19-
are shorthand for switches that are assigned a value of `1` when
20-
present.
18+
either set or unset, for example `-v` or `--verbose`. If a
19+
boolean has a long name (eg `--verbose`) then it implicitly has
20+
a negation prefixed by `no-` (in this case, `--no-verbose`).
21+
The given value will be set to `1` when the boolean is given on
22+
the command line and `0` when its negation is given.
23+
* Accumulators are options that can be provided multiple times to
24+
increase its value. For example, `-v` will set verbosity to `1`,
25+
but `-vvv` will set verbosity to `3`.
2126
* Switches are options that do not take a value on the command
2227
line, for example `--long` or `--short`. When a switch is present
2328
on the command line, a variable will be set to a predetermined value.
@@ -41,15 +46,21 @@ Options should be specified as an array of `adopt_spec` elements,
4146
elements, terminated with an `adopt_spec` initialized to zeroes.
4247

4348
```c
49+
int debug = 0;
4450
int verbose = 0;
4551
int volume = 1;
4652
char *channel = "default";
4753
char *filename1 = NULL;
4854
char *filename2 = NULL;
4955

5056
adopt_spec opt_specs[] = {
51-
/* `verbose`, above, will be set to `1` when specified. */
52-
{ ADOPT_BOOL, "verbose", 'v', &verbose },
57+
/* `verbose`, above, will increment each time `-v` is specified. */
58+
{ ADOPT_ACCUMULATOR, "verbose", 'v', &verbose },
59+
60+
/* `debug`, above, will be set to `1` when `--debug` is specified,
61+
* or to `0` if `--no-debug` is specified.
62+
*/
63+
{ ADOPT_BOOL, "debug", 'd', &debug },
5364

5465
/* `volume` will be set to `0` when `--quiet` is specified, and
5566
* set to `2` when `--loud` is specified. if neither is specified,

adopt.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ static adopt_status_t parse_long(adopt_opt *opt, adopt_parser *parser)
180180
else if (spec->type == ADOPT_TYPE_BOOL && spec->value)
181181
*((int *)spec->value) = !is_negated;
182182

183+
/* --accumulate */
184+
else if (spec->type == ADOPT_TYPE_ACCUMULATOR && spec->value)
185+
*((int *)spec->value) += spec->switch_value ? spec->switch_value : 1;
186+
183187
/* --switch */
184188
else if (spec->type == ADOPT_TYPE_SWITCH && spec->value)
185189
*((int *)spec->value) = spec->switch_value;
@@ -228,6 +232,9 @@ static adopt_status_t parse_short(adopt_opt *opt, adopt_parser *parser)
228232
if (spec->type == ADOPT_TYPE_BOOL && spec->value)
229233
*((int *)spec->value) = 1;
230234

235+
else if (spec->type == ADOPT_TYPE_ACCUMULATOR && spec->value)
236+
*((int *)spec->value) += spec->switch_value ? spec->switch_value : 1;
237+
231238
if (spec->type == ADOPT_TYPE_SWITCH && spec->value)
232239
*((int *)spec->value) = spec->switch_value;
233240

adopt.h

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,27 @@ typedef enum {
2929
ADOPT_TYPE_BOOL,
3030

3131
/**
32-
* An option that, when specified, sets the given `value_ptr`
33-
* to the given `value`.
32+
* An option that, when specified, sets the given `value` pointer
33+
* to the specified `switch_value`. This is useful for booleans
34+
* where you do not want the implicit negation that comes with an
35+
* `ADOPT_TYPE_BOOL`, or for switches that multiplex a value, like
36+
* setting a mode. For example, `--read` may set the `value` to
37+
* `MODE_READ` and `--write` may set the `value` to `MODE_WRITE`.
3438
*/
3539
ADOPT_TYPE_SWITCH,
3640

37-
/** An option that has a value ("-nvalue" or "--name value") */
41+
/**
42+
* An option that, when specified, increments the given
43+
* `value` by the given `switch_value`. This can be specified
44+
* multiple times to continue to increment the `value`.
45+
* (For example, "-vvv" to set verbosity to 3.)
46+
*/
47+
ADOPT_TYPE_ACCUMULATOR,
48+
49+
/**
50+
* An option that takes a value, for example `-n value`,
51+
* `-nvalue`, `--name value` or `--name=value`.
52+
*/
3853
ADOPT_TYPE_VALUE,
3954

4055
/**
@@ -46,10 +61,21 @@ typedef enum {
4661
*/
4762
ADOPT_TYPE_LITERAL,
4863

49-
/** A single bare argument ("path") */
64+
/**
65+
* A single argument, not an option. When options are exhausted,
66+
* arguments will be matches in the order that they're specified
67+
* in the spec list. For example, if two `ADOPT_TYPE_ARGS` are
68+
* specified, `input_file` and `output_file`, then the first bare
69+
* argument on the command line will be `input_file` and the
70+
* second will be `output_file`.
71+
*/
5072
ADOPT_TYPE_ARG,
5173

52-
/** Unmatched arguments, a collection of bare arguments ("paths...") */
74+
/**
75+
* A collection of arguments. This is useful when you want to take
76+
* a list of arguments, for example, multiple paths. When specified,
77+
* the value will be set to the first argument in the list.
78+
*/
5379
ADOPT_TYPE_ARGS,
5480
} adopt_type_t;
5581

@@ -123,8 +149,13 @@ typedef struct adopt_spec {
123149
* to an `int` that will be set to `1` if the option is specified.
124150
*
125151
* If this spec is of type `ADOPT_TYPE_SWITCH`, this is a pointer
126-
* to an `int` that will be set to the opt's `value` (below) when
127-
* this option is specified.
152+
* to an `int` that will be set to the opt's `switch_value` (below)
153+
* when this option is specified.
154+
*
155+
* If this spec is of type `ADOPT_TYPE_ACCUMULATOR`, this is a
156+
* pointer to an `int` that will be incremented by the opt's
157+
* `switch_value` (below). If no `switch_value` is provided then
158+
* the value will be incremented by 1.
128159
*
129160
* If this spec is of type `ADOPT_TYPE_VALUE`,
130161
* `ADOPT_TYPE_VALUE_OPTIONAL`, or `ADOPT_TYPE_ARG`, this is
@@ -139,8 +170,10 @@ typedef struct adopt_spec {
139170

140171
/**
141172
* If this spec is of type `ADOPT_TYPE_SWITCH`, this is the value
142-
* to set in the option's `value_ptr` pointer when it is specified.
143-
* This is ignored for other opt types.
173+
* to set in the option's `value` pointer when it is specified. If
174+
* this spec is of type `ADOPT_TYPE_ACCUMULATOR`, this is the value
175+
* to increment in the option's `value` pointer when it is
176+
* specified. This is ignored for other opt types.
144177
*/
145178
int switch_value;
146179

tests/adopt.c

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,82 @@ void test_adopt__parse_arg_mixed_with_switches(void)
701701
cl_assert_equal_p(NULL, result.value);
702702
}
703703

704+
void test_adopt__accumulator(void)
705+
{
706+
int foo = 0;
707+
adopt_opt result;
708+
char *argz;
709+
710+
char *args_zero[] = { "foo", "bar", "baz" };
711+
char *args_one[] = { "-f", "foo", "bar", "baz" };
712+
char *args_two[] = { "-f", "-f", "foo", "bar", "baz" };
713+
char *args_four[] = { "-f", "-f", "-f", "-f", "foo", "bar", "baz" };
714+
715+
adopt_spec specs[] = {
716+
{ ADOPT_TYPE_ACCUMULATOR, "foo", 'f', &foo, 0 },
717+
{ ADOPT_TYPE_ARGS, "argz", 0, &argz, 0 },
718+
{ 0 },
719+
};
720+
721+
foo = 0;
722+
cl_must_pass(adopt_parse(&result, specs, args_zero, 3, ADOPT_PARSE_DEFAULT));
723+
cl_assert_equal_i(ADOPT_STATUS_DONE, result.status);
724+
cl_assert_equal_i(0, foo);
725+
726+
foo = 0;
727+
cl_must_pass(adopt_parse(&result, specs, args_one, 4, ADOPT_PARSE_DEFAULT));
728+
cl_assert_equal_i(ADOPT_STATUS_DONE, result.status);
729+
cl_assert_equal_i(1, foo);
730+
731+
foo = 0;
732+
cl_must_pass(adopt_parse(&result, specs, args_two, 5, ADOPT_PARSE_DEFAULT));
733+
cl_assert_equal_i(ADOPT_STATUS_DONE, result.status);
734+
cl_assert_equal_i(2, foo);
735+
736+
foo = 0;
737+
cl_must_pass(adopt_parse(&result, specs, args_four, 7, ADOPT_PARSE_DEFAULT));
738+
cl_assert_equal_i(ADOPT_STATUS_DONE, result.status);
739+
cl_assert_equal_i(4, foo);
740+
}
741+
742+
void test_adopt__accumulator_with_custom_incrementor(void)
743+
{
744+
int foo = 0;
745+
adopt_opt result;
746+
char *argz;
747+
748+
char *args_zero[] = { "foo", "bar", "baz" };
749+
char *args_one[] = { "-f", "foo", "bar", "baz" };
750+
char *args_two[] = { "-f", "-f", "foo", "bar", "baz" };
751+
char *args_four[] = { "-f", "-f", "-f", "-f", "foo", "bar", "baz" };
752+
753+
adopt_spec specs[] = {
754+
{ ADOPT_TYPE_ACCUMULATOR, "foo", 'f', &foo, 42 },
755+
{ ADOPT_TYPE_ARGS, "argz", 0, &argz, 0 },
756+
{ 0 },
757+
};
758+
759+
foo = 0;
760+
cl_must_pass(adopt_parse(&result, specs, args_zero, 3, ADOPT_PARSE_DEFAULT));
761+
cl_assert_equal_i(ADOPT_STATUS_DONE, result.status);
762+
cl_assert_equal_i(0, foo);
763+
764+
foo = 0;
765+
cl_must_pass(adopt_parse(&result, specs, args_one, 4, ADOPT_PARSE_DEFAULT));
766+
cl_assert_equal_i(ADOPT_STATUS_DONE, result.status);
767+
cl_assert_equal_i(42, foo);
768+
769+
foo = 0;
770+
cl_must_pass(adopt_parse(&result, specs, args_two, 5, ADOPT_PARSE_DEFAULT));
771+
cl_assert_equal_i(ADOPT_STATUS_DONE, result.status);
772+
cl_assert_equal_i(84, foo);
773+
774+
foo = 0;
775+
cl_must_pass(adopt_parse(&result, specs, args_four, 7, ADOPT_PARSE_DEFAULT));
776+
cl_assert_equal_i(ADOPT_STATUS_DONE, result.status);
777+
cl_assert_equal_i(168, foo);
778+
}
779+
704780
void test_adopt__parse_arg_with_literal(void)
705781
{
706782
int foo = 0;

0 commit comments

Comments
 (0)