Print the proper usage in CommandRunner. (#53)

This now prints the usage for the command that failed to parse, rather
than always printing the root usage.

Closes #52
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0861e19..108d60e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+## 0.13.6
+
+* `ArgParser.parse()` now throws an `ArgParserException`, which implements
+  `FormatException` and has a field that lists the commands that were parsed.
+
+* If `CommandRunner.run()` encounters a parse error for a subcommand, it now
+  prints the subcommand's usage rather than the global usage.
+
 ## 0.13.5
 
 * Allow `CommandRunner.argParser` and `Command.argParser` to be overridden in
diff --git a/README.md b/README.md
index 461ebc6..36e931c 100644
--- a/README.md
+++ b/README.md
@@ -51,9 +51,11 @@
 be a `bool`.
 
 To validate a non-flag option, you can use the `allowed` parameter to provide an
-allowed set of values. When you do, the parser throws a [FormatException] if the
-value for an option is not in the allowed set. Here's an example of specifying
-allowed values:
+allowed set of values. When you do, the parser throws an
+[`ArgParserException`][ArgParserException] if the value for an option is not in
+the allowed set. Here's an example of specifying allowed values:
+
+[ArgParserException]: https://www.dartdocs.org/documentation/args/latest/args/ArgParserException-class.html
 
 ```dart
 parser.addOption('mode', allowed: ['debug', 'release']);
diff --git a/lib/args.dart b/lib/args.dart
index 2534082..97b47b5 100644
--- a/lib/args.dart
+++ b/lib/args.dart
@@ -3,5 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 
 export 'src/arg_parser.dart';
+export 'src/arg_parser_exception.dart';
 export 'src/arg_results.dart' hide newArgResults;
 export 'src/option.dart' hide newOption;
diff --git a/lib/command_runner.dart b/lib/command_runner.dart
index 21dfa32..5780fae 100644
--- a/lib/command_runner.dart
+++ b/lib/command_runner.dart
@@ -7,6 +7,7 @@
 import 'dart:math' as math;
 
 import 'src/arg_parser.dart';
+import 'src/arg_parser_exception.dart';
 import 'src/arg_results.dart';
 import 'src/help_command.dart';
 import 'src/usage_exception.dart';
@@ -103,18 +104,23 @@
   Future run(Iterable<String> args) =>
       new Future.sync(() => runCommand(parse(args)));
 
-  /// Parses [args] and returns the result, converting a [FormatException] to a
-  /// [UsageException].
+  /// Parses [args] and returns the result, converting an [ArgParserException]
+  /// to a [UsageException].
   ///
   /// This is notionally a protected method. It may be overridden or called from
   /// subclasses, but it shouldn't be called externally.
   ArgResults parse(Iterable<String> args) {
     try {
-      // TODO(nweiz): if arg parsing fails for a command, print that command's
-      // usage, not the global usage.
       return argParser.parse(args);
-    } on FormatException catch (error) {
-      usageException(error.message);
+    } on ArgParserException catch (error) {
+      if (error.commands.isEmpty) usageException(error.message);
+
+      var command = commands[error.commands.first];
+      for (var commandName in error.commands.skip(1)) {
+        command = command.subcommands[commandName];
+      }
+
+      command.usageException(error.message);
     }
   }
 
diff --git a/lib/src/arg_parser_exception.dart b/lib/src/arg_parser_exception.dart
new file mode 100644
index 0000000..20eddb9
--- /dev/null
+++ b/lib/src/arg_parser_exception.dart
@@ -0,0 +1,17 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// An exception thrown by [ArgParser].
+class ArgParserException extends FormatException {
+  /// The command(s) that were parsed before discovering the error.
+  ///
+  /// This will be empty if the error was on the root parser.
+  final List<String> commands;
+
+  ArgParserException(String message, [Iterable<String> commands])
+      : commands = commands == null
+            ? const []
+            : new List.unmodifiable(commands),
+        super(message);
+}
diff --git a/lib/src/parser.dart b/lib/src/parser.dart
index 22579fb..64374fc 100644
--- a/lib/src/parser.dart
+++ b/lib/src/parser.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'arg_parser.dart';
+import 'arg_parser_exception.dart';
 import 'arg_results.dart';
 import 'option.dart';
 
@@ -63,7 +64,15 @@
         validate(rest.isEmpty, 'Cannot specify arguments before a command.');
         var commandName = args.removeAt(0);
         var commandParser = new Parser(commandName, command, args, this, rest);
-        commandResults = commandParser.parse();
+
+        try {
+          commandResults = commandParser.parse();
+        } on ArgParserException catch (error) {
+          if (commandName == null) rethrow;
+          throw new ArgParserException(
+              error.message,
+              [commandName]..addAll(error.commands));
+        }
 
         // All remaining arguments were passed to command so clear them here.
         rest.clear();
@@ -242,9 +251,9 @@
 
   /// Called during parsing to validate the arguments.
   ///
-  /// Throws a [FormatException] if [condition] is `false`.
+  /// Throws an [ArgParserException] if [condition] is `false`.
   void validate(bool condition, String message) {
-    if (!condition) throw new FormatException(message);
+    if (!condition) throw new ArgParserException(message);
   }
 
   /// Validates and stores [value] as the value for [option], which must not be
diff --git a/pubspec.yaml b/pubspec.yaml
index e52bf29..ab921a5 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: args
-version: 0.13.5
+version: 0.13.6
 author: "Dart Team <misc@dartlang.org>"
 homepage: https://github.com/dart-lang/args
 description: >
diff --git a/test/command_runner_test.dart b/test/command_runner_test.dart
index d6605a4..6b4e30e 100644
--- a/test/command_runner_test.dart
+++ b/test/command_runner_test.dart
@@ -246,6 +246,27 @@
 """));
       });
     });
+
+    group("with an invalid argument", () {
+      test("at the root throws the root usage", () {
+        expect(runner.run(["--asdf"]), throwsUsageException(
+            'Could not find an option named "asdf".',
+            '$_DEFAULT_USAGE'));
+      });
+
+      test("for a command throws the command usage", () {
+        var command = new FooCommand();
+        runner.addCommand(command);
+
+        expect(runner.run(["foo", "--asdf"]), throwsUsageException(
+            'Could not find an option named "asdf".',
+            """
+Usage: test foo [arguments]
+-h, --help    Print this usage information.
+
+Run "test help" to see global options."""));
+      });
+    });
   });
 
   group("with a footer", () {