Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better handling of "--" #49

Merged
merged 1 commit into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 10 additions & 21 deletions command-line-parser.dylan
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ define abstract class <command> (<object>)
slot command-subcommands :: <sequence> = #[],
init-keyword: subcommands:;
slot selected-subcommand :: false-or(<subcommand>) = #f;
// All arguments following the first "--" positional argument, if any.
slot unconsumed-arguments :: <sequence> = #();
end class;

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

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

// Parse the command line, side-effecting the parser, its options, and its
// subcommands with the parsed values. `args` is a sequence of command line
// arguments (strings). It must not include the command name.
// Parse the command line, side-effecting the parser, its options, and its subcommands
// with the parsed values. `args` is a sequence of command line arguments (strings) that
// does not include the command name.
define method parse-command-line
(parser :: <command-line-parser>, args :: <sequence>)
=> ()
Expand All @@ -721,24 +725,9 @@ define method parse-command-line
let chopped-args = chop-args(clean-args);
tokenize-args(parser, chopped-args);
process-tokens(program-name(), parser, #f);

if (~empty?(extra-args))
// Append any more positional options from after the '--'. If there's a
// subcommand the extra args go with that.
// (This feels hackish. Can we handle this directly in process-tokens?)
let command = parser.selected-subcommand | parser;
let option = last(command.command-options);
if (~(instance?(option, <positional-option>)
& option.option-repeated?))
let opts = command.positional-options;
usage-error("Only %d positional argument%s allowed.",
opts.size,
if (opts.size = 1) "" else "s" end);
end;
for (arg in extra-args)
option.option-value := add!(option.option-value,
parse-option-value(arg, option.option-type));
end for;
command.unconsumed-arguments := extra-args;
end;
end method;

Expand Down Expand Up @@ -838,7 +827,7 @@ end function;

Parameterless options:
-b, --bar, --no-bar
Present or absent. May have opposites; latter values override
Present or absent. May have opposites; later values override
previous values.

Parameter options:
Expand Down
1 change: 1 addition & 0 deletions library.dylan
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ define module command-line-parser
parse-command-line,
get-option-value,
print-help,
unconsumed-arguments,

parse-option-value;

Expand Down
17 changes: 17 additions & 0 deletions tests/options-test.dylan
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,20 @@ define test test-positional-option-parsing ()
assert-equal("d", p4.option-value);
assert-equal(#["e", "f"], p5.option-value);
end test;

define test test-unconsumed-arguments ()
let p1 = make(<positional-option>, name: "p1", help: "?", required?: #f);
let cmd1 = make(<command-line-parser>, help: "?", options: list(p1));
assert-no-errors(parse-command-line(cmd1, #["x", "--", "y", "z"]));
assert-equal(#["y", "z"], cmd1.unconsumed-arguments);
assert-no-errors(parse-command-line(cmd1, #["--", "y", "z"]));
assert-equal(#["y", "z"], cmd1.unconsumed-arguments);

// Same assertions but with the final positional option allowing more than one arg.
let p2 = make(<positional-option>, name: "p1", help: "?", required?: #f, repeated?: #t);
let cmd2 = make(<command-line-parser>, help: "?", options: list(p2));
assert-no-errors(parse-command-line(cmd2, #["x", "--", "y", "z"]));
assert-equal(#["y", "z"], cmd2.unconsumed-arguments);
assert-no-errors(parse-command-line(cmd2, #["--", "y", "z"]));
assert-equal(#["y", "z"], cmd2.unconsumed-arguments);
end test;
Loading