If no paths are provided, read source from stdin. Fix #165.

R=pquitslund@google.com

Review URL: https://chromiumcodereview.appspot.com//945583004
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b5afed5..e19a191 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@
 * Put a space between nested unary `-` calls (#170).
 * Allow `-t` flag to preserve compability with old formatter (#166).
 * Support `--machine` flag for machine-readable output (#164).
+* If no paths are provided, read source from stdin (#165).
 
 # 0.1.3
 
diff --git a/bin/format.dart b/bin/format.dart
index a1644b4..d6be2b9 100644
--- a/bin/format.dart
+++ b/bin/format.dart
@@ -1,6 +1,13 @@
+// Copyright (c) 2015, 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.
+
+import 'dart:convert';
 import 'dart:io';
 
 import 'package:args/args.dart';
+import 'package:dart_style/src/dart_formatter.dart';
+import 'package:dart_style/src/formatter_exception.dart';
 import 'package:dart_style/src/formatter_options.dart';
 import 'package:dart_style/src/io.dart';
 
@@ -15,13 +22,12 @@
   parser.addFlag("dry-run", abbr: "n", negatable: false,
       help: "Show which files would be modified but make no changes.");
   parser.addFlag("overwrite", abbr: "w", negatable: false,
-      help: "Overwrite input files with formatted output.\n"
-            "If unset, prints results to standard output.");
+      help: "Overwrite input files with formatted output.");
+  parser.addFlag("machine", abbr: "m", negatable: false,
+      help: "Produce machine-readable JSON output.");
   parser.addFlag("follow-links", negatable: false,
       help: "Follow links to files and directories.\n"
             "If unset, links will be ignored.");
-  parser.addFlag("machine", abbr: "m", negatable: false,
-      help: "Produce machine-readable JSON output.");
   parser.addFlag("transform", abbr: "t", negatable: false,
       help: "Unused flag for compability with the old formatter.");
 
@@ -47,22 +53,29 @@
   }
 
   checkForReporterCollision(String chosen, String other) {
-    if (argResults[other]) {
-      printUsage(parser,
-          "Cannot use --$chosen and --$other at the same time.");
-      exitCode = 64;
-      return;
-    }
+    if (!argResults[other]) return false;
+
+    printUsage(parser,
+        "Cannot use --$chosen and --$other at the same time.");
+    exitCode = 64;
+    return true;
   }
 
   var reporter = OutputReporter.print;
   if (argResults["dry-run"]) {
-    checkForReporterCollision("dry-run", "overwrite");
-    checkForReporterCollision("dry-run", "machine");
+    if (checkForReporterCollision("dry-run", "overwrite")) return;
+    if (checkForReporterCollision("dry-run", "machine")) return;
 
     reporter = OutputReporter.dryRun;
   } else if (argResults["overwrite"]) {
-    checkForReporterCollision("overwrite", "machine");
+    if (checkForReporterCollision("overwrite", "machine")) return;
+
+    if (argResults.rest.isEmpty) {
+      printUsage(parser,
+          "Cannot use --overwrite without providing any paths to format.");
+      exitCode = 64;
+      return;
+    }
 
     reporter = OutputReporter.overwrite;
   } else if (argResults["machine"]) {
@@ -82,17 +95,41 @@
 
   var followLinks = argResults["follow-links"];
 
-  if (argResults.rest.isEmpty) {
-    printUsage(parser,
-        "Please provide at least one directory or file to format.");
-    exitCode = 64;
-    return;
-  }
-
   var options = new FormatterOptions(reporter,
       pageWidth: pageWidth, followLinks: followLinks);
 
-  for (var path in argResults.rest) {
+  if (argResults.rest.isEmpty) {
+    formatStdin(options);
+  } else {
+    formatPaths(options, argResults.rest);
+  }
+}
+
+/// Reads input from stdin until it's closed, and the formats it.
+void formatStdin(FormatterOptions options) {
+  var input = new StringBuffer();
+  stdin.transform(new Utf8Decoder()).listen(input.write, onDone: () {
+    var formatter = new DartFormatter(pageWidth: options.pageWidth);
+    try {
+      var source = input.toString();
+      var output = formatter.format(source, uri: "stdin");
+      options.reporter.showFile(null, "<stdin>", output,
+          changed: source != output);
+      return true;
+    } on FormatterException catch (err) {
+      stderr.writeln(err.message());
+    } catch (err, stack) {
+      stderr.writeln('''Hit a bug in the formatter when formatting stdin.
+Please report at: github.com/dart-lang/dart_style/issues
+$err
+$stack''');
+    }
+  });
+}
+
+/// Formats all of the files and directories given by [paths].
+void formatPaths(FormatterOptions options, List<String> paths) {
+  for (var path in paths) {
     var directory = new Directory(path);
     if (directory.existsSync()) {
       if (!processDirectory(options, directory)) {
@@ -123,7 +160,7 @@
 
   output.write("""$message
 
-Usage: dartformat [-w] <files or directories...>
+Usage: dartformat [-n|-w] [files or directories...]
 
 ${parser.usage}
 """);
diff --git a/example/format.dart b/example/format.dart
index b84924e..cb9de73 100644
--- a/example/format.dart
+++ b/example/format.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, 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.
+
 import 'package:dart_style/dart_style.dart';
 
 import 'package:dart_style/src/debug.dart';
diff --git a/lib/src/io.dart b/lib/src/io.dart
index 10927b1..a1f5361 100644
--- a/lib/src/io.dart
+++ b/lib/src/io.dart
@@ -60,8 +60,8 @@
   } on FormatterException catch (err) {
     stderr.writeln(err.message());
   } catch (err, stack) {
-    stderr.writeln('''Hit a bug in the formatter when formatting $label
-  Please report at: github.com/dart-lang/dart_style/issues
+    stderr.writeln('''Hit a bug in the formatter when formatting $label.
+Please report at: github.com/dart-lang/dart_style/issues
 $err
 $stack''');
   }
diff --git a/test/command_line_test.dart b/test/command_line_test.dart
index 64f31ac..8b130b6 100644
--- a/test/command_line_test.dart
+++ b/test/command_line_test.dart
@@ -20,12 +20,12 @@
       d.file("a.dart", unformattedSource)
     ]).create();
 
-    var process = runFormatter();
+    var process = runFormatterOnDir();
     process.shouldExit(0);
   });
 
   test("Exits with 64 on a command line argument error.", () {
-    var process = runFormatter(["-wat"]);
+    var process = runFormatterOnDir(["-wat"]);
     process.shouldExit(64);
   });
 
@@ -34,7 +34,7 @@
       d.file("a.dart", "herp derp i are a dart")
     ]).create();
 
-    var process = runFormatter();
+    var process = runFormatterOnDir();
     process.shouldExit(65);
   });
 
@@ -43,7 +43,25 @@
       d.file("a.dart", unformattedSource)
     ]).create();
 
-    var process = runFormatter(["--dry-run", "--overwrite"]);
+    var process = runFormatterOnDir(["--dry-run", "--overwrite"]);
+    process.shouldExit(64);
+  });
+
+  test("Errors if --dry-run and --machine are both passed.", () {
+    d.dir("code", [
+      d.file("a.dart", unformattedSource)
+    ]).create();
+
+    var process = runFormatterOnDir(["--dry-run", "--machine"]);
+    process.shouldExit(64);
+  });
+
+  test("Errors if --machine and --overwrite are both passed.", () {
+    d.dir("code", [
+      d.file("a.dart", unformattedSource)
+    ]).create();
+
+    var process = runFormatterOnDir(["--machine", "--overwrite"]);
     process.shouldExit(64);
   });
 
@@ -74,7 +92,7 @@
         d.file("d_good.dart", formattedSource)
       ]).create();
 
-      var process = runFormatter(["--dry-run"]);
+      var process = runFormatterOnDir(["--dry-run"]);
       process.stdout.expect(p.join("code", "a_bad.dart"));
       process.stdout.expect(p.join("code", "c_bad.dart"));
       process.shouldExit();
@@ -85,13 +103,13 @@
         d.file("a.dart", unformattedSource)
       ]).create();
 
-      var process = runFormatter(["--dry-run"]);
+      var process = runFormatterOnDir(["--dry-run"]);
       process.stdout.expect(p.join("code", "a.dart"));
       process.shouldExit();
 
       d.dir('code', [
         d.file('a.dart', unformattedSource)
-    ]).validate();
+      ]).validate();
     });
   });
 
@@ -102,7 +120,7 @@
         d.file("b.dart", unformattedSource)
       ]).create();
 
-      var process = runFormatter(["--machine", "code"]);
+      var process = runFormatterOnDir(["--machine"]);
 
       var json = {
         "path": p.join("code", "a.dart"),
@@ -118,4 +136,21 @@
       process.shouldExit();
     });
   });
+
+  group("with no paths", () {
+    test("errors on --overwrite.", () {
+      var process = runFormatter(["--overwrite"]);
+      process.shouldExit(64);
+    });
+
+    test("reads from stdin.", () {
+      var process = runFormatter();
+      process.writeLine(unformattedSource);
+      process.closeStdin();
+
+      // No trailing newline at the end.
+      process.stdout.expect(formattedSource.trimRight());
+      process.shouldExit();
+    });
+  });
 }
diff --git a/test/utils.dart b/test/utils.dart
index de8db01..56b7d7d 100644
--- a/test/utils.dart
+++ b/test/utils.dart
@@ -15,16 +15,24 @@
 const unformattedSource = 'void  main()  =>  print("hello") ;';
 const formattedSource = 'void main() => print("hello");\n';
 
+/// Runs the command line formatter, passing it [args].
 ScheduledProcess runFormatter([List<String> args]) {
   if (args == null) args = [];
 
   var formatterPath = p.join(
       p.dirname(p.fromUri(Platform.script)), "..", "bin", "format.dart");
 
-  args.insertAll(0, [formatterPath, d.defaultRoot]);
+  args.insert(0, formatterPath);
   return new ScheduledProcess.start(Platform.executable, args);
 }
 
+/// Runs the command line formatter, passing it the test directory followed by
+/// [args].
+ScheduledProcess runFormatterOnDir([List<String> args]) {
+  if (args == null) args = [];
+  return runFormatter([d.defaultRoot]..addAll(args));
+}
+
 /// Set up the scheduled test suite.
 ///
 /// Configures the unit test output and makes a sandbox directory for the