@@ -31,9 +31,19 @@ class CommandRunner<T> {
3131
3232 /// A single-line template for how to invoke this executable.
3333 ///
34- /// Defaults to `"$executableName <command> arguments` ". Subclasses can
35- /// override this for a more specific template.
36- String get invocation => '$executableName <command> [arguments]' ;
34+ /// Defaults to `"$executableName <command> arguments"` (if there is no
35+ /// default command) or `"$executableName [<command>] arguments"` (otherwise).
36+ ///
37+ /// Subclasses can override this for a more specific template.
38+ String get invocation {
39+ var command = '<command>' ;
40+
41+ if (argParser.defaultCommand != null ) {
42+ command = '[$command ]' ;
43+ }
44+
45+ return '$executableName $command [arguments]' ;
46+ }
3747
3848 /// Generates a string displaying usage information for the executable.
3949 ///
@@ -56,9 +66,10 @@ class CommandRunner<T> {
5666 );
5767 buffer.writeln (_wrap ('Global options:' ));
5868 buffer.writeln ('${argParser .usage }\n ' );
59- buffer.writeln (
60- '${_getCommandUsage (_commands , lineLength : argParser .usageLineLength )}\n ' ,
61- );
69+ buffer.writeln (_getCommandUsage (_commands,
70+ lineLength: argParser.usageLineLength,
71+ defaultCommand: argParser.defaultCommand));
72+ buffer.writeln ();
6273 buffer.write (_wrap (
6374 'Run "$executableName help <command>" for more information about a '
6475 'command.' ));
@@ -105,12 +116,25 @@ class CommandRunner<T> {
105116 throw UsageException (message, _usageWithoutDescription);
106117
107118 /// Adds [Command] as a top-level command to this runner.
108- void addCommand (Command <T > command) {
119+ ///
120+ /// If [isDefault] is `true` then added command will be designated as a
121+ /// default one. Default command is selected if no other sibling command
122+ /// matches. Only a single leaf-command can be designated as a default.
123+ void addCommand (Command <T > command, {bool isDefault = false }) {
124+ if (isDefault && command.subcommands.isNotEmpty) {
125+ throw ArgumentError ('default command must be a leaf command' );
126+ }
127+ if (isDefault && argParser.defaultCommand != null ) {
128+ throw StateError ('default command already defined' );
129+ }
109130 var names = [command.name, ...command.aliases];
110131 for (var name in names) {
111132 _commands[name] = command;
112133 argParser.addCommand (name, command.argParser);
113134 }
135+ if (isDefault) {
136+ argParser.defaultCommand = command.name;
137+ }
114138 command._runner = this ;
115139 }
116140
@@ -288,9 +312,13 @@ abstract class Command<T> {
288312 parents.add (runner! .executableName);
289313
290314 var invocation = parents.reversed.join (' ' );
291- return _subcommands.isNotEmpty
292- ? '$invocation <subcommand> [arguments]'
293- : '$invocation [arguments]' ;
315+ if (argParser.defaultCommand != null ) {
316+ return '$invocation [<subcommand>] [arguments]' ;
317+ } else if (_subcommands.isNotEmpty) {
318+ return '$invocation <subcommand> [arguments]' ;
319+ } else {
320+ return '$invocation [arguments]' ;
321+ }
294322 }
295323
296324 /// The command's parent command, if this is a subcommand.
@@ -363,11 +391,10 @@ abstract class Command<T> {
363391
364392 if (_subcommands.isNotEmpty) {
365393 buffer.writeln ();
366- buffer.writeln (_getCommandUsage (
367- _subcommands,
368- isSubcommand: true ,
369- lineLength: length,
370- ));
394+ buffer.writeln (_getCommandUsage (_subcommands,
395+ isSubcommand: true ,
396+ lineLength: length,
397+ defaultCommand: argParser.defaultCommand));
371398 }
372399
373400 buffer.writeln ();
@@ -446,12 +473,26 @@ abstract class Command<T> {
446473 }
447474
448475 /// Adds [Command] as a subcommand of this.
449- void addSubcommand (Command <T > command) {
476+ ///
477+ /// If [isDefault] is `true` then added command will be designated as a
478+ /// default one. Default subcommand is selected if no other sibling subcommand
479+ /// matches. Only a single leaf-command can be designated as a default.
480+ void addSubcommand (Command <T > command, {bool isDefault = false }) {
481+ if (isDefault && command.subcommands.isNotEmpty) {
482+ throw ArgumentError ('default command must be a leaf command' );
483+ }
484+ if (isDefault && argParser.defaultCommand != null ) {
485+ throw StateError ('default command already defined' );
486+ }
487+
450488 var names = [command.name, ...command.aliases];
451489 for (var name in names) {
452490 _subcommands[name] = command;
453491 argParser.addCommand (name, command.argParser);
454492 }
493+ if (isDefault) {
494+ argParser.defaultCommand = command.name;
495+ }
455496 command._parent = this ;
456497 }
457498
@@ -470,8 +511,10 @@ abstract class Command<T> {
470511///
471512/// [isSubcommand] indicates whether the commands should be called "commands" or
472513/// "subcommands".
514+ ///
515+ /// [defaultCommand] indicate which command (if any) is designated as default.
473516String _getCommandUsage (Map <String , Command > commands,
474- {bool isSubcommand = false , int ? lineLength}) {
517+ {bool isSubcommand = false , int ? lineLength, String ? defaultCommand }) {
475518 // Don't include aliases.
476519 var names =
477520 commands.keys.where ((name) => ! commands[name]! .aliases.contains (name));
@@ -502,7 +545,8 @@ String _getCommandUsage(Map<String, Command> commands,
502545 buffer.write (category);
503546 }
504547 for (var command in commandsByCategory[category]! ) {
505- var lines = wrapTextAsLines (command.summary,
548+ var defaultMarker = defaultCommand == command.name ? '(default) ' : '' ;
549+ var lines = wrapTextAsLines (defaultMarker + command.summary,
506550 start: columnStart, length: lineLength);
507551 buffer.writeln ();
508552 buffer.write (' ${padRight (command .name , length )} ${lines .first }' );
@@ -515,6 +559,15 @@ String _getCommandUsage(Map<String, Command> commands,
515559 }
516560 }
517561
562+ if (defaultCommand != null ) {
563+ buffer.writeln ();
564+ buffer.writeln ();
565+ buffer.write (wrapText (
566+ 'Default command ($defaultCommand ) will be selected if no command'
567+ ' is explicitly specified.' ,
568+ length: lineLength));
569+ }
570+
518571 return buffer.toString ();
519572}
520573
0 commit comments