Skip to content

Commit edb3098

Browse files
committed
handle "compressed" short options
Short options may be compressed together, for example: "tar -xvffilename", which is the moral equivalent of "tar -x -v -f filename".
1 parent 29b6220 commit edb3098

File tree

3 files changed

+174
-19
lines changed

3 files changed

+174
-19
lines changed

adopt.c

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,8 @@ INLINE(const adopt_spec *) spec_for_long(
4343
char *eql;
4444
size_t eql_pos;
4545

46-
arg += 2;
47-
eql = strrchr(arg, '=');
48-
eql_pos = (eql = strrchr(arg, '=')) ? (size_t)(eql - arg) : strlen(arg);
46+
eql = strchr(arg, '=');
47+
eql_pos = (eql = strchr(arg, '=')) ? (size_t)(eql - arg) : strlen(arg);
4948

5049
for (spec = parser->specs; spec->type; ++spec) {
5150
/* Handle -- (everything after this is literal) */
@@ -90,15 +89,14 @@ INLINE(const adopt_spec *) spec_for_short(
9089
for (spec = parser->specs; spec->type; ++spec) {
9190
/* Handle -svalue short options with a value */
9291
if (spec->type == ADOPT_TYPE_VALUE &&
93-
arg[1] == spec->alias &&
94-
arg[2] != '\0') {
95-
*value = &arg[2];
92+
arg[0] == spec->alias &&
93+
arg[1] != '\0') {
94+
*value = &arg[1];
9695
return spec;
9796
}
9897

9998
/* Handle typical -s short options */
100-
if (arg[1] == spec->alias &&
101-
arg[2] == '\0') {
99+
if (arg[0] == spec->alias) {
102100
*value = NULL;
103101
return spec;
104102
}
@@ -164,7 +162,7 @@ static adopt_status_t parse_long(adopt_opt *opt, adopt_parser *parser)
164162

165163
opt->arg = arg;
166164

167-
if ((spec = spec_for_long(&is_negated, &has_value, &value, parser, arg)) == NULL) {
165+
if ((spec = spec_for_long(&is_negated, &has_value, &value, parser, &arg[2])) == NULL) {
168166
opt->spec = NULL;
169167
opt->status = ADOPT_STATUS_UNKNOWN_OPTION;
170168
goto done;
@@ -221,7 +219,7 @@ static adopt_status_t parse_short(adopt_opt *opt, adopt_parser *parser)
221219

222220
opt->arg = arg;
223221

224-
if ((spec = spec_for_short(&value, parser, arg)) == NULL) {
222+
if ((spec = spec_for_short(&value, parser, &arg[1 + parser->in_short])) == NULL) {
225223
opt->spec = NULL;
226224
opt->status = ADOPT_STATUS_UNKNOWN_OPTION;
227225
goto done;
@@ -235,11 +233,11 @@ static adopt_status_t parse_short(adopt_opt *opt, adopt_parser *parser)
235233
else if (spec->type == ADOPT_TYPE_ACCUMULATOR && spec->value)
236234
*((int *)spec->value) += spec->switch_value ? spec->switch_value : 1;
237235

238-
if (spec->type == ADOPT_TYPE_SWITCH && spec->value)
236+
else if (spec->type == ADOPT_TYPE_SWITCH && spec->value)
239237
*((int *)spec->value) = spec->switch_value;
240238

241239
/* Parse values as "-ifoo" or "-i foo" */
242-
if (spec->type == ADOPT_TYPE_VALUE) {
240+
else if (spec->type == ADOPT_TYPE_VALUE) {
243241
if (value)
244242
opt->value = (char *)value;
245243
else if ((parser->idx + 1) <= parser->args_len)
@@ -249,6 +247,18 @@ static adopt_status_t parse_short(adopt_opt *opt, adopt_parser *parser)
249247
*((char **)spec->value) = opt->value;
250248
}
251249

250+
/*
251+
* Handle compressed short arguments, like "-fbcd"; see if there's
252+
* another character after the one we processed. If not, advance
253+
* the parser index.
254+
*/
255+
if (spec->type != ADOPT_TYPE_VALUE && arg[2 + parser->in_short] != '\0') {
256+
parser->in_short++;
257+
parser->idx--;
258+
} else {
259+
parser->in_short = 0;
260+
}
261+
252262
/* Required argument was not provided */
253263
if (spec->type == ADOPT_TYPE_VALUE && !opt->value)
254264
opt->status = ADOPT_STATUS_MISSING_VALUE;
@@ -341,16 +351,25 @@ INLINE(const adopt_spec *) spec_for_sort(
341351
int is_negated, has_value = 0;
342352
const char *value;
343353
const adopt_spec *spec = NULL;
354+
size_t idx = 0;
344355

345356
*needs_value = 0;
346357

347358
if (strncmp(arg, "--", 2) == 0) {
348-
spec = spec_for_long(&is_negated, &has_value, &value, parser, arg);
359+
spec = spec_for_long(&is_negated, &has_value, &value, parser, &arg[2]);
349360
*needs_value = !has_value;
350361
}
351362

352363
else if (strncmp(arg, "-", 1) == 0) {
353-
spec = spec_for_short(&value, parser, arg);
364+
spec = spec_for_short(&value, parser, &arg[1]);
365+
366+
/*
367+
* Advance through compressed short arguments to see if
368+
* the last one has a value, eg "-xvffilename".
369+
*/
370+
while (spec && !value && arg[1 + ++idx] != '\0')
371+
spec = spec_for_short(&value, parser, &arg[1 + idx]);
372+
354373
*needs_value = (value == NULL);
355374
}
356375

@@ -440,12 +459,14 @@ adopt_status_t adopt_parser_next(adopt_opt *opt, adopt_parser *parser)
440459

441460
/* Handle options in long form, those beginning with "--" */
442461
if (strncmp(parser->args[parser->idx], "--", 2) == 0 &&
443-
!parser->in_literal)
462+
!parser->in_short &&
463+
!parser->in_literal)
444464
return parse_long(opt, parser);
445465

446466
/* Handle options in short form, those beginning with "-" */
447-
else if (strncmp(parser->args[parser->idx], "-", 1) == 0 &&
448-
!parser->in_literal)
467+
else if (parser->in_short ||
468+
(strncmp(parser->args[parser->idx], "-", 1) == 0 &&
469+
!parser->in_literal))
449470
return parse_short(opt, parser);
450471

451472
/*

adopt.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,9 +267,9 @@ typedef struct adopt_parser {
267267
size_t idx;
268268
size_t arg_idx;
269269
size_t in_args;
270+
size_t in_short;
270271
int needs_sort : 1,
271-
in_literal : 1,
272-
in_short : 1;
272+
in_literal : 1;
273273
} adopt_parser;
274274

275275
/**

tests/adopt.c

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -972,6 +972,140 @@ void test_adopt__parse_options_gnustyle_dangling_value(void)
972972
free(args);
973973
}
974974

975+
void test_adopt__parse_options_gnustyle_with_compressed_shorts(void)
976+
{
977+
int foo = 0, baz = 0;
978+
char *bar = NULL, **argz = NULL;
979+
char **args;
980+
adopt_opt result;
981+
982+
adopt_spec specs[] = {
983+
{ ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f' },
984+
{ ADOPT_TYPE_BOOL, "baz", 'z', &baz, 0 },
985+
{ ADOPT_TYPE_VALUE, "bar", 'b', &bar, 'b' },
986+
{ ADOPT_TYPE_ARGS, "argz", 0, &argz, 0 },
987+
{ 0 },
988+
};
989+
990+
/* allocate so that `adopt_parse` can reorder things */
991+
cl_assert(args = malloc(sizeof(char *) * 7));
992+
args[0] = "BRR";
993+
args[1] = "-fzb";
994+
args[2] = "bar";
995+
args[3] = "one";
996+
args[4] = "two";
997+
args[5] = "three";
998+
args[6] = "four";
999+
1000+
cl_must_pass(adopt_parse(&result, specs, args, 7, ADOPT_PARSE_GNU));
1001+
1002+
cl_assert_equal_i(ADOPT_STATUS_DONE, result.status);
1003+
cl_assert_equal_p(NULL, result.arg);
1004+
cl_assert_equal_p(NULL, result.value);
1005+
cl_assert_equal_i(5, result.args_len);
1006+
1007+
cl_assert_equal_i('f', foo);
1008+
cl_assert_equal_s("bar", bar);
1009+
cl_assert(argz);
1010+
cl_assert_equal_s("BRR", argz[0]);
1011+
cl_assert_equal_s("one", argz[1]);
1012+
cl_assert_equal_s("two", argz[2]);
1013+
cl_assert_equal_s("three", argz[3]);
1014+
cl_assert_equal_s("four", argz[4]);
1015+
1016+
free(args);
1017+
}
1018+
1019+
void test_adopt__compressed_shorts1(void)
1020+
{
1021+
int foo = 0, baz = 0;
1022+
char *bar = NULL, **argz = NULL;
1023+
adopt_opt result;
1024+
1025+
adopt_spec specs[] = {
1026+
{ ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f' },
1027+
{ ADOPT_TYPE_BOOL, "baz", 'z', &baz, 0 },
1028+
{ ADOPT_TYPE_VALUE, "bar", 'b', &bar, 'b' },
1029+
{ ADOPT_TYPE_ARGS, "argz", 0, &argz, 0 },
1030+
{ 0 },
1031+
};
1032+
1033+
char *args[] = { "-fzb", "asdf", "foobar" };
1034+
1035+
cl_must_pass(adopt_parse(&result, specs, args, 3, ADOPT_PARSE_DEFAULT));
1036+
1037+
cl_assert_equal_i(ADOPT_STATUS_DONE, result.status);
1038+
cl_assert_equal_p(NULL, result.arg);
1039+
cl_assert_equal_p(NULL, result.value);
1040+
cl_assert_equal_i(1, result.args_len);
1041+
1042+
cl_assert_equal_i('f', foo);
1043+
cl_assert_equal_i(1, baz);
1044+
cl_assert_equal_s("asdf", bar);
1045+
cl_assert(argz);
1046+
cl_assert_equal_s("foobar", argz[0]);
1047+
}
1048+
1049+
void test_adopt__compressed_shorts2(void)
1050+
{
1051+
int foo = 0, baz = 0;
1052+
char *bar = NULL, **argz = NULL;
1053+
adopt_opt result;
1054+
1055+
adopt_spec specs[] = {
1056+
{ ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f' },
1057+
{ ADOPT_TYPE_BOOL, "baz", 'z', &baz, 0 },
1058+
{ ADOPT_TYPE_VALUE, "bar", 'b', &bar, 'b' },
1059+
{ ADOPT_TYPE_ARGS, "argz", 0, &argz, 0 },
1060+
{ 0 },
1061+
};
1062+
1063+
char *args[] = { "-fzbasdf", "foobar" };
1064+
1065+
cl_must_pass(adopt_parse(&result, specs, args, 2, ADOPT_PARSE_DEFAULT));
1066+
1067+
cl_assert_equal_i(ADOPT_STATUS_DONE, result.status);
1068+
cl_assert_equal_p(NULL, result.arg);
1069+
cl_assert_equal_p(NULL, result.value);
1070+
cl_assert_equal_i(1, result.args_len);
1071+
1072+
cl_assert_equal_i('f', foo);
1073+
cl_assert_equal_i(1, baz);
1074+
cl_assert_equal_s("asdf", bar);
1075+
cl_assert(argz);
1076+
cl_assert_equal_s("foobar", argz[0]);
1077+
}
1078+
1079+
void test_adopt__compressed_shorts3(void)
1080+
{
1081+
int foo = 0, baz = 0;
1082+
char *bar = NULL, **argz = NULL;
1083+
adopt_opt result;
1084+
1085+
adopt_spec specs[] = {
1086+
{ ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f' },
1087+
{ ADOPT_TYPE_BOOL, "baz", 'z', &baz, 0 },
1088+
{ ADOPT_TYPE_VALUE, "bar", 'b', &bar, 'b' },
1089+
{ ADOPT_TYPE_ARGS, "argz", 0, &argz, 0 },
1090+
{ 0 },
1091+
};
1092+
1093+
char *args[] = { "-fbzasdf", "foobar" };
1094+
1095+
cl_must_pass(adopt_parse(&result, specs, args, 2, ADOPT_PARSE_DEFAULT));
1096+
1097+
cl_assert_equal_i(ADOPT_STATUS_DONE, result.status);
1098+
cl_assert_equal_p(NULL, result.arg);
1099+
cl_assert_equal_p(NULL, result.value);
1100+
cl_assert_equal_i(1, result.args_len);
1101+
1102+
cl_assert_equal_i('f', foo);
1103+
cl_assert_equal_i(0, baz);
1104+
cl_assert_equal_s("zasdf", bar);
1105+
cl_assert(argz);
1106+
cl_assert_equal_s("foobar", argz[0]);
1107+
}
1108+
9751109
void test_adopt__value_required(void)
9761110
{
9771111
int foo = 0;

0 commit comments

Comments
 (0)