Skip to content

Commit

Permalink
Merge pull request #19 from cgay/subcommands
Browse files Browse the repository at this point in the history
Fix subcommand option parsing
  • Loading branch information
cgay authored Apr 21, 2021
2 parents 014c500 + a72ab27 commit 18bc948
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 17 deletions.
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;

0 comments on commit 18bc948

Please sign in to comment.