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
dartpedia
project. - 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.dart
file.Add imports for
console.dart
andexceptions.dart
at 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
run
method with the following. This new version uses aStringBuffer
to 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(); }
StringBuffer
is 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
_renderCommandVerbose
private helper method to theHelpCommand
class. 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.dart
file.Add the
onOutput
argument to theCommandRunner
constructor, and add the correspondingonOutput
member 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
run
method to use theonOutput
argument.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
run
method to use theonOutput
function 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.dart
file.Update the
main
function to pass theonOutput
function to theCommandRunner
. You will also need to add an import forconsole.dart
to make thewrite
function 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
cli
directory.Run the command
dart run bin/cli.dart help --verbose
.You should see detailed usage information for the
help
command, printed using the customwrite
function.
Review
#In this lesson, you learned:
- How to use
StringBuffer
for efficient string manipulation. - How to improve the
HelpCommand
to display detailed usage information. - How to add an
onOutput
argument to theCommandRunner
for 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.8.1. Page last updated on 2025-07-31. View source or report an issue.