Command_runner polish
In this chapter, you'll put the finishing touches on the command_runner
package. You'll refine the HelpCommand to provide more detailed usage
information and add an onOutput argument for more flexible output handling.
This finalizes the CommandRunner package and prepares it for use in more
complex scenarios.
Prerequisites
#Before you begin this chapter, ensure you:
-
Have completed Chapter 7 and have a working Dart development environment
with the
dartpediaproject. - Are familiar with basic programming concepts like variables, functions, and control flow.
- Understand the concepts of packages and libraries in Dart.
- Are familiar with object-oriented programming principles like inheritance and abstract classes.
Tasks
#
You will polish the command_runner package to make it more robust and
user-friendly.
Task 1 Improve the HelpCommand output
#
Enhance the HelpCommand to provide more detailed usage information,
including options and their descriptions. This will make it easier for users to
understand how to use your CLI application.
Open the
command_runner/lib/src/help_command.dartfile.-
Add imports for
console.dartandexceptions.dartat the top of the file. You need these to use the color extensions and to throw anArgumentException.dartimport 'dart:async'; import 'package:command_runner/command_runner.dart'; import 'console.dart'; import 'exceptions.dart'; -
Replace the existing
runmethod with the following. This new version uses aStringBufferto efficiently build the help string and includes logic to handle verbose output.dart@override FutureOr<String> run(ArgResults args) async { final buffer = StringBuffer(); buffer.writeln(runner.usage.titleText); if (args.flag('verbose')) { for (var cmd in runner.commands) { buffer.write(_renderCommandVerbose(cmd)); } return buffer.toString(); } if (args.hasOption('command')) { var (:option, :input) = args.getOption('command'); var cmd = runner.commands.firstWhere( (command) => command.name == input, orElse: () { throw ArgumentException( 'Input ${args.commandArg} is not a known command.', ); }, ); return _renderCommandVerbose(cmd); } // Verbose is false and no arg was passed in, so print basic usage. for (var command in runner.commands) { buffer.writeln(command.usage); } return buffer.toString(); }StringBufferis a Dart class that allows you to efficiently build strings. It's more performant than using the+operator, especially when performing many concatenations inside a loop. -
Add the
_renderCommandVerboseprivate helper method to theHelpCommandclass. This method formats the detailed output for a single command.dartString _renderCommandVerbose(Command cmd) { final indent = ' ' * 10; final buffer = StringBuffer(); buffer.writeln(cmd.usage.instructionText); //abbr, name: description buffer.writeln('$indent ${cmd.help}'); if (cmd.valueHelp != null) { buffer.writeln( '$indent [Argument] Required? ${cmd.requiresArgument}, Type: ${cmd.valueHelp}, Default: ${cmd.defaultValue ?? 'none'}', ); } buffer.writeln('$indent Options:'); for (var option in cmd.options) { buffer.writeln('$indent ${option.usage}'); } return buffer.toString(); }
Task 2 Add an onOutput callback
#
Next, add an onOutput argument to the CommandRunner to allow for
flexible output handling.
Open the
command_runner/lib/src/command_runner_base.dartfile.-
Add the
onOutputargument to theCommandRunnerconstructor, and add the correspondingonOutputmember to the class.dartclass CommandRunner { CommandRunner({this.onOutput, this.onError}); /// If not null, this method is used to handle output. Useful if you want to /// execute code before the output is printed to the console, or if you /// want to do something other than print output the console. /// If null, the onInput method will [print] the output. FutureOr<void> Function(String)? onOutput; FutureOr<void> Function(Object)? onError; // ... rest of the class } -
Update the
runmethod to use theonOutputargument.dartFuture<void> run(List<String> input) async { try { final ArgResults results = parse(input); if (results.command != null) { Object? output = await results.command!.run(results); if (onOutput != null) { await onOutput!(output.toString()); } else { print(output.toString()); } } } on Exception catch (e) { print(e); } }This updates the
runmethod to use theonOutputfunction if it is provided, otherwise it defaults to printing to the console.
Task 3 Use the onOutput callback
#
Finally, update your main application to use the new onOutput feature.
Open the
cli/bin/cli.dartfile.-
Update the
mainfunction to pass theonOutputfunction to theCommandRunner. You will also need to add an import forconsole.dartto make thewritefunction available.dartimport 'package:command_runner/command_runner.dart'; import 'package:command_runner/src/console.dart'; const version = '0.0.1'; void main(List<String> arguments) { var commandRunner = CommandRunner<String>( onOutput: (String output) async { await write(output); }, onError: (Object error) { if (error is Error) { throw error; } if (error is Exception) { print(error); } }, )..addCommand(HelpCommand()); commandRunner.run(arguments); }
Task 4 Test the changes
#Test the improved HelpCommand and the onOutput callback.
Open your terminal and navigate to the
clidirectory.-
Run the command
dart run bin/cli.dart help --verbose.You should see detailed usage information for the
helpcommand, printed using the customwritefunction.
Review
#In this lesson, you learned:
- How to use
StringBufferfor efficient string manipulation. - How to improve the
HelpCommandto display detailed usage information. -
How to add an
onOutputargument to theCommandRunnerfor customizable output handling.
Quiz
#Question 1: What is the purpose of the StringBuffer class in Dart?
- A) To store a fixed-size string.
- B) To efficiently build strings by appending multiple parts.
- C) To encrypt strings.
- D) To compare two strings for equality.
Question 2: What does the onOutput argument in the CommandRunner
class
allow you to do?
- A) Specify the input for a command.
- B) Customize the output handling of a command.
- C) Set the error message for a command.
- D) Define the name of a command.
Next lesson
#In the next lesson, you'll prepare for the Wikipedia portion of the application.
Unless stated otherwise, the documentation on this site reflects Dart 3.9.2. Page last updated on 2025-9-15. View source or report an issue.