Add utilities to sharedStdIn. (dart-lang/io#26)

diff --git a/pkgs/io/CHANGELOG.md b/pkgs/io/CHANGELOG.md
index a16a183..3dc176d 100644
--- a/pkgs/io/CHANGELOG.md
+++ b/pkgs/io/CHANGELOG.md
@@ -1,3 +1,19 @@
+## 0.3.1
+
+- Added `SharedStdIn.nextLine` (similar to `readLineSync`) and `lines`:
+
+```dart
+main() async {
+  // Prints the first line entered on stdin.
+  print(await sharedStdIn.nextLine());
+  
+  // Prints all remaining lines.
+  await for (final line in sharedStdIn.lines) {
+    print(line);
+  }
+}
+```
+
 ## 0.3.0
 
 - **BREAKING CHANGE**: The `arguments` argument to `ProcessManager.spawn` is
@@ -5,7 +21,9 @@
   built-in `Process.start`, and easier to use as a drop in replacement:
 
 ```dart
-processManager.spawn('dart', ['--version']);
+main() {
+  processManager.spawn('dart', ['--version']);
+}
 ```
 
 - Fixed a bug where processes created from `ProcessManager.spawn` could not
diff --git a/pkgs/io/lib/src/shared_stdin.dart b/pkgs/io/lib/src/shared_stdin.dart
index 79ff97d..23b3d50 100644
--- a/pkgs/io/lib/src/shared_stdin.dart
+++ b/pkgs/io/lib/src/shared_stdin.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:convert';
 import 'dart:io';
 
 import 'package:meta/meta.dart';
@@ -32,6 +33,28 @@
     _sub = (stream ??= stdin).listen(_onInput);
   }
 
+  /// Returns a future that completes with the next line.
+  ///
+  /// This is similar to the standard [Stdin.readLineSync], but asynchronous.
+  Future<String> nextLine({Encoding encoding: SYSTEM_ENCODING}) {
+    return lines(encoding: encoding).first;
+  }
+
+  /// Returns the stream transformed as UTF8 strings separated by line breaks.
+  ///
+  /// This is similar to synchronous code using [Stdin.readLineSync]:
+  /// ```dart
+  /// while (true) {
+  ///   var line = stdin.readLineSync();
+  ///   // ...
+  /// }
+  /// ```
+  ///
+  /// ... but asynchronous.
+  Stream<String> lines({Encoding encoding: SYSTEM_ENCODING}) {
+    return transform(UTF8.decoder).transform(const LineSplitter());
+  }
+
   void _onInput(List<int> event) => _getCurrent().add(event);
 
   StreamController<List<int>> _getCurrent() {
diff --git a/pkgs/io/test/shared_stdin_test.dart b/pkgs/io/test/shared_stdin_test.dart
index 722ec1f..82e833f 100644
--- a/pkgs/io/test/shared_stdin_test.dart
+++ b/pkgs/io/test/shared_stdin_test.dart
@@ -43,4 +43,37 @@
     await active.cancel();
     expect(() => sharedStdIn.listen((_) {}), returnsNormally);
   });
+
+  test('should return a stream of lines', () async {
+    expect(
+      sharedStdIn.lines(),
+      emitsInOrder(<dynamic>[
+        'I',
+        'Think',
+        'Therefore',
+        'I',
+        'Am',
+      ]),
+    );
+    [
+      'I\nThink\n',
+      'Therefore\n',
+      'I\n',
+      'Am\n',
+    ].forEach(fakeStdIn.add);
+  });
+
+  test('should return the next line', () {
+    expect(sharedStdIn.nextLine(), completion('Hello World'));
+    fakeStdIn.add('Hello World\n');
+  });
+
+  test('should allow listening for new lines multiple times', () async {
+    expect(sharedStdIn.nextLine(), completion('Hello World'));
+    fakeStdIn.add('Hello World\n');
+    await new Future<Null>.value();
+
+    expect(sharedStdIn.nextLine(), completion('Hello World'));
+    fakeStdIn.add('Hello World\n');
+  });
 }