Add optional wrapping of text for usage messages.
diff --git a/lib/src/allow_anything_parser.dart b/lib/src/allow_anything_parser.dart
index 248a39a..34a0b19 100644
--- a/lib/src/allow_anything_parser.dart
+++ b/lib/src/allow_anything_parser.dart
@@ -13,6 +13,7 @@
   Map<String, ArgParser> get commands => const {};
   bool get allowTrailingOptions => false;
   bool get allowsAnything => true;
+  int get maxLineLength => null;
 
   ArgParser addCommand(String name, [ArgParser parser]) {
     throw new UnsupportedError(
diff --git a/lib/src/arg_parser.dart b/lib/src/arg_parser.dart
index bcdb8df..a25a8b8 100644
--- a/lib/src/arg_parser.dart
+++ b/lib/src/arg_parser.dart
@@ -30,6 +30,18 @@
   /// arguments.
   final bool allowTrailingOptions;
 
+  /// An optional maximum line length for [usage] messages.
+  ///
+  /// If specified, then help messages in the usage are wrapped at the given
+  /// column, after taking into account the width of the options. Will refuse to
+  /// wrap help text to less than 10 characters of help text per line if there
+  /// isn't enough space on the line. It preserves embedded newlines, and
+  /// attempts to wrap at whitespace breaks (although it will split words if
+  /// there is no whitespace at which to split).
+  ///
+  /// If null (the default), help messages are not wrapped.
+  final int maxLineLength;
+
   /// Whether or not this parser treats unrecognized options as non-option
   /// arguments.
   bool get allowsAnything => false;
@@ -40,9 +52,10 @@
   /// flags and options that appear after positional arguments. If it's `false`,
   /// the parser stops parsing as soon as it finds an argument that is neither
   /// an option nor a command.
-  factory ArgParser({bool allowTrailingOptions: true}) =>
+  factory ArgParser({bool allowTrailingOptions: true, int maxLineLength}) =>
       new ArgParser._(<String, Option>{}, <String, ArgParser>{},
-          allowTrailingOptions: allowTrailingOptions);
+          allowTrailingOptions: allowTrailingOptions,
+          maxLineLength: maxLineLength);
 
   /// Creates a new ArgParser that treats *all input* as non-option arguments.
   ///
@@ -53,7 +66,7 @@
   factory ArgParser.allowAnything() = AllowAnythingParser;
 
   ArgParser._(Map<String, Option> options, Map<String, ArgParser> commands,
-      {bool allowTrailingOptions: true})
+      {bool allowTrailingOptions: true, this.maxLineLength})
       : this._options = options,
         this.options = new UnmodifiableMapView(options),
         this._commands = commands,
@@ -315,7 +328,10 @@
   /// Generates a string displaying usage information for the defined options.
   ///
   /// This is basically the help text shown on the command line.
-  String get usage => new Usage(_optionsAndSeparators).generate();
+  String get usage {
+    return new Usage(_optionsAndSeparators, maxLineLength: maxLineLength)
+        .generate();
+  }
 
   /// Get the default value for an option. Useful after parsing to test if the
   /// user specified something other than the default.
diff --git a/lib/src/usage.dart b/lib/src/usage.dart
index aa3ed07..9898b44 100644
--- a/lib/src/usage.dart
+++ b/lib/src/usage.dart
@@ -51,7 +51,12 @@
   /// content.
   int newlinesNeeded = 0;
 
-  Usage(this.optionsAndSeparators);
+  /// The horizontal character position at which help text is wrapped. Help that
+  /// extends past this column will be wrapped at the nearest whitespace (or
+  /// truncated if there is no available whitespace).
+  final int maxLineLength;
+
+  Usage(this.optionsAndSeparators, {this.maxLineLength});
 
   /// Generates a string displaying usage information for the defined options.
   /// This is basically the help text shown on the command line.
@@ -171,8 +176,52 @@
     numHelpLines = 0;
   }
 
+  /// Wrap a single line of text into lines no longer than length.
+  /// Try to split at whitespace, but if that's not good enough to keep it
+  /// under the length, then split in the middle of a word.
+  List<String> wrap(String text, int length) {
+    assert(length > 0, 'Wrap length must be larger than zero.');
+    text = text.trim();
+    if (text.length <= length) {
+      return [text];
+    }
+    var result = <String>[];
+    int currentLineStart = 0;
+    int lastWhitespace;
+    for (int i = 0; i < text.length; ++i) {
+      if (isWhitespace(text, i)) {
+        lastWhitespace = i;
+      }
+      if ((i - currentLineStart) >= length) {
+        // Back up to the last whitespace, unless there wasn't any, in which
+        // case we just split where we are.
+        if (lastWhitespace != null) {
+          i = lastWhitespace;
+        }
+        result.add(text.substring(currentLineStart, i));
+        // Skip any intervening whitespace.
+        while (isWhitespace(text, i) && i < text.length) i++;
+        currentLineStart = i;
+        lastWhitespace = null;
+      }
+    }
+    result.add(text.substring(currentLineStart));
+    return result;
+  }
+
   void write(int column, String text) {
+    const int minColumnWidth = 10;
     var lines = text.split('\n');
+    if (column == columnWidths.length && maxLineLength != null) {
+      var wrappedLines = <String>[];
+      var start = 0;
+      columnWidths.sublist(0, column).forEach((int width) => start += width);
+      for (var line in lines) {
+        wrappedLines
+            .addAll(wrap(line, max(maxLineLength - start, minColumnWidth)));
+      }
+      lines = wrappedLines;
+    }
 
     // Strip leading and trailing empty lines.
     while (lines.length > 0 && lines[0].trim() == '') {
@@ -261,3 +310,20 @@
 
   return result.toString();
 }
+
+bool isWhitespace(String text, int index) {
+  final int rune = text.codeUnitAt(index);
+  return ((rune >= 0x0009 && rune <= 0x000D) ||
+      rune == 0x0020 ||
+      rune == 0x0085 ||
+      rune == 0x00A0 ||
+      rune == 0x1680 ||
+      rune == 0x180E ||
+      (rune >= 0x2000 && rune <= 0x200A) ||
+      rune == 0x2028 ||
+      rune == 0x2029 ||
+      rune == 0x202F ||
+      rune == 0x205F ||
+      rune == 0x3000 ||
+      rune == 0xFEFF);
+}
diff --git a/test/usage_test.dart b/test/usage_test.dart
index 48a33dd..3bd228c 100644
--- a/test/usage_test.dart
+++ b/test/usage_test.dart
@@ -314,6 +314,102 @@
           ''');
     });
 
+    test("help strings are not wrapped if maxLineLength is null", () {
+      var parser = new ArgParser(maxLineLength: null);
+      parser.addFlag('long',
+          help: 'The flag with a really long help text that will not '
+              'be wrapped.');
+      validateUsage(parser, '''
+          --[no-]long    The flag with a really long help text that will not be wrapped.
+          ''');
+    });
+
+    test("help strings are wrapped properly when maxLineLength is specified",
+        () {
+      var parser = new ArgParser(maxLineLength: 60);
+      parser.addFlag('long',
+          help: 'The flag with a really long help text that will be wrapped.');
+      parser.addFlag('longNewline',
+          help: 'The flag with a really long help text and newlines\n\nthat '
+              'will still be wrapped because it is really long.');
+      parser.addFlag('solid',
+          help:
+              'The-flag-with-no-whitespace-that-will-be-wrapped-by-splitting-a-word.');
+      parser.addFlag('small1', help: ' a ');
+      parser.addFlag('small2', help: ' a');
+      parser.addFlag('small3', help: 'a ');
+      validateUsage(parser, '''
+          --[no-]long           The flag with a really long help text
+                                that will be wrapped.
+
+          --[no-]longNewline    The flag with a really long help text
+                                and newlines
+                                
+                                that will still be wrapped because it
+                                is really long.
+
+          --[no-]solid          The-flag-with-no-whitespace-that-will-
+                                be-wrapped-by-splitting-a-word.
+
+          --[no-]small1         a
+          --[no-]small2         a
+          --[no-]small3         a
+          ''');
+    });
+
+    test(
+        "help strings are wrapped with at 10 chars when maxHelpLineLength is "
+        "smaller than available space", () {
+      var parser = new ArgParser(maxLineLength: 1);
+      parser.addFlag('long',
+          help: 'The flag with a really long help text that will be wrapped.');
+      parser.addFlag('longNewline',
+          help:
+              'The flag with a really long help text and newlines\n\nthat will '
+              'still be wrapped because it is really long.');
+      parser.addFlag('solid',
+          help:
+              'The-flag-with-no-whitespace-that-will-be-wrapped-by-splitting-a-word.');
+      parser.addFlag('small1', help: ' a ');
+      parser.addFlag('small2', help: ' a');
+      parser.addFlag('small3', help: 'a ');
+      validateUsage(parser, '''
+          --[no-]long           The flag
+                                with a
+                                really
+                                long help
+                                text that
+                                will be
+                                wrapped.
+          
+          --[no-]longNewline    The flag
+                                with a
+                                really
+                                long help
+                                text and
+                                newlines
+                                
+                                that will
+                                still be
+                                wrapped
+                                because it
+                                is really
+                                long.
+          
+          --[no-]solid          The-flag-w
+                                ith-no-whi
+                                tespace-th
+                                at-will-be
+                                -wrapped-b
+                                y-splittin
+                                g-a-word.
+          
+          --[no-]small1         a
+          --[no-]small2         a
+          --[no-]small3         a
+          ''');
+    });
+
     group("separators", () {
       test("separates options where it's placed", () {
         var parser = new ArgParser();