From 11ed14c594a9e2b1cb287ae5c89d49e8058c6b10 Mon Sep 17 00:00:00 2001 From: Slava Egorov Date: Tue, 9 Dec 2025 13:20:11 +0100 Subject: [PATCH 1/3] Make it possible to designate command default. Allow designating a top-level command or a subcommand as a default one by passing `isDefault: true` to `addCommand` or `addSubcommand`. Default command will be selected by argument parser if no sibling command matches. This allows creating command line interfaces where both `program command` and `program command subcommand` are runnable Fixes #103 --- pkgs/args/CHANGELOG.md | 9 ++ pkgs/args/lib/command_runner.dart | 89 ++++++++++--- pkgs/args/lib/src/allow_anything_parser.dart | 7 + pkgs/args/lib/src/arg_parser.dart | 3 + pkgs/args/lib/src/parser.dart | 61 ++++++--- pkgs/args/pubspec.yaml | 2 +- pkgs/args/test/command_runner_test.dart | 128 ++++++++++++++++++- pkgs/args/test/command_test.dart | 38 ++++++ pkgs/args/test/test_utils.dart | 22 ++++ 9 files changed, 318 insertions(+), 41 deletions(-) diff --git a/pkgs/args/CHANGELOG.md b/pkgs/args/CHANGELOG.md index f712877b5..d15f0a3c8 100644 --- a/pkgs/args/CHANGELOG.md +++ b/pkgs/args/CHANGELOG.md @@ -1,3 +1,12 @@ +## 2.8.0-dev + +* Allow designating a top-level command or a subcommand as a default one by + passing `isDefault: true` to `addCommand` or `addSubcommand`. + Default command will be selected by argument parser if no sibling command + matches. This allows creating command line interfaces where both + `program command` and `program command subcommand` are runnable + (Fixes #103). + ## 2.7.0 * Remove sorting of the `allowedHelp` argument in usage output. Ordering will diff --git a/pkgs/args/lib/command_runner.dart b/pkgs/args/lib/command_runner.dart index e72a08d44..2f878c5ef 100644 --- a/pkgs/args/lib/command_runner.dart +++ b/pkgs/args/lib/command_runner.dart @@ -31,9 +31,19 @@ class CommandRunner { /// A single-line template for how to invoke this executable. /// - /// Defaults to `"$executableName arguments`". Subclasses can - /// override this for a more specific template. - String get invocation => '$executableName [arguments]'; + /// Defaults to `"$executableName arguments"` (if there is no + /// default command) or `"$executableName [] arguments"` (otherwise). + /// + /// Subclasses can override this for a more specific template. + String get invocation { + var command = ''; + + if (argParser.defaultCommand != null) { + command = '[$command]'; + } + + return '$executableName $command [arguments]'; + } /// Generates a string displaying usage information for the executable. /// @@ -56,9 +66,10 @@ class CommandRunner { ); buffer.writeln(_wrap('Global options:')); buffer.writeln('${argParser.usage}\n'); - buffer.writeln( - '${_getCommandUsage(_commands, lineLength: argParser.usageLineLength)}\n', - ); + buffer.writeln(_getCommandUsage(_commands, + lineLength: argParser.usageLineLength, + defaultCommand: argParser.defaultCommand)); + buffer.writeln(); buffer.write(_wrap( 'Run "$executableName help " for more information about a ' 'command.')); @@ -105,12 +116,25 @@ class CommandRunner { throw UsageException(message, _usageWithoutDescription); /// Adds [Command] as a top-level command to this runner. - void addCommand(Command command) { + /// + /// If [isDefault] is `true` then added command will be designated as a + /// default one. Default command is selected if no other sibling command + /// matches. Only a single leaf-command can be designated as a default. + void addCommand(Command command, {bool isDefault = false}) { + if (isDefault && command.subcommands.isNotEmpty) { + throw ArgumentError('default command must be a leaf command'); + } + if (isDefault && argParser.defaultCommand != null) { + throw StateError('default command already defined'); + } var names = [command.name, ...command.aliases]; for (var name in names) { _commands[name] = command; argParser.addCommand(name, command.argParser); } + if (isDefault) { + argParser.defaultCommand = command.name; + } command._runner = this; } @@ -288,9 +312,13 @@ abstract class Command { parents.add(runner!.executableName); var invocation = parents.reversed.join(' '); - return _subcommands.isNotEmpty - ? '$invocation [arguments]' - : '$invocation [arguments]'; + if (argParser.defaultCommand != null) { + return '$invocation [] [arguments]'; + } else if (_subcommands.isNotEmpty) { + return '$invocation [arguments]'; + } else { + return '$invocation [arguments]'; + } } /// The command's parent command, if this is a subcommand. @@ -363,11 +391,10 @@ abstract class Command { if (_subcommands.isNotEmpty) { buffer.writeln(); - buffer.writeln(_getCommandUsage( - _subcommands, - isSubcommand: true, - lineLength: length, - )); + buffer.writeln(_getCommandUsage(_subcommands, + isSubcommand: true, + lineLength: length, + defaultCommand: argParser.defaultCommand)); } buffer.writeln(); @@ -446,12 +473,26 @@ abstract class Command { } /// Adds [Command] as a subcommand of this. - void addSubcommand(Command command) { + /// + /// If [isDefault] is `true` then added command will be designated as a + /// default one. Default subcommand is selected if no other sibling subcommand + /// matches. Only a single leaf-command can be designated as a default. + void addSubcommand(Command command, {bool isDefault = false}) { + if (isDefault && command.subcommands.isNotEmpty) { + throw ArgumentError('default command must be a leaf command'); + } + if (isDefault && argParser.defaultCommand != null) { + throw StateError('default command already defined'); + } + var names = [command.name, ...command.aliases]; for (var name in names) { _subcommands[name] = command; argParser.addCommand(name, command.argParser); } + if (isDefault) { + argParser.defaultCommand = command.name; + } command._parent = this; } @@ -470,8 +511,10 @@ abstract class Command { /// /// [isSubcommand] indicates whether the commands should be called "commands" or /// "subcommands". +/// +/// [defaultCommand] indicate which command (if any) is designated as default. String _getCommandUsage(Map commands, - {bool isSubcommand = false, int? lineLength}) { + {bool isSubcommand = false, int? lineLength, String? defaultCommand}) { // Don't include aliases. var names = commands.keys.where((name) => !commands[name]!.aliases.contains(name)); @@ -502,7 +545,8 @@ String _getCommandUsage(Map commands, buffer.write(category); } for (var command in commandsByCategory[category]!) { - var lines = wrapTextAsLines(command.summary, + var defaultMarker = defaultCommand == command.name ? '(default) ' : ''; + var lines = wrapTextAsLines(defaultMarker + command.summary, start: columnStart, length: lineLength); buffer.writeln(); buffer.write(' ${padRight(command.name, length)} ${lines.first}'); @@ -515,6 +559,15 @@ String _getCommandUsage(Map commands, } } + if (defaultCommand != null) { + buffer.writeln(); + buffer.writeln(); + buffer.write(wrapText( + 'Default command ($defaultCommand) will be selected if no command' + ' is explicitly specified.', + length: lineLength)); + } + return buffer.toString(); } diff --git a/pkgs/args/lib/src/allow_anything_parser.dart b/pkgs/args/lib/src/allow_anything_parser.dart index 69472b37d..20d77db91 100644 --- a/pkgs/args/lib/src/allow_anything_parser.dart +++ b/pkgs/args/lib/src/allow_anything_parser.dart @@ -104,4 +104,11 @@ class AllowAnythingParser implements ArgParser { @override Option? findByNameOrAlias(String name) => null; + + @override + String? get defaultCommand => null; + + @override + set defaultCommand(String? value) => throw UnsupportedError( + "ArgParser.allowAnything().defaultCommand= isn't supported."); } diff --git a/pkgs/args/lib/src/arg_parser.dart b/pkgs/args/lib/src/arg_parser.dart index 37041d7f4..cc7c9410b 100644 --- a/pkgs/args/lib/src/arg_parser.dart +++ b/pkgs/args/lib/src/arg_parser.dart @@ -25,6 +25,9 @@ class ArgParser { /// The commands that have been defined for this parser. final Map commands; + /// Command which will be executed by default if no command is specified. + String? defaultCommand; + /// A list of the [Option]s in [options] intermingled with [String] /// separators. final _optionsAndSeparators = []; diff --git a/pkgs/args/lib/src/parser.dart b/pkgs/args/lib/src/parser.dart index 660e56deb..ca01174dd 100644 --- a/pkgs/args/lib/src/parser.dart +++ b/pkgs/args/lib/src/parser.dart @@ -50,6 +50,7 @@ class Parser { } ArgResults? commandResults; + ({String name, ArgParser parser})? command; // Parse the args. while (_args.isNotEmpty) { @@ -61,26 +62,15 @@ class Parser { // Try to parse the current argument as a command. This happens before // options so that commands can have option-like names. - var command = _grammar.commands[_current]; - if (command != null) { - _validate(_rest.isEmpty, 'Cannot specify arguments before a command.', - _current); - var commandName = _args.removeFirst(); - var commandParser = Parser(commandName, command, _args, this, _rest); - - try { - commandResults = commandParser.parse(); - } on ArgParserException catch (error) { - throw ArgParserException( - error.message, - [commandName, ...error.commands], - error.argumentName, - error.source, - error.offset); - } - - // All remaining arguments were passed to command so clear them here. - _rest.clear(); + // + // Otherwise, if there is a default command then select it before parsing + // any arguments. + if (_grammar.commands[_current] case final parser?) { + command = (name: _args.removeFirst(), parser: parser); + break; + } else if (_grammar.defaultCommand case final defaultCommand?) { + command = + (name: defaultCommand, parser: _grammar.commands[defaultCommand]!); break; } @@ -96,6 +86,37 @@ class Parser { _rest.add(_args.removeFirst()); } + // If there is a default command and we did not select any other commands + // and we don't have any trailing arguments then select the default + // command. + if (command == null && _rest.isEmpty) { + if (_grammar.defaultCommand case final defaultCommand?) { + command = + (name: defaultCommand, parser: _grammar.commands[defaultCommand]!); + } + } + + if (command != null) { + _validate(_rest.isEmpty, 'Cannot specify arguments before a command.', + command.name); + var commandParser = + Parser(command.name, command.parser, _args, this, _rest); + + try { + commandResults = commandParser.parse(); + } on ArgParserException catch (error) { + throw ArgParserException( + error.message, + [command.name, ...error.commands], + error.argumentName, + error.source, + error.offset); + } + + // All remaining arguments were passed to command so clear them here. + _rest.clear(); + } + // Check if mandatory and invoke existing callbacks. _grammar.options.forEach((name, option) { var parsedOption = _results[name]; diff --git a/pkgs/args/pubspec.yaml b/pkgs/args/pubspec.yaml index d0a54e071..03f36e80e 100644 --- a/pkgs/args/pubspec.yaml +++ b/pkgs/args/pubspec.yaml @@ -1,5 +1,5 @@ name: args -version: 2.7.0 +version: 2.8.0-dev description: >- Library for defining parsers for parsing raw command-line arguments into a set of options and values using GNU and POSIX style options. diff --git a/pkgs/args/test/command_runner_test.dart b/pkgs/args/test/command_runner_test.dart index b9fde8ae3..9d8d8a80e 100644 --- a/pkgs/args/test/command_runner_test.dart +++ b/pkgs/args/test/command_runner_test.dart @@ -24,8 +24,15 @@ void main() { runner = CommandRunner('test', 'A test command runner.'); }); - test('.invocation has a sane default', () { - expect(runner.invocation, equals('test [arguments]')); + group('.invocation', () { + test('has a sane default', () { + expect(runner.invocation, equals('test [arguments]')); + }); + + test('with default command', () { + runner.addCommand(FooCommand(), isDefault: true); + expect(runner.invocation, equals('test [] [arguments]')); + }); }); group('.usage', () { @@ -256,6 +263,27 @@ Available commands: Run "name help " for more information about a command.''')); }); + + test('contains default command', () { + runner.addCommand(FooCommand()); + runner.addCommand(BarCommand(), isDefault: true); + + expect(runner.usage, equals(''' +A test command runner. + +Usage: test [] [arguments] + +Global options: +-h, --help Print this usage information. + +Available commands: + bar (default) Set another value. + foo Set a value. + +Default command (bar) will be selected if no command is explicitly specified. + +Run "test help " for more information about a command.''')); + }); }); test('usageException splits up the message and usage', () { @@ -263,6 +291,21 @@ information about a command.''')); throwsUsageException('message', _defaultUsage)); }); + group('.addCommand', () { + test('only one command can be default', () { + runner.addCommand(FooCommand(), isDefault: true); + expect(() => runner.addCommand(BarCommand(), isDefault: true), + throwsStateError); + }); + + test('only leaf command can be default', () { + expect( + () => runner.addCommand(BarCommand()..addSubcommand(FooCommand()), + isDefault: true), + throwsArgumentError); + }); + }); + group('run()', () { test('runs a command', () { var command = FooCommand(); @@ -741,6 +784,87 @@ Run "test help" to see global options.''')); expect(await runner.run([subcommand.name, '--mandatory-option', 'foo']), 'foo'); }); + + test('default command runs', () { + final defaultSubcommand = FooCommand(); + runner.addCommand(defaultSubcommand, isDefault: true); + + expect( + runner.run([]).then((_) { + expect(defaultSubcommand.hasRun, isTrue); + }), + completes); + }); + + test('default subcommand runs', () { + final defaultSubcommand = FooCommand(); + final command = FooCommand() + ..addSubcommand(AsyncCommand()) + ..addSubcommand(defaultSubcommand, isDefault: true); + runner.addCommand(command); + + expect( + runner.run(['foo']).then((_) { + expect(defaultSubcommand.hasRun, isTrue); + }), + completes); + }); + + test('default subcommand parses flags', () { + final defaultSubcommand = BarCommand(); + final command = FooCommand() + ..addSubcommand(AsyncCommand()) + ..addSubcommand(defaultSubcommand, isDefault: true); + runner.addCommand(command); + + expect( + runner.run(['foo', '--flag']).then((_) { + expect(defaultSubcommand.hasRun, isTrue); + expect(defaultSubcommand.argResults?.flag('flag'), isTrue); + }), + completes); + }); + + test('named subcommand has precedence over unnamed', () { + final defaultSubcommand = BarCommand(); + final asyncCommand = AsyncCommand(); + final command = FooCommand() + ..addSubcommand(asyncCommand) + ..addSubcommand(defaultSubcommand, isDefault: true); + runner.addCommand(command); + + expect( + runner.run(['foo', 'async']).then((_) { + expect(defaultSubcommand.hasRun, isFalse); + expect(asyncCommand.hasRun, isTrue); + }), + completes); + }); + + test('default command throws meaningful error for unexpected argument', () { + final defaultSubcommand = BarCommand(); + runner.addCommand(defaultSubcommand, isDefault: true); + + expect( + runner.run(['foo']), + throwsUsageException( + '''Command "bar" does not take any arguments.''', anything)); + }); + + test('default subcommand throws meaningful error for unexpected argument', + () { + final defaultSubcommand = BarCommand(); + final asyncCommand = AsyncCommand(); + final command = FooCommand() + ..addSubcommand(asyncCommand) + ..addSubcommand(defaultSubcommand, isDefault: true); + runner.addCommand(command); + + expect( + runner.run(['foo', 'baz']), + throwsUsageException( + '''Command "bar" does not take any arguments.''', anything)); + }); } class _MandatoryOptionCommand extends Command { diff --git a/pkgs/args/test/command_test.dart b/pkgs/args/test/command_test.dart index 555cc8db7..424253eac 100644 --- a/pkgs/args/test/command_test.dart +++ b/pkgs/args/test/command_test.dart @@ -26,6 +26,11 @@ void main() { expect(foo.invocation, equals('test foo [arguments]')); }); + test('with default subcommand', () { + foo.addSubcommand(AsyncCommand(), isDefault: true); + expect(foo.invocation, equals('test foo [] [arguments]')); + }); + test('for a subcommand', () { var async = AsyncCommand(); foo.addSubcommand(async); @@ -140,6 +145,24 @@ Usage: longtest long [arguments] Run "longtest help" to see global options.''')); }); + + test('prints default subcommand', () { + foo.addSubcommand(BarCommand(), isDefault: true); + foo.addSubcommand(AsyncCommand()); + expect(foo.usage, equals(''' +Set a value. + +Usage: test foo [] [arguments] +-h, --help Print this usage information. + +Available subcommands: + async Set a value asynchronously. + bar (default) Set another value. + +Default command (bar) will be selected if no command is explicitly specified. + +Run "test help" to see global options.''')); + }); }); test('usageException splits up the message and usage', () { @@ -155,4 +178,19 @@ Run "test help" to see global options.''')); foo.addSubcommand(HiddenCommand()); expect(foo.hidden, isTrue); }); + + group('.addSubcommand', () { + test('only one subcommand can be default', () { + foo.addSubcommand(BarCommand(), isDefault: true); + expect(() => foo.addSubcommand(AsyncCommand(), isDefault: true), + throwsStateError); + }); + + test('only leaf subcommand can be default', () { + expect( + () => foo.addSubcommand(BarCommand()..addSubcommand(FooCommand()), + isDefault: true), + throwsArgumentError); + }); + }); } diff --git a/pkgs/args/test/test_utils.dart b/pkgs/args/test/test_utils.dart index f7d8b8a1d..340e52bec 100644 --- a/pkgs/args/test/test_utils.dart +++ b/pkgs/args/test/test_utils.dart @@ -45,6 +45,28 @@ class FooCommand extends Command { } } +class BarCommand extends Command { + bool hasRun = false; + + @override + final name = 'bar'; + + @override + final description = 'Set another value.'; + + @override + final takesArguments = false; + + BarCommand() { + argParser.addFlag('flag', help: 'Some flag'); + } + + @override + void run() { + hasRun = true; + } +} + class ValueCommand extends Command { @override final name = 'foo'; From 24e467cf822f803b57fcbb29238c7b3077100dee Mon Sep 17 00:00:00 2001 From: Slava Egorov Date: Wed, 10 Dec 2025 09:54:02 +0100 Subject: [PATCH 2/3] Address comments --- pkgs/args/CHANGELOG.md | 2 +- pkgs/args/lib/src/arg_parser.dart | 2 ++ pkgs/args/pubspec.yaml | 2 +- pkgs/args/test/command_runner_test.dart | 4 ++-- pkgs/args/test/command_test.dart | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pkgs/args/CHANGELOG.md b/pkgs/args/CHANGELOG.md index d15f0a3c8..54985ffe6 100644 --- a/pkgs/args/CHANGELOG.md +++ b/pkgs/args/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.8.0-dev +## 2.8.0 * Allow designating a top-level command or a subcommand as a default one by passing `isDefault: true` to `addCommand` or `addSubcommand`. diff --git a/pkgs/args/lib/src/arg_parser.dart b/pkgs/args/lib/src/arg_parser.dart index cc7c9410b..22bc2b42b 100644 --- a/pkgs/args/lib/src/arg_parser.dart +++ b/pkgs/args/lib/src/arg_parser.dart @@ -26,6 +26,8 @@ class ArgParser { final Map commands; /// Command which will be executed by default if no command is specified. + /// + /// When `null` it is a usage error to omit the command name. String? defaultCommand; /// A list of the [Option]s in [options] intermingled with [String] diff --git a/pkgs/args/pubspec.yaml b/pkgs/args/pubspec.yaml index 03f36e80e..d65ca0b2f 100644 --- a/pkgs/args/pubspec.yaml +++ b/pkgs/args/pubspec.yaml @@ -1,5 +1,5 @@ name: args -version: 2.8.0-dev +version: 2.8.0 description: >- Library for defining parsers for parsing raw command-line arguments into a set of options and values using GNU and POSIX style options. diff --git a/pkgs/args/test/command_runner_test.dart b/pkgs/args/test/command_runner_test.dart index 9d8d8a80e..66a396d13 100644 --- a/pkgs/args/test/command_runner_test.dart +++ b/pkgs/args/test/command_runner_test.dart @@ -24,8 +24,8 @@ void main() { runner = CommandRunner('test', 'A test command runner.'); }); - group('.invocation', () { - test('has a sane default', () { + group('.invocation has a sensible default', () { + test('without default command', () { expect(runner.invocation, equals('test [arguments]')); }); diff --git a/pkgs/args/test/command_test.dart b/pkgs/args/test/command_test.dart index 424253eac..619bded4c 100644 --- a/pkgs/args/test/command_test.dart +++ b/pkgs/args/test/command_test.dart @@ -16,7 +16,7 @@ void main() { CommandRunner('test', 'A test command runner.').addCommand(foo); }); - group('.invocation has a sane default', () { + group('.invocation has a sensible default', () { test('without subcommands', () { expect(foo.invocation, equals('test foo [arguments]')); }); From 7cd3f187c27e36849f2a282e5a88ea07a741baf9 Mon Sep 17 00:00:00 2001 From: Slava Egorov Date: Wed, 10 Dec 2025 10:19:20 +0100 Subject: [PATCH 3/3] Change behavior of help --- pkgs/args/lib/src/parser.dart | 14 ++++--- pkgs/args/test/command_runner_test.dart | 56 ++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/pkgs/args/lib/src/parser.dart b/pkgs/args/lib/src/parser.dart index ca01174dd..9aca0f21a 100644 --- a/pkgs/args/lib/src/parser.dart +++ b/pkgs/args/lib/src/parser.dart @@ -49,7 +49,6 @@ class Parser { _grammar, const {}, _commandName, null, arguments, arguments); } - ArgResults? commandResults; ({String name, ArgParser parser})? command; // Parse the args. @@ -64,11 +63,15 @@ class Parser { // options so that commands can have option-like names. // // Otherwise, if there is a default command then select it before parsing - // any arguments. + // any arguments. We make exception for situations when help flag is + // passed because we want `program command -h` to display help for + // `command` rather than display help for the default subcommand of the + // `command`. if (_grammar.commands[_current] case final parser?) { command = (name: _args.removeFirst(), parser: parser); break; - } else if (_grammar.defaultCommand case final defaultCommand?) { + } else if (_grammar.defaultCommand case final defaultCommand? + when !(_current == '-h' || _current == '--help')) { command = (name: defaultCommand, parser: _grammar.commands[defaultCommand]!); break; @@ -88,14 +91,15 @@ class Parser { // If there is a default command and we did not select any other commands // and we don't have any trailing arguments then select the default - // command. - if (command == null && _rest.isEmpty) { + // command unless user requested help. + if (command == null && _rest.isEmpty && !_results.containsKey('help')) { if (_grammar.defaultCommand case final defaultCommand?) { command = (name: defaultCommand, parser: _grammar.commands[defaultCommand]!); } } + ArgResults? commandResults; if (command != null) { _validate(_rest.isEmpty, 'Cannot specify arguments before a command.', command.name); diff --git a/pkgs/args/test/command_runner_test.dart b/pkgs/args/test/command_runner_test.dart index 66a396d13..b3ac7b82a 100644 --- a/pkgs/args/test/command_runner_test.dart +++ b/pkgs/args/test/command_runner_test.dart @@ -825,7 +825,7 @@ Run "test help" to see global options.''')); completes); }); - test('named subcommand has precedence over unnamed', () { + test('named subcommand has precedence over default', () { final defaultSubcommand = BarCommand(); final asyncCommand = AsyncCommand(); final command = FooCommand() @@ -865,6 +865,60 @@ Run "test help" to see global options.''')); throwsUsageException( '''Command "bar" does not take any arguments.''', anything)); }); + + test('help flag has precedence over default command', () { + final defaultCommand = BarCommand(); + runner.addCommand(defaultCommand, isDefault: true); + + expect( + () => runner.run(['-h']).then((_) { + expect(defaultCommand.hasRun, isFalse); + }), + prints(''' +A test command runner. + +Usage: test [] [arguments] + +Global options: +-h, --help Print this usage information. + +Available commands: + bar (default) Set another value. + +Default command (bar) will be selected if no command is explicitly specified. + +Run "test help " for more information about a command. +''')); + }); + + test('help flag has precedence over default subcommand', () { + final defaultSubcommand = BarCommand(); + final asyncCommand = AsyncCommand(); + final command = FooCommand() + ..addSubcommand(asyncCommand) + ..addSubcommand(defaultSubcommand, isDefault: true); + runner.addCommand(command); + + expect( + () => runner.run(['foo', '-h']).then((_) { + expect(defaultSubcommand.hasRun, isFalse); + expect(asyncCommand.hasRun, isFalse); + }), + prints(''' +Set a value. + +Usage: test foo [] [arguments] +-h, --help Print this usage information. + +Available subcommands: + async Set a value asynchronously. + bar (default) Set another value. + +Default command (bar) will be selected if no command is explicitly specified. + +Run "test help" to see global options. +''')); + }); } class _MandatoryOptionCommand extends Command {