Skip to content

Commit 28a1fdd

Browse files
authored
Merge pull request #49 from cgay/dev
Better handling of "--"
2 parents 4e73ed6 + 0810f1e commit 28a1fdd

File tree

3 files changed

+28
-21
lines changed

3 files changed

+28
-21
lines changed

command-line-parser.dylan

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ define abstract class <command> (<object>)
114114
slot command-subcommands :: <sequence> = #[],
115115
init-keyword: subcommands:;
116116
slot selected-subcommand :: false-or(<subcommand>) = #f;
117+
// All arguments following the first "--" positional argument, if any.
118+
slot unconsumed-arguments :: <sequence> = #();
117119
end class;
118120

119121
// The --help option is added by default but we provide a way to turn it off here.
@@ -598,8 +600,10 @@ end;
598600
//======================================================================
599601

600602
// Break up our arguments around '--' in the traditional fashion.
601-
define function split-args(argv)
603+
define function split-args (argv)
602604
=> (clean-args :: <sequence>, extra-args :: <sequence>)
605+
// TODO(cgay): A <parameter-option> "--foo --" should be valid.
606+
// https://github.com/dylan-lang/command-line-parser/issues/47
603607
let splitter = find-key(argv, curry(\=, "--"));
604608
if (splitter)
605609
let clean-args = copy-sequence(argv, end: splitter);
@@ -710,9 +714,9 @@ define open generic parse-command-line
710714
(parser :: <command-line-parser>, argv :: <sequence>)
711715
=> ();
712716

713-
// Parse the command line, side-effecting the parser, its options, and its
714-
// subcommands with the parsed values. `args` is a sequence of command line
715-
// arguments (strings). It must not include the command name.
717+
// Parse the command line, side-effecting the parser, its options, and its subcommands
718+
// with the parsed values. `args` is a sequence of command line arguments (strings) that
719+
// does not include the command name.
716720
define method parse-command-line
717721
(parser :: <command-line-parser>, args :: <sequence>)
718722
=> ()
@@ -721,24 +725,9 @@ define method parse-command-line
721725
let chopped-args = chop-args(clean-args);
722726
tokenize-args(parser, chopped-args);
723727
process-tokens(program-name(), parser, #f);
724-
725728
if (~empty?(extra-args))
726-
// Append any more positional options from after the '--'. If there's a
727-
// subcommand the extra args go with that.
728-
// (This feels hackish. Can we handle this directly in process-tokens?)
729729
let command = parser.selected-subcommand | parser;
730-
let option = last(command.command-options);
731-
if (~(instance?(option, <positional-option>)
732-
& option.option-repeated?))
733-
let opts = command.positional-options;
734-
usage-error("Only %d positional argument%s allowed.",
735-
opts.size,
736-
if (opts.size = 1) "" else "s" end);
737-
end;
738-
for (arg in extra-args)
739-
option.option-value := add!(option.option-value,
740-
parse-option-value(arg, option.option-type));
741-
end for;
730+
command.unconsumed-arguments := extra-args;
742731
end;
743732
end method;
744733

@@ -838,7 +827,7 @@ end function;
838827
839828
Parameterless options:
840829
-b, --bar, --no-bar
841-
Present or absent. May have opposites; latter values override
830+
Present or absent. May have opposites; later values override
842831
previous values.
843832
844833
Parameter options:

library.dylan

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ define module command-line-parser
7070
parse-command-line,
7171
get-option-value,
7272
print-help,
73+
unconsumed-arguments,
7374

7475
parse-option-value;
7576

tests/options-test.dylan

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,3 +217,20 @@ define test test-positional-option-parsing ()
217217
assert-equal("d", p4.option-value);
218218
assert-equal(#["e", "f"], p5.option-value);
219219
end test;
220+
221+
define test test-unconsumed-arguments ()
222+
let p1 = make(<positional-option>, name: "p1", help: "?", required?: #f);
223+
let cmd1 = make(<command-line-parser>, help: "?", options: list(p1));
224+
assert-no-errors(parse-command-line(cmd1, #["x", "--", "y", "z"]));
225+
assert-equal(#["y", "z"], cmd1.unconsumed-arguments);
226+
assert-no-errors(parse-command-line(cmd1, #["--", "y", "z"]));
227+
assert-equal(#["y", "z"], cmd1.unconsumed-arguments);
228+
229+
// Same assertions but with the final positional option allowing more than one arg.
230+
let p2 = make(<positional-option>, name: "p1", help: "?", required?: #f, repeated?: #t);
231+
let cmd2 = make(<command-line-parser>, help: "?", options: list(p2));
232+
assert-no-errors(parse-command-line(cmd2, #["x", "--", "y", "z"]));
233+
assert-equal(#["y", "z"], cmd2.unconsumed-arguments);
234+
assert-no-errors(parse-command-line(cmd2, #["--", "y", "z"]));
235+
assert-equal(#["y", "z"], cmd2.unconsumed-arguments);
236+
end test;

0 commit comments

Comments
 (0)