Merge pull request #93 from dart-lang/add-multi-option

Add a separate addMultiOption() method
diff --git a/.travis.yml b/.travis.yml
index ac48a95..3b94716 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,7 +9,7 @@
 
 matrix:
   include:
-  - dart: stable
+  - dart: dev
     dart_task: dartfmt
 
 # Only building master means that we don't run two builds for each pull request.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7cc69c0..20eba1f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,12 @@
   `Option.abbr` and `Option.defaultsTo`. This makes all of `Option`'s fields
   match the corresponding parameters to `ArgParser.addOption()`.
 
+* Deprecated the `allowMultiple` and `splitCommas` arguments to
+  `ArgParser.addOption()` in favor of a separate `ArgParser.addMultiOption()`
+  method. This allows us to provide more accurate type information, and to avoid
+  adding flags that only make sense for multi-options in places where they might
+  be usable for single-value options.
+
 ## 1.3.0
 
 * Type `Command.run()`'s return value as `FutureOr<T>`.
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 092773b..32747ee 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -1,5 +1,11 @@
 analyzer:
- strong-mode: true
+  strong-mode: true
+
+  errors:
+    # TODO(nweiz): Remove this ignore when sdk#30084 is fixed or when args no
+    # longer refers to its own deprecated members.
+    deprecated_member_use: ignore
+
 linter:
   rules:
      # Errors
diff --git a/example/test_runner.dart b/example/test_runner.dart
index d9a6297..64f0fff 100644
--- a/example/test_runner.dart
+++ b/example/test_runner.dart
@@ -150,8 +150,7 @@
       defaultsTo: false,
       help: 'Keep the generated files in the temporary directory');
 
-  parser.addOption('special-command',
-      help: """
+  parser.addOption('special-command', help: """
 Special command support. Wraps the command line in
 a special command. The special command should contain
 an '@' character which will be replaced by the normal
diff --git a/lib/src/allow_anything_parser.dart b/lib/src/allow_anything_parser.dart
index 58e4812..248a39a 100644
--- a/lib/src/allow_anything_parser.dart
+++ b/lib/src/allow_anything_parser.dart
@@ -45,6 +45,20 @@
         "ArgParser.allowAnything().addOption() isn't supported.");
   }
 
+  void addMultiOption(String name,
+      {String abbr,
+      String help,
+      String valueHelp,
+      Iterable<String> allowed,
+      Map<String, String> allowedHelp,
+      Iterable<String> defaultsTo,
+      void callback(List<String> values),
+      bool splitCommas: true,
+      bool hide: false}) {
+    throw new UnsupportedError(
+        "ArgParser.allowAnything().addMultiOption() isn't supported.");
+  }
+
   void addSeparator(String text) {
     throw new UnsupportedError(
         "ArgParser.allowAnything().addSeparator() isn't supported.");
diff --git a/lib/src/arg_parser.dart b/lib/src/arg_parser.dart
index c115197..bcdb8df 100644
--- a/lib/src/arg_parser.dart
+++ b/lib/src/arg_parser.dart
@@ -156,14 +156,8 @@
   /// that are often surprising, and its use is discouraged in favor of reading
   /// values from the [ArgResult].
   ///
-  /// If [allowMultiple] is `true`, the user may pass this option multiple times
-  /// and its value will be a `List<String>` rather than a `String`. The default
-  /// value will be `[]` rather than `null`, or `[defaultsTo]` if [defaultsTo]
-  /// is passed.
-  ///
-  /// If [splitCommas] is `true`, multiple values may be passed by writing
-  /// `--option a,b` in addition to `--option a --option b`. It defaults to
-  /// `true` if [allowMultiple] is `true` and `false` otherwise.
+  /// The [allowMultiple] and [splitCommas] options are deprecated; the
+  /// [addMultiOption] method should be used instead.
   ///
   /// If [hide] is `true`, this option won't be included in [usage].
   ///
@@ -180,17 +174,89 @@
       Map<String, String> allowedHelp,
       String defaultsTo,
       Function callback,
-      bool allowMultiple: false,
-      bool splitCommas,
+      @Deprecated("Use addMultiOption() instead.") bool allowMultiple: false,
+      @Deprecated("Use addMultiOption() instead.") bool splitCommas,
       bool hide: false}) {
     if (!allowMultiple && splitCommas != null) {
       throw new ArgumentError(
           'splitCommas may not be set if allowMultiple is false.');
     }
 
-    _addOption(name, abbr, help, valueHelp, allowed, allowedHelp, defaultsTo,
-        callback, allowMultiple ? OptionType.multiple : OptionType.single,
-        splitCommas: splitCommas, hide: hide);
+    _addOption(
+        name,
+        abbr,
+        help,
+        valueHelp,
+        allowed,
+        allowedHelp,
+        allowMultiple
+            ? (defaultsTo == null ? <String>[] : [defaultsTo])
+            : defaultsTo,
+        callback,
+        allowMultiple ? OptionType.multiple : OptionType.single,
+        splitCommas: splitCommas,
+        hide: hide);
+  }
+
+  /// Defines an option that takes multiple values.
+  ///
+  /// The [abbr] argument is a single-character string that can be used as a
+  /// shorthand for this option. For example, `abbr: "a"` will allow the user to
+  /// pass `-a value` or `-avalue`.
+  ///
+  /// The [help] argument is used by [usage] to describe this option.
+  ///
+  /// The [valueHelp] argument is used by [usage] as a name for the value this
+  /// argument takes. For example, `valueHelp: "FOO"` will include
+  /// `--option=<FOO>` rather than just `--option` in the usage string.
+  ///
+  /// The [allowed] argument is a list of valid values for this argument. If
+  /// it's non-`null` and the user passes a value that's not included in the
+  /// list, [parse] will throw a [FormatException]. The allowed values will also
+  /// be included in [usage].
+  ///
+  /// The [allowedHelp] argument is a map from values in [allowed] to
+  /// documentation for those values that will be included in [usage].
+  ///
+  /// The [defaultsTo] argument indicates the values this option will have if
+  /// the user doesn't explicitly pass it in (or `[]` by default).
+  ///
+  /// The [callback] argument is invoked with the option's value when the option
+  /// is parsed. Note that this makes argument parsing order-dependent in ways
+  /// that are often surprising, and its use is discouraged in favor of reading
+  /// values from the [ArgResult].
+  ///
+  /// If [splitCommas] is `true` (the default), multiple options may be passed
+  /// by writing `--option a,b` in addition to `--option a --option b`.
+  ///
+  /// If [hide] is `true`, this option won't be included in [usage].
+  ///
+  /// Throws an [ArgumentError] if:
+  ///
+  /// * There is already an option with name [name].
+  /// * There is already an option using abbreviation [abbr].
+  void addMultiOption(String name,
+      {String abbr,
+      String help,
+      String valueHelp,
+      Iterable<String> allowed,
+      Map<String, String> allowedHelp,
+      Iterable<String> defaultsTo,
+      void callback(List<String> values),
+      bool splitCommas: true,
+      bool hide: false}) {
+    _addOption(
+        name,
+        abbr,
+        help,
+        valueHelp,
+        allowed,
+        allowedHelp,
+        defaultsTo?.toList() ?? <String>[],
+        callback == null ? null : (value) => callback(value as List<String>),
+        OptionType.multiple,
+        splitCommas: splitCommas,
+        hide: hide);
   }
 
   void _addOption(
diff --git a/lib/src/option.dart b/lib/src/option.dart
index 0902fe3..727ea52 100644
--- a/lib/src/option.dart
+++ b/lib/src/option.dart
@@ -141,10 +141,8 @@
   /// list containing [defaultsTo] if set.
   dynamic getOrDefault(value) {
     if (value != null) return value;
-
-    if (!isMultiple) return defaultsTo;
-    if (defaultsTo != null) return [defaultsTo];
-    return [];
+    if (isMultiple) return defaultsTo ?? <String>[];
+    return defaultsTo;
   }
 
   static final _invalidChars = new RegExp(r'''[ \t\r\n"'\\/]''');
diff --git a/pubspec.yaml b/pubspec.yaml
index 4985ea2..c476bc3 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: args
-version: 1.4.0-dev
+version: 1.4.0
 author: "Dart Team <misc@dartlang.org>"
 homepage: https://github.com/dart-lang/args
 description: >
diff --git a/test/args_test.dart b/test/args_test.dart
index ff968ad..8741ad8 100644
--- a/test/args_test.dart
+++ b/test/args_test.dart
@@ -274,8 +274,11 @@
       parser.addFlag('flag-def', defaultsTo: true);
       parser.addOption('single-no');
       parser.addOption('single-def', defaultsTo: 'def');
-      parser.addOption('multi-no', allowMultiple: true);
-      parser.addOption('multi-def', allowMultiple: true, defaultsTo: 'def');
+      parser.addOption('allow-multi-no', allowMultiple: true);
+      parser.addOption('allow-multi-def',
+          allowMultiple: true, defaultsTo: 'def');
+      parser.addMultiOption('multi-no');
+      parser.addMultiOption('multi-def', defaultsTo: ['def']);
 
       expect(parser.options['flag-no'].getOrDefault(null), equals(null));
       expect(parser.options['flag-no'].getOrDefault(false), equals(false));
@@ -285,6 +288,13 @@
       expect(parser.options['single-no'].getOrDefault('v'), equals('v'));
       expect(parser.options['single-def'].getOrDefault(null), equals('def'));
       expect(parser.options['single-def'].getOrDefault('v'), equals('v'));
+      expect(parser.options['allow-multi-no'].getOrDefault(null), equals([]));
+      expect(
+          parser.options['allow-multi-no'].getOrDefault(['v']), equals(['v']));
+      expect(parser.options['allow-multi-def'].getOrDefault(null),
+          equals(['def']));
+      expect(
+          parser.options['allow-multi-def'].getOrDefault(['v']), equals(['v']));
       expect(parser.options['multi-no'].getOrDefault(null), equals([]));
       expect(parser.options['multi-no'].getOrDefault(['v']), equals(['v']));
       expect(parser.options['multi-def'].getOrDefault(null), equals(['def']));
diff --git a/test/command_runner_test.dart b/test/command_runner_test.dart
index 5a765b1..ebb2f61 100644
--- a/test/command_runner_test.dart
+++ b/test/command_runner_test.dart
@@ -299,11 +299,8 @@
         var command = new FooCommand();
         runner.addCommand(command);
 
-        expect(
-            runner.run(["foo", "--asdf"]),
-            throwsUsageException(
-                'Could not find an option named "asdf".',
-                """
+        expect(runner.run(["foo", "--asdf"]),
+            throwsUsageException('Could not find an option named "asdf".', """
 Usage: test foo [arguments]
 -h, --help    Print this usage information.
 
@@ -351,11 +348,8 @@
     test("a subcommand doesn't exist", () {
       runner.addCommand(new FooCommand()..addSubcommand(new AsyncCommand()));
 
-      expect(
-          runner.run(["foo bad"]),
-          throwsUsageException(
-              'Could not find a command named "foo bad".',
-              """
+      expect(runner.run(["foo bad"]),
+          throwsUsageException('Could not find a command named "foo bad".', """
 Usage: test <command> [arguments]
 
 Global options:
@@ -371,11 +365,8 @@
     test("a subcommand wasn't passed", () {
       runner.addCommand(new FooCommand()..addSubcommand(new AsyncCommand()));
 
-      expect(
-          runner.run(["foo"]),
-          throwsUsageException(
-              'Missing subcommand for "test foo".',
-              """
+      expect(runner.run(["foo"]),
+          throwsUsageException('Missing subcommand for "test foo".', """
 Usage: test foo <subcommand> [arguments]
 -h, --help    Print this usage information.
 
@@ -388,11 +379,8 @@
     test("a command that doesn't take arguments was given them", () {
       runner.addCommand(new FooCommand());
 
-      expect(
-          runner.run(["foo", "bar"]),
-          throwsUsageException(
-              'Command "foo" does not take any arguments.',
-              """
+      expect(runner.run(["foo", "bar"]),
+          throwsUsageException('Command "foo" does not take any arguments.', """
 Usage: test foo [arguments]
 -h, --help    Print this usage information.
 
diff --git a/test/command_test.dart b/test/command_test.dart
index c0c08d3..c4f29d4 100644
--- a/test/command_test.dart
+++ b/test/command_test.dart
@@ -91,10 +91,7 @@
 
   test("usageException splits up the message and usage", () {
     expect(
-        () => foo.usageException("message"),
-        throwsUsageException(
-            "message",
-            """
+        () => foo.usageException("message"), throwsUsageException("message", """
 Usage: test foo [arguments]
 -h, --help    Print this usage information.
 
diff --git a/test/parse_test.dart b/test/parse_test.dart
index 87f3d81..d212cf0 100644
--- a/test/parse_test.dart
+++ b/test/parse_test.dart
@@ -152,104 +152,186 @@
         expect(a, isNull);
       });
 
-      test(
-          'for multiple present, allowMultiple, options are invoked with '
-          'value as a list', () {
-        var a;
-        var parser = new ArgParser();
-        parser.addOption('a',
-            allowMultiple: true, callback: (value) => a = value);
+      group("with allowMultiple", () {
+        test('for multiple present, options are invoked with value as a list',
+            () {
+          var a;
+          var parser = new ArgParser();
+          parser.addOption('a',
+              allowMultiple: true, callback: (value) => a = value);
 
-        parser.parse(['--a=v', '--a=x']);
-        expect(a, equals(['v', 'x']));
+          parser.parse(['--a=v', '--a=x']);
+          expect(a, equals(['v', 'x']));
 
-        // This reified type is important in strong mode so that people can
-        // safely write "as List<String>".
-        expect(a, new isInstanceOf<List<String>>());
+          // This reified type is important in strong mode so that people can
+          // safely write "as List<String>".
+          expect(a, new isInstanceOf<List<String>>());
+        });
+
+        test(
+            'for single present, options are invoked with value as a single '
+            'element list', () {
+          var a;
+          var parser = new ArgParser();
+          parser.addOption('a',
+              allowMultiple: true, callback: (value) => a = value);
+
+          parser.parse(['--a=v']);
+          expect(a, equals(['v']));
+        });
+
+        test('for absent, options are invoked with default value as a list',
+            () {
+          var a;
+          var parser = new ArgParser();
+          parser.addOption('a',
+              allowMultiple: true,
+              defaultsTo: 'v',
+              callback: (value) => a = value);
+
+          parser.parse([]);
+          expect(a, equals(['v']));
+        });
+
+        test('for absent, options are invoked with value as an empty list', () {
+          var a;
+          var parser = new ArgParser();
+          parser.addOption('a',
+              allowMultiple: true, callback: (value) => a = value);
+
+          parser.parse([]);
+          expect(a, isEmpty);
+        });
+
+        test('parses comma-separated strings', () {
+          var a;
+          var parser = new ArgParser();
+          parser.addOption('a',
+              allowMultiple: true, callback: (value) => a = value);
+
+          parser.parse(['--a=v,w', '--a=x']);
+          expect(a, equals(['v', 'w', 'x']));
+        });
+
+        test("doesn't parse comma-separated strings with splitCommas: false",
+            () {
+          var a;
+          var parser = new ArgParser();
+          parser.addOption('a',
+              allowMultiple: true,
+              splitCommas: false,
+              callback: (value) => a = value);
+
+          parser.parse(['--a=v,w', '--a=x']);
+          expect(a, equals(['v,w', 'x']));
+        });
+
+        test('parses empty strings', () {
+          var a;
+          var parser = new ArgParser();
+          parser.addOption('a',
+              allowMultiple: true, callback: (value) => a = value);
+
+          parser.parse(['--a=,v', '--a=w,', '--a=,', '--a=x,,y', '--a', '']);
+          expect(a, equals(['', 'v', 'w', '', '', '', 'x', '', 'y', '']));
+        });
+
+        test('with allowed parses comma-separated strings', () {
+          var a;
+          var parser = new ArgParser();
+          parser.addOption('a',
+              allowMultiple: true,
+              allowed: ['v', 'w', 'x'],
+              callback: (value) => a = value);
+
+          parser.parse(['--a=v,w', '--a=x']);
+          expect(a, equals(['v', 'w', 'x']));
+        });
       });
 
-      test(
-          'for single present, allowMultiple, options are invoked with '
-          ' value as a single element list', () {
-        var a;
-        var parser = new ArgParser();
-        parser.addOption('a',
-            allowMultiple: true, callback: (value) => a = value);
+      group("with addMultiOption", () {
+        test('for multiple present, options are invoked with value as a list',
+            () {
+          var a;
+          var parser = new ArgParser();
+          parser.addMultiOption('a', callback: (value) => a = value);
 
-        parser.parse(['--a=v']);
-        expect(a, equals(['v']));
-      });
+          parser.parse(['--a=v', '--a=x']);
+          expect(a, equals(['v', 'x']));
 
-      test(
-          'for absent, allowMultiple, options are invoked with default '
-          'value as a list.', () {
-        var a;
-        var parser = new ArgParser();
-        parser.addOption('a',
-            allowMultiple: true,
-            defaultsTo: 'v',
-            callback: (value) => a = value);
+          // This reified type is important in strong mode so that people can
+          // safely write "as List<String>".
+          expect(a, new isInstanceOf<List<String>>());
+        });
 
-        parser.parse([]);
-        expect(a, equals(['v']));
-      });
+        test(
+            'for single present, options are invoked with value as a single '
+            'element list', () {
+          var a;
+          var parser = new ArgParser();
+          parser.addMultiOption('a', callback: (value) => a = value);
 
-      test(
-          'for absent, allowMultiple, options are invoked with value '
-          'as an empty list.', () {
-        var a;
-        var parser = new ArgParser();
-        parser.addOption('a',
-            allowMultiple: true, callback: (value) => a = value);
+          parser.parse(['--a=v']);
+          expect(a, equals(['v']));
+        });
 
-        parser.parse([]);
-        expect(a, isEmpty);
-      });
+        test('for absent, options are invoked with default value', () {
+          var a;
+          var parser = new ArgParser();
+          parser.addMultiOption('a',
+              defaultsTo: ['v', 'w'], callback: (value) => a = value);
 
-      test('allowMultiple parses comma-separated strings', () {
-        var a;
-        var parser = new ArgParser();
-        parser.addOption('a',
-            allowMultiple: true, callback: (value) => a = value);
+          parser.parse([]);
+          expect(a, equals(['v', 'w']));
+        });
 
-        parser.parse(['--a=v,w', '--a=x']);
-        expect(a, equals(['v', 'w', 'x']));
-      });
+        test('for absent, options are invoked with value as an empty list', () {
+          var a;
+          var parser = new ArgParser();
+          parser.addMultiOption('a', callback: (value) => a = value);
 
-      test(
-          "allowMultiple doesn't parses comma-separated strings with "
-          "splitCommas: false", () {
-        var a;
-        var parser = new ArgParser();
-        parser.addOption('a',
-            allowMultiple: true,
-            splitCommas: false,
-            callback: (value) => a = value);
+          parser.parse([]);
+          expect(a, isEmpty);
+        });
 
-        parser.parse(['--a=v,w', '--a=x']);
-        expect(a, equals(['v,w', 'x']));
-      });
+        test('parses comma-separated strings', () {
+          var a;
+          var parser = new ArgParser();
+          parser.addMultiOption('a', callback: (value) => a = value);
 
-      test('allowMultiple parses empty strings', () {
-        var a;
-        var parser = new ArgParser();
-        parser.addOption('a',
-            allowMultiple: true, callback: (value) => a = value);
+          parser.parse(['--a=v,w', '--a=x']);
+          expect(a, equals(['v', 'w', 'x']));
+        });
 
-        parser.parse(['--a=,v', '--a=w,', '--a=,', '--a=x,,y', '--a', '']);
-        expect(a, equals(['', 'v', 'w', '', '', '', 'x', '', 'y', '']));
-      });
+        test("doesn't parse comma-separated strings with splitCommas: false",
+            () {
+          var a;
+          var parser = new ArgParser();
+          parser.addMultiOption('a',
+              splitCommas: false, callback: (value) => a = value);
 
-      test('allowMultiple with allowed parses comma-separated strings', () {
-        var a;
-        var parser = new ArgParser();
-        parser.addOption('a',
-            allowMultiple: true,
-            allowed: ['v', 'w', 'x'],
-            callback: (value) => a = value);
+          parser.parse(['--a=v,w', '--a=x']);
+          expect(a, equals(['v,w', 'x']));
+        });
 
-        parser.parse(['--a=v,w', '--a=x']);
-        expect(a, equals(['v', 'w', 'x']));
+        test('parses empty strings', () {
+          var a;
+          var parser = new ArgParser();
+          parser.addMultiOption('a', callback: (value) => a = value);
+
+          parser.parse(['--a=,v', '--a=w,', '--a=,', '--a=x,,y', '--a', '']);
+          expect(a, equals(['', 'v', 'w', '', '', '', 'x', '', 'y', '']));
+        });
+
+        test('with allowed parses comma-separated strings', () {
+          var a;
+          var parser = new ArgParser();
+          parser.addMultiOption('a',
+              allowed: ['v', 'w', 'x'], callback: (value) => a = value);
+
+          parser.parse(['--a=v,w', '--a=x']);
+          expect(a, equals(['v', 'w', 'x']));
+        });
       });
     });
 
@@ -340,12 +422,22 @@
         throwsFormat(parser, ['-mprofile']);
       });
 
-      test('throw if a comma-separated value is not allowed', () {
-        var parser = new ArgParser();
-        parser.addOption('mode',
-            abbr: 'm', allowMultiple: true, allowed: ['debug', 'release']);
+      group('throw if a comma-separated value is not allowed', () {
+        test("with allowMultiple", () {
+          var parser = new ArgParser();
+          parser.addOption('mode',
+              abbr: 'm', allowMultiple: true, allowed: ['debug', 'release']);
 
-        throwsFormat(parser, ['-mdebug,profile']);
+          throwsFormat(parser, ['-mdebug,profile']);
+        });
+
+        test("with addMultiOption", () {
+          var parser = new ArgParser();
+          parser
+              .addMultiOption('mode', abbr: 'm', allowed: ['debug', 'release']);
+
+          throwsFormat(parser, ['-mdebug,profile']);
+        });
       });
 
       test('throw if any but the first is not a flag', () {
@@ -449,22 +541,40 @@
         expect(args['define'], equals('2'));
       });
 
-      test('returns a List if multi-valued', () {
-        var parser = new ArgParser();
-        parser.addOption('define', allowMultiple: true);
-        var args = parser.parse(['--define=1']);
-        expect(args['define'], equals(['1']));
-        args = parser.parse(['--define=1', '--define=2']);
-        expect(args['define'], equals(['1', '2']));
+      group('returns a List', () {
+        test('with allowMultiple', () {
+          var parser = new ArgParser();
+          parser.addOption('define', allowMultiple: true);
+          var args = parser.parse(['--define=1']);
+          expect(args['define'], equals(['1']));
+          args = parser.parse(['--define=1', '--define=2']);
+          expect(args['define'], equals(['1', '2']));
+        });
+
+        test('with addMultiOption', () {
+          var parser = new ArgParser();
+          parser.addMultiOption('define');
+          var args = parser.parse(['--define=1']);
+          expect(args['define'], equals(['1']));
+          args = parser.parse(['--define=1', '--define=2']);
+          expect(args['define'], equals(['1', '2']));
+        });
       });
 
-      test(
-          'returns the default value for multi-valued arguments '
-          'if not explicitly set', () {
-        var parser = new ArgParser();
-        parser.addOption('define', defaultsTo: '0', allowMultiple: true);
-        var args = parser.parse(['']);
-        expect(args['define'], equals(['0']));
+      group('returns the default value if not explicitly set', () {
+        test('with allowMultiple', () {
+          var parser = new ArgParser();
+          parser.addOption('define', defaultsTo: '0', allowMultiple: true);
+          var args = parser.parse(['']);
+          expect(args['define'], equals(['0']));
+        });
+
+        test('with addMultiOption', () {
+          var parser = new ArgParser();
+          parser.addMultiOption('define', defaultsTo: ['0']);
+          var args = parser.parse(['']);
+          expect(args['define'], equals(['0']));
+        });
       });
 
       test('are case-sensitive', () {
diff --git a/test/usage_test.dart b/test/usage_test.dart
index 6c591bf..cef3a16 100644
--- a/test/usage_test.dart
+++ b/test/usage_test.dart
@@ -11,9 +11,7 @@
       var parser = new ArgParser();
       parser.addFlag('mode', help: 'The mode');
 
-      validateUsage(
-          parser,
-          '''
+      validateUsage(parser, '''
           --[no-]mode    The mode
           ''');
     });
@@ -22,9 +20,7 @@
       var parser = new ArgParser();
       parser.addFlag('mode', negatable: false, help: 'The mode');
 
-      validateUsage(
-          parser,
-          '''
+      validateUsage(parser, '''
           --mode    The mode
           ''');
     });
@@ -33,9 +29,7 @@
       var parser = new ArgParser();
       parser.addFlag('mode', help: 'The mode');
 
-      validateUsage(
-          parser,
-          '''
+      validateUsage(parser, '''
           --[no-]mode    The mode
           ''');
     });
@@ -45,9 +39,7 @@
       parser.addFlag('mode', abbr: 'm', help: 'The mode');
       parser.addOption('long', help: 'Lacks an abbreviation');
 
-      validateUsage(
-          parser,
-          '''
+      validateUsage(parser, '''
           -m, --[no-]mode    The mode
               --long         Lacks an abbreviation
           ''');
@@ -58,9 +50,7 @@
       parser.addFlag('mode', abbr: 'm', help: 'Lined up with below');
       parser.addOption('a-really-long-name', help: 'Its help text');
 
-      validateUsage(
-          parser,
-          '''
+      validateUsage(parser, '''
           -m, --[no-]mode             Lined up with below
               --a-really-long-name    Its help text
           ''');
@@ -70,9 +60,7 @@
       var parser = new ArgParser();
       parser.addFlag('mode', help: '\n\n\n\nAfter newlines');
 
-      validateUsage(
-          parser,
-          '''
+      validateUsage(parser, '''
           --[no-]mode    After newlines
           ''');
     });
@@ -81,9 +69,7 @@
       var parser = new ArgParser();
       parser.addFlag('mode', help: 'Before newlines\n\n\n\n');
 
-      validateUsage(
-          parser,
-          '''
+      validateUsage(parser, '''
           --[no-]mode    Before newlines
           ''');
     });
@@ -94,9 +80,7 @@
       parser.addFlag('monkey', help: 'Second');
       parser.addFlag('wombat', help: 'Third');
 
-      validateUsage(
-          parser,
-          '''
+      validateUsage(parser, '''
           --[no-]zebra     First
           --[no-]monkey    Second
           --[no-]wombat    Third
@@ -108,9 +92,7 @@
       parser.addFlag('affirm', help: 'Should be on', defaultsTo: true);
       parser.addFlag('negate', help: 'Should be off', defaultsTo: false);
 
-      validateUsage(
-          parser,
-          '''
+      validateUsage(parser, '''
           --[no-]affirm    Should be on
                            (defaults to on)
 
@@ -122,9 +104,7 @@
       var parser = new ArgParser();
       parser.addOption('any', help: 'Can be anything', defaultsTo: 'whatevs');
 
-      validateUsage(
-          parser,
-          '''
+      validateUsage(parser, '''
           --any    Can be anything
                    (defaults to "whatevs")
           ''');
@@ -135,9 +115,7 @@
       parser.addOption('out',
           abbr: 'o', help: 'Where to write file', valueHelp: 'path');
 
-      validateUsage(
-          parser,
-          '''
+      validateUsage(parser, '''
           -o, --out=<path>    Where to write file
           ''');
     });
@@ -148,9 +126,7 @@
           help: 'Like in cards',
           allowed: ['spades', 'clubs', 'hearts', 'diamonds']);
 
-      validateUsage(
-          parser,
-          '''
+      validateUsage(parser, '''
           --suit    Like in cards
                     [spades, clubs, hearts, diamonds]
           ''');
@@ -163,9 +139,7 @@
           defaultsTo: 'clubs',
           allowed: ['spades', 'clubs', 'hearts', 'diamonds']);
 
-      validateUsage(
-          parser,
-          '''
+      validateUsage(parser, '''
           --suit    Like in cards
                     [spades, clubs (default), hearts, diamonds]
           ''');
@@ -189,9 +163,7 @@
             'hearts': 'The shape of my heart'
           });
 
-      validateUsage(
-          parser,
-          '''
+      validateUsage(parser, '''
           --suit              Like in cards
 
                 [clubs]       Weapons of war
@@ -207,9 +179,7 @@
       parser.addOption('second', hide: true);
       parser.addOption('third', help: 'The third option');
 
-      validateUsage(
-          parser,
-          '''
+      validateUsage(parser, '''
           --first    The first option
           --third    The third option
           ''');
@@ -221,9 +191,7 @@
       parser.addFlag('second', hide: true);
       parser.addFlag('third', help: 'The third flag');
 
-      validateUsage(
-          parser,
-          '''
+      validateUsage(parser, '''
           --[no-]first    The first flag
           --[no-]third    The third flag
           ''');
@@ -235,9 +203,7 @@
       parser.addFlag('second-very-long-option', hide: true);
       parser.addFlag('third', help: 'The third flag');
 
-      validateUsage(
-          parser,
-          '''
+      validateUsage(parser, '''
           --[no-]first    The first flag
           --[no-]third    The third flag
           ''');
@@ -252,9 +218,7 @@
         parser.addSeparator('Marsupial:');
         parser.addFlag('wombat', help: 'Third');
 
-        validateUsage(
-            parser,
-            '''
+        validateUsage(parser, '''
             --[no-]zebra     First
 
             Primate:
@@ -271,9 +235,7 @@
         parser.addSeparator('Primate:');
         parser.addFlag('monkey', help: 'Second');
 
-        validateUsage(
-            parser,
-            '''
+        validateUsage(parser, '''
             --[no-]zebra     Multi
                              line
 
@@ -287,9 +249,7 @@
         parser.addSeparator('Equine:');
         parser.addFlag('zebra', help: 'First');
 
-        validateUsage(
-            parser,
-            '''
+        validateUsage(parser, '''
             Equine:
             --[no-]zebra    First
             ''');
@@ -300,9 +260,7 @@
         parser.addFlag('zebra', help: 'First');
         parser.addSeparator('Primate:');
 
-        validateUsage(
-            parser,
-            '''
+        validateUsage(parser, '''
             --[no-]zebra    First
 
             Primate:
@@ -314,9 +272,7 @@
         parser.addSeparator('First');
         parser.addSeparator('Second');
 
-        validateUsage(
-            parser,
-            '''
+        validateUsage(parser, '''
             First
 
             Second