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

Fix subcommand option parsing #19

Merged
merged 3 commits into from
Apr 21, 2021
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
13 changes: 3 additions & 10 deletions command-line-parser.dylan
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ copyright: See LICENSE file in this distribution.
//======================================================================
// The All-Singing, All-Dancing Argument Parser
//======================================================================
//
// Ole J. Tetlie wrote an option parser, and it was pretty good. But it
// didn't support all the option types required by d2c, and besides, we
// felt a need to overdo something.
Expand All @@ -29,20 +30,12 @@ copyright: See LICENSE file in this distribution.
// All the tokens on that command line are arguments. "-x" and "--y"
// are options, and "bar" is a parameter. "baz" is a positional argument.

// todo -- There is no indication of default values in the generated synopsis,
// and the syntax for specifying "syntax" and docstring is bizarre at
// best. --cgay 2006.11.27

// TODO(cgay): <choice-option>: --foo=a|b|c (#f as choice means option
// value is optional?)

// TODO(cgay): Add a required: (or required?: ?) init keyword that
// makes non-positional args required else an error is generated.

// TODO(cgay): This error sucks: "<unknown-option>" is not present as
// a key for {<string-table>: size 12}. How about "<unknown-option>
// is not a recognized command-line option." See next item.

// TODO(cgay): With an option that has negative options (e.g.,
// --verbose and --quiet in the same option) just show the positive
// option in the synopsis but add a comment to the doc about the
Expand Down Expand Up @@ -747,14 +740,14 @@ define function process-tokens
option.option-present? := #t;
end;
<short-option-token>, <long-option-token> =>
let option = find-option(parser, value)
let option = find-option(subcmd | parser, value)
| usage-error("Unrecognized option: %s%s",
if (value.size = 1) "-" else "--" end,
value);
if (instance?(option, <help-option>))
// Handle --help early in case the remainder of the command line is
// invalid or there are missing required arguments.
print-synopsis(parser, subcmd);
print-help(parser, subcmd);
abort-command(0);
end;
parse-option(option, parser);
Expand Down
13 changes: 8 additions & 5 deletions help.dylan
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ Module: command-line-parser
Synopsis: Implements the --help flag and help subcommand


// TODO(cgay): Automatically display option default values. It's too easy to
// forget to add %default% to the help string.

// TODO(cgay): Wrap the descriptions nicely

define function program-name () => (name :: <string>)
Expand Down Expand Up @@ -68,12 +71,12 @@ define method execute-subcommand
if (name)
let subcmd = find-subcommand(parser, name);
if (subcmd)
print-synopsis(parser, subcmd);
print-help(parser, subcmd);
else
usage-error("Subcommand %= not found.", name);
end;
else
print-synopsis(parser, #f); // 'app help' same as 'app --help'
print-help(parser, #f); // 'app help' same as 'app --help'
end;
end method;

Expand Down Expand Up @@ -146,10 +149,10 @@ define method format-option-usage
option.canonical-option-name
end;

define open generic print-synopsis
define open generic print-help
(parser :: <command-line-parser>, subcmd :: false-or(<subcommand>), #key stream);

define method print-synopsis
define method print-help
(parser :: <command-line-parser>, subcmd == #f,
#key stream :: <stream> = *standard-output*)
format(stream, "%s\n", parser.command-help);
Expand Down Expand Up @@ -177,7 +180,7 @@ define method print-synopsis
end;
end method;

define method print-synopsis
define method print-help
(parser :: <command-line-parser>, subcmd :: <subcommand>,
#key stream :: <stream> = *standard-output*)
format(stream, "%s\n", subcmd.command-help);
Expand Down
2 changes: 1 addition & 1 deletion library.dylan
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ define module command-line-parser
add-option,
parse-command-line,
get-option-value,
print-synopsis,
print-help,

parse-option-value;

Expand Down
2 changes: 1 addition & 1 deletion tests/command-line-parser-test-suite.dylan
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ end test test-command-line-parser;
define test test-synopsis-format ()
let parser = make-parser();
let synopsis = with-output-to-string (stream)
print-synopsis(parser, #f, stream: stream)
print-help(parser, #f, stream: stream)
end;
let expected = #:str:"x

Expand Down
1 change: 1 addition & 0 deletions tests/command-line-parser-test-suite.lid
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ target-type: dll
files: command-line-parser-test-suite-library
options-test
command-line-parser-test-suite
subcommands-test
31 changes: 31 additions & 0 deletions tests/subcommands-test.dylan
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Module: command-line-parser-test-suite


define class <subcommand-s1> (<subcommand>) end;

define test test-subcommand-parsing ()
let global-a = make(<flag-option>, names: #("a"), help: "a help");
let global-b = make(<parameter-option>, names: #("b"), help: "b help");
let local-c = make(<flag-option>, names: #("c"), help: "c help");
let positional-d = make(<positional-option>, names: #("d"), help: "d help");
let positional-e = make(<positional-option>,
repeated?: #t, names: #("e"), help: "e help");
let s1 = make(<subcommand-s1>, name: "s1", help: "s1 help");
add-option(s1, local-c);
add-option(s1, positional-d);
add-option(s1, positional-e);
let p = make(<command-line-parser>,
help: "main help",
subcommands: list(s1));
add-option(p, global-a);
add-option(p, global-b);

// Done with setup

assert-no-errors(parse-command-line(p, #["-a", "s1", "-c", "d", "e", "e"]));
assert-true(get-option-value(p, "a"));
assert-false(get-option-value(p, "b"));
assert-true(get-option-value(s1, "c"));
assert-equal("d", get-option-value(s1, "d"));
assert-equal(#["e", "e"], get-option-value(s1, "e"));
end test;