Skip to content

Commit 4ca9af4

Browse files
authored
Merge pull request #21 from cgay/keyed-option
Fix <keyed-option> parsing and add test
2 parents 18bc948 + 1753d21 commit 4ca9af4

File tree

3 files changed

+67
-22
lines changed

3 files changed

+67
-22
lines changed

command-line-parser.dylan

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,8 @@ define function tokens-remaining?
320320
~parser.parser-tokens.empty?
321321
end;
322322

323+
// TODO(cgay): This says it returns false-or, but it doesn't. I think it would be an
324+
// improvement to make it do that and get rid of tokens-remaining?.
323325
define function peek-token
324326
(parser :: <command>) => (token :: false-or(<token>))
325327
unless (tokens-remaining?(parser))
@@ -611,8 +613,8 @@ define function tokenize-args
611613
end
612614
end,
613615
// Add a token to our deque
614-
method token (class :: <class>, value :: <string>,
615-
#rest keys, #key, #all-keys) => ()
616+
method add-token (class :: <class>, value :: <string>,
617+
#rest keys, #key, #all-keys) => ()
616618
apply(add-argument-token, parser, class, value, keys);
617619
end,
618620
method parse-short-option (arg)
@@ -624,14 +626,13 @@ define function tokenize-args
624626
& opt.option-might-have-parameters?
625627
& i + 1 < arg.size)
626628
// Take rest of argument, and use it as a parameter.
627-
token(<short-option-token>, name, tightly-bound?: #t);
628-
token(<argument-token>,
629-
copy-sequence(arg, start: i + 1));
629+
add-token(<short-option-token>, name, tightly-bound?: #t);
630+
add-token(<argument-token>, copy-sequence(arg, start: i + 1));
630631
done();
631632
else
632-
// A solitary option with no parameter.
633-
// TODO(cgay): why do we not exit the loop here??
634-
token(<short-option-token>, name);
633+
// A solitary option with no parameter. Do not exit the loop because there
634+
// may be multiple short options strung together as in 'ls -ltR'.
635+
add-token(<short-option-token>, name);
635636
end if;
636637
end for;
637638
end block;
@@ -640,22 +641,22 @@ define function tokenize-args
640641
let arg = pop(args);
641642
case
642643
(arg = "=") =>
643-
token(<equals-token>, "=");
644-
token(<argument-token>, next-arg());
644+
add-token(<equals-token>, "=");
645+
add-token(<argument-token>, next-arg());
645646

646647
starts-with?(arg, "--") =>
647-
token(<long-option-token>, copy-sequence(arg, start: 2));
648+
add-token(<long-option-token>, copy-sequence(arg, start: 2));
648649

649650
starts-with?(arg, "-") =>
650651
if (arg.size = 1)
651652
// Probably a fake filename representing stdin ('cat -')
652-
token(<argument-token>, "-");
653+
add-token(<argument-token>, "-");
653654
else
654655
parse-short-option(arg);
655656
end if;
656657

657658
otherwise =>
658-
token(<argument-token>, arg);
659+
add-token(<argument-token>, arg);
659660
end case;
660661
end until;
661662
end function tokenize-args;

parsers.dylan

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -195,14 +195,23 @@ define method parse-option
195195
=> ()
196196
pop-token(parser);
197197
let key = pop-token(parser).token-value;
198-
let value =
199-
if (instance?(peek-token(parser), <equals-token>))
200-
pop-token(parser);
201-
parse-option-value(pop-token(parser).token-value,
202-
option.option-type)
203-
else
204-
#t
205-
end;
198+
let equal-pos = find-key(key, curry(\=, '='));
199+
let value = if (equal-pos)
200+
// We have `-D key=value` as tokens #("-D", "key=value"). The "key=value"
201+
// can't be further broken down into tokens by the tokenizer because it
202+
// doesn't know that it is parsing a <keyed-option> at that point, so do
203+
// it here.
204+
let v = copy-sequence(key, start: equal-pos + 1);
205+
key := copy-sequence(key, end: equal-pos);
206+
parse-option-value(v, option.option-type);
207+
elseif (tokens-remaining?(parser)
208+
& instance?(peek-token(parser), <equals-token>))
209+
pop-token(parser);
210+
parse-option-value(pop-token(parser).token-value,
211+
option.option-type)
212+
else
213+
#t
214+
end;
206215
option.option-value[key] := value;
207216
end method parse-option;
208217

tests/options-test.dylan

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
Module: command-line-parser-test-suite
22

3+
// Note: when debugging tests it can be useful to wrap them in
4+
// block ()
5+
// ...
6+
// exception (ex :: <usage-error>)
7+
// signal(make(<simple-error>, ...))
8+
// end;
9+
// so that Testworks' --debug=crashes works. This is only an issue when debugging
10+
// command-line-parser tests. We might want to just wrap them all in a macro that
11+
// does that.
12+
313
define method parse-one (option, argv)
414
let p = make(<command-line-parser>,
515
help: "x",
@@ -64,7 +74,32 @@ define test test-parameter-option ()
6474
end test;
6575

6676
define test test-keyed-option ()
67-
end;
77+
local method opt ()
78+
// Make a new one each time to ensure option-value slot cleared.
79+
make(<keyed-option>, names: #("define", "D"), help: "help")
80+
end;
81+
let cmd = parse-one(opt(), #[]);
82+
assert-true(empty?(get-option-value(cmd, "define")));
83+
assert-true(empty?(get-option-value(cmd, "D")));
84+
85+
// When no "=value" given value defaults to #t?
86+
let cmd = parse-one(opt(), #["-Dkey"]);
87+
assert-equal(1, size(get-option-value(cmd, "D")));
88+
assert-equal(#t, get-option-value(cmd, "define")["key"]);
89+
90+
// The rest should all set "key" => "value".
91+
for (args in #(#("-Dkey=value"),
92+
#("-D", "key=value"),
93+
#("-D", "key", "=", "value"),
94+
#("--define", "key=value"),
95+
#("--define", "key", "=", "value")))
96+
let cmd = parse-one(opt(), args);
97+
let table = get-option-value(cmd, "define");
98+
assert-true(table);
99+
assert-equal(1, size(table));
100+
assert-equal("value", table["key"]);
101+
end;
102+
end test;
68103

69104
define test test-repeated-parameter-option ()
70105
end;

0 commit comments

Comments
 (0)