Skip to content

Commit 13000b5

Browse files
committed
Throw during parsing for missing mandatory args
Fixes #872 Closes #888 The exception for missing mandatory arguments was moved from parse time to the time when the options are read in order to work around an issue where trying to run the `help` command would still raise the warning about missing mandatory arguments. Remove the errors attempting to read the fields and restore the checking at parse time. Use the presence of the `help` key as a universal signal of the help command. Update the tests to expect the exception during parse time. Add a test that the default `help` command still prints exactly the intended usage.
1 parent 52e44b3 commit 13000b5

5 files changed

Lines changed: 33 additions & 27 deletions

File tree

pkgs/args/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
* Remove sorting of the subcommands in usage output. Ordering will depend on the
1010
order that `addSubCommand` is called.
1111
* Remove extra newlines following separators when using flags without help text.
12+
* Throw an exception when parsing argument lists with missing mandatory
13+
arguments instead of waiting until they are read.
1214

1315
## 2.7.0
1416

pkgs/args/lib/src/arg_results.dart

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,6 @@ class ArgResults {
7070
}
7171

7272
final option = _parser.options[name]!;
73-
if (option.mandatory && !_parsed.containsKey(name)) {
74-
throw ArgumentError('Option $name is mandatory.');
75-
}
76-
7773
return option.valueOrDefault(_parsed[name]);
7874
}
7975

@@ -102,9 +98,6 @@ class ArgResults {
10298
if (!option.isSingle) {
10399
throw ArgumentError('"$name" is a multi-option.');
104100
}
105-
if (option.mandatory && !_parsed.containsKey(name)) {
106-
throw ArgumentError('Option $name is mandatory.');
107-
}
108101
return option.valueOrDefault(_parsed[name]) as String?;
109102
}
110103

pkgs/args/lib/src/parser.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,18 +122,19 @@ class Parser {
122122
}
123123

124124
// Check if mandatory and invoke existing callbacks.
125+
final ignoreMandatoryOptions = _results.containsKey('help');
125126
_grammar.options.forEach((name, option) {
126127
var parsedOption = _results[name];
127128

128-
var callback = option.callback;
129-
if (callback == null) return;
130-
131129
// Check if an option is mandatory and was passed; if not, throw an
132130
// exception.
133-
if (option.mandatory && parsedOption == null) {
131+
if (!ignoreMandatoryOptions && option.mandatory && parsedOption == null) {
134132
throw ArgParserException('Option $name is mandatory.', null, name);
135133
}
136134

135+
var callback = option.callback;
136+
if (callback == null) return;
137+
137138
// ignore: avoid_dynamic_calls
138139
callback(option.valueOrDefault(parsedOption));
139140
});

pkgs/args/test/command_runner_test.dart

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -779,12 +779,30 @@ Run "test help" to see global options.'''));
779779
runner.addCommand(subcommand);
780780
expect(
781781
() => runner.run([subcommand.name]),
782-
throwsA(isA<ArgumentError>().having((e) => e.message, 'message',
782+
throwsA(isA<UsageException>().having((e) => e.message, 'message',
783783
contains('Option mandatory-option is mandatory'))));
784784
expect(await runner.run([subcommand.name, '--mandatory-option', 'foo']),
785785
'foo');
786786
});
787787

788+
test('mandatory options in does not interfere with help command', () async {
789+
var subcommand = _MandatoryOptionCommand();
790+
runner.addCommand(subcommand);
791+
expect(() => runner.run(['help']), prints('''
792+
A test command runner.
793+
794+
Usage: test <command> [arguments]
795+
796+
Global options:
797+
-h, --help Print this usage information.
798+
799+
Available commands:
800+
mandatory-option-command A command with a mandatory option
801+
802+
Run "test help <command>" for more information about a command.
803+
'''));
804+
});
805+
788806
test('default command runs', () {
789807
final defaultSubcommand = FooCommand();
790808
runner.addCommand(defaultSubcommand, isDefault: true);

pkgs/args/test/parse_test.dart

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -594,34 +594,26 @@ void main() {
594594
test('throw if no args', () {
595595
var parser = ArgParser();
596596
parser.addOption('username', mandatory: true);
597-
var results = parser.parse([]);
598-
expect(() => results['username'], throwsA(isA<ArgumentError>()));
597+
throwsArgParserException(
598+
parser, [], 'Option username is mandatory.', [], 'username');
599599
});
600600

601601
test('throw if no mandatory args', () {
602602
var parser = ArgParser();
603603
parser.addOption('test');
604604
parser.addOption('username', mandatory: true);
605-
var results = parser.parse(['--test', 'test']);
606-
expect(results['test'], equals('test'));
607-
expect(() => results['username'], throwsA(isA<ArgumentError>()));
608-
});
609-
610-
test('parse successfully', () {
611-
var parser = ArgParser();
612-
parser.addOption('test', mandatory: true);
613-
var results = parser.parse(['--test', 'test']);
614-
expect(results['test'], equals('test'));
605+
throwsArgParserException(parser, ['--test', 'test'],
606+
'Option username is mandatory.', [], 'username');
615607
});
616608

617-
test('throws when value retrieved', () {
609+
test('does not interfere with help flag', () {
618610
var parser = ArgParser();
619611
parser.addFlag('help', abbr: 'h', negatable: false);
620612
parser.addOption('test', mandatory: true);
621613
var results = parser.parse(['-h']);
622614
expect(results['help'], true);
623-
expect(() => results['test'], throwsA(isA<ArgumentError>()));
624-
expect(() => results.option('test'), throwsA(isA<ArgumentError>()));
615+
expect(results['test'], null);
616+
expect(results.option('test'), null);
625617
});
626618
});
627619
});

0 commit comments

Comments
 (0)