Reorganize the old command line code to get ready for the new CLI.

The only functionality change is that "--verbose" is no longer a
supported option. It was only used for help, and this just always shows
all the help now.

The other changes mostly move code out of bin/format.dart so that
relevant pieces can be shared with the new CLI.
diff --git a/bin/format.dart b/bin/format.dart
index fdca0f8..834dddd 100644
--- a/bin/format.dart
+++ b/bin/format.dart
@@ -2,87 +2,18 @@
 // 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/exceptions.dart';
-import 'package:dart_style/src/formatter_options.dart';
+import 'package:dart_style/src/cli/formatter_options.dart';
+import 'package:dart_style/src/cli/options.dart';
 import 'package:dart_style/src/io.dart';
-import 'package:dart_style/src/source_code.dart';
 import 'package:dart_style/src/style_fix.dart';
 
-// Note: The following line of code is modified by tool/grind.dart.
-const version = '1.3.3';
-
 void main(List<String> args) {
   var parser = ArgParser(allowTrailingOptions: true);
 
-  var verbose = args.contains('-v') || args.contains('--verbose');
-  var hide = !verbose;
-
-  parser.addSeparator('Common options:');
-  parser.addFlag('help',
-      abbr: 'h',
-      negatable: false,
-      help:
-          'Shows this usage information.  Add --verbose to show hidden options.');
-  parser.addFlag('version',
-      negatable: false, help: 'Shows version information.');
-  parser.addFlag('verbose',
-      abbr: 'v', negatable: false, help: 'Verbose output.');
-  parser.addOption('line-length',
-      abbr: 'l', help: 'Wrap lines longer than this.', defaultsTo: '80');
-  parser.addFlag('overwrite',
-      abbr: 'w',
-      negatable: false,
-      help: 'Overwrite input files with formatted output.');
-  parser.addFlag('dry-run',
-      abbr: 'n',
-      negatable: false,
-      help: 'Show which files would be modified but make no changes.');
-
-  parser.addSeparator('Non-whitespace fixes (off by default):');
-  parser.addFlag('fix', negatable: false, help: 'Apply all style fixes.');
-
-  for (var fix in StyleFix.all) {
-    // TODO(rnystrom): Allow negating this if used in concert with "--fix"?
-    parser.addFlag('fix-${fix.name}', negatable: false, help: fix.description);
-  }
-
-  if (verbose) {
-    parser.addSeparator('Other options:');
-  }
-  parser.addOption('indent',
-      abbr: 'i',
-      help: 'Spaces of leading indentation.',
-      defaultsTo: '0',
-      hide: hide);
-  parser.addFlag('machine',
-      abbr: 'm',
-      negatable: false,
-      help: 'Produce machine-readable JSON output.',
-      hide: hide);
-  parser.addFlag('set-exit-if-changed',
-      negatable: false,
-      help: 'Return exit code 1 if there are any formatting changes.',
-      hide: hide);
-  parser.addFlag('follow-links',
-      negatable: false,
-      help: 'Follow links to files and directories.\n'
-          'If unset, links will be ignored.',
-      hide: hide);
-  parser.addOption('preserve',
-      help: 'Selection to preserve, formatted as "start:length".', hide: hide);
-  parser.addOption('stdin-name',
-      help: 'The path name to show when an error occurs in source read from '
-          'stdin.',
-      defaultsTo: '<stdin>',
-      hide: hide);
-
-  parser.addFlag('profile', negatable: false, hide: true);
-  parser.addFlag('transform', abbr: 't', negatable: false, hide: true);
+  defineOptions(parser);
 
   ArgResults argResults;
   try {
@@ -97,7 +28,7 @@
   }
 
   if (argResults['version']) {
-    print(version);
+    print(dartStyleVersion);
     return;
   }
 
@@ -108,7 +39,7 @@
   }
 
   try {
-    selection = parseSelection(argResults['preserve']);
+    selection = parseSelection(argResults, 'preserve');
   } on FormatException catch (_) {
     usageError(
         parser,
@@ -210,79 +141,6 @@
   }
 }
 
-List<int> parseSelection(String selection) {
-  if (selection == null) return null;
-
-  var coordinates = selection.split(':');
-  if (coordinates.length != 2) {
-    throw FormatException(
-        'Selection should be a colon-separated pair of integers, "123:45".');
-  }
-
-  return coordinates.map((coord) => coord.trim()).map(int.parse).toList();
-}
-
-/// Reads input from stdin until it's closed, and the formats it.
-void formatStdin(FormatterOptions options, List<int> selection, String name) {
-  var selectionStart = 0;
-  var selectionLength = 0;
-
-  if (selection != null) {
-    selectionStart = selection[0];
-    selectionLength = selection[1];
-  }
-
-  var input = StringBuffer();
-  stdin.transform(Utf8Decoder()).listen(input.write, onDone: () {
-    var formatter = DartFormatter(
-        indent: options.indent,
-        pageWidth: options.pageWidth,
-        fixes: options.fixes);
-    try {
-      options.reporter.beforeFile(null, name);
-      var source = SourceCode(input.toString(),
-          uri: name,
-          selectionStart: selectionStart,
-          selectionLength: selectionLength);
-      var output = formatter.formatSource(source);
-      options.reporter
-          .afterFile(null, name, output, changed: source.text != output.text);
-      return;
-    } on FormatterException catch (err) {
-      stderr.writeln(err.message());
-      exitCode = 65; // sysexits.h: EX_DATAERR
-    } 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''');
-      exitCode = 70; // sysexits.h: EX_SOFTWARE
-    }
-  });
-}
-
-/// Formats all of the files and directories given by [paths].
-void formatPaths(FormatterOptions options, List<String> paths) {
-  for (var path in paths) {
-    var directory = Directory(path);
-    if (directory.existsSync()) {
-      if (!processDirectory(options, directory)) {
-        exitCode = 65;
-      }
-      continue;
-    }
-
-    var file = File(path);
-    if (file.existsSync()) {
-      if (!processFile(options, file)) {
-        exitCode = 65;
-      }
-    } else {
-      stderr.writeln('No file or directory found at "$path".');
-    }
-  }
-}
-
 /// Prints [error] and usage help then exits with exit code 64.
 void usageError(ArgParser parser, String error) {
   printUsage(parser, error);
diff --git a/lib/src/formatter_options.dart b/lib/src/cli/formatter_options.dart
similarity index 97%
rename from lib/src/formatter_options.dart
rename to lib/src/cli/formatter_options.dart
index b4073a6..6250e88 100644
--- a/lib/src/formatter_options.dart
+++ b/lib/src/cli/formatter_options.dart
@@ -7,8 +7,11 @@
 import 'dart:convert';
 import 'dart:io';
 
-import 'source_code.dart';
-import 'style_fix.dart';
+import '../source_code.dart';
+import '../style_fix.dart';
+
+// Note: The following line of code is modified by tool/grind.dart.
+const dartStyleVersion = '1.3.3';
 
 /// Global options that affect how the formatter produces and uses its outputs.
 class FormatterOptions {
diff --git a/lib/src/cli/options.dart b/lib/src/cli/options.dart
new file mode 100644
index 0000000..455a68b
--- /dev/null
+++ b/lib/src/cli/options.dart
@@ -0,0 +1,89 @@
+// Copyright (c) 2020, 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:args/args.dart';
+
+import '../style_fix.dart';
+
+void defineOptions(ArgParser parser) {
+  parser.addSeparator('Common options:');
+
+  // Command implicitly adds "--help", so we only need to manually add it for
+  // the old CLI.
+  parser.addFlag('help',
+      abbr: 'h', negatable: false, help: 'Shows this usage information.');
+
+  parser.addFlag('overwrite',
+      abbr: 'w',
+      negatable: false,
+      help: 'Overwrite input files with formatted output.');
+  parser.addFlag('dry-run',
+      abbr: 'n',
+      negatable: false,
+      help: 'Show which files would be modified but make no changes.');
+
+  parser.addSeparator('Non-whitespace fixes (off by default):');
+  parser.addFlag('fix', negatable: false, help: 'Apply all style fixes.');
+
+  for (var fix in StyleFix.all) {
+    // TODO(rnystrom): Allow negating this if used in concert with "--fix"?
+    parser.addFlag('fix-${fix.name}', negatable: false, help: fix.description);
+  }
+
+  parser.addSeparator('Other options:');
+
+  parser.addOption('line-length',
+      abbr: 'l', help: 'Wrap lines longer than this.', defaultsTo: '80');
+  parser.addOption('indent',
+      abbr: 'i', help: 'Spaces of leading indentation.', defaultsTo: '0');
+  parser.addFlag('machine',
+      abbr: 'm',
+      negatable: false,
+      help: 'Produce machine-readable JSON output.');
+  parser.addFlag('set-exit-if-changed',
+      negatable: false,
+      help: 'Return exit code 1 if there are any formatting changes.');
+  parser.addFlag('follow-links',
+      negatable: false,
+      help: 'Follow links to files and directories.\n'
+          'If unset, links will be ignored.');
+  parser.addFlag('version',
+      negatable: false, help: 'Show version information.');
+
+  parser.addSeparator('Options when formatting from stdin:');
+
+  parser.addOption('preserve',
+      help: 'Selection to preserve formatted as "start:length".');
+  parser.addOption('stdin-name',
+      help: 'The path name to show when an error occurs.',
+      defaultsTo: '<stdin>');
+  parser.addFlag('profile', negatable: false, hide: true);
+
+  // Ancient no longer used flag.
+  parser.addFlag('transform', abbr: 't', negatable: false, hide: true);
+}
+
+List<int> parseSelection(ArgResults argResults, String optionName) {
+  var option = argResults[optionName];
+  if (option == null) return null;
+
+  // Can only preserve a selection when parsing from stdin.
+  if (argResults.rest.isNotEmpty) {
+    throw FormatException(
+        'Can only use --$optionName when reading from stdin.');
+  }
+
+  try {
+    var coordinates = option.split(':');
+    if (coordinates.length != 2) {
+      throw FormatException(
+          'Selection should be a colon-separated pair of integers, "123:45".');
+    }
+
+    return coordinates.map<int>((coord) => int.parse(coord.trim())).toList();
+  } on FormatException catch (_) {
+    throw FormatException(
+        '--$optionName must be a colon-separated pair of integers, was '
+        '"${argResults[optionName]}".');
+  }
+}
diff --git a/lib/src/io.dart b/lib/src/io.dart
index a69e500..c6a70c4 100644
--- a/lib/src/io.dart
+++ b/lib/src/io.dart
@@ -4,15 +4,77 @@
 
 library dart_style.src.io;
 
+import 'dart:convert';
 import 'dart:io';
 
 import 'package:path/path.dart' as p;
 
+import 'cli/formatter_options.dart';
 import 'dart_formatter.dart';
 import 'exceptions.dart';
-import 'formatter_options.dart';
 import 'source_code.dart';
 
+/// Reads input from stdin until it's closed, and the formats it.
+void formatStdin(FormatterOptions options, List<int> selection, String name) {
+  var selectionStart = 0;
+  var selectionLength = 0;
+
+  if (selection != null) {
+    selectionStart = selection[0];
+    selectionLength = selection[1];
+  }
+
+  var input = StringBuffer();
+  stdin.transform(Utf8Decoder()).listen(input.write, onDone: () {
+    var formatter = DartFormatter(
+        indent: options.indent,
+        pageWidth: options.pageWidth,
+        fixes: options.fixes);
+    try {
+      options.reporter.beforeFile(null, name);
+      var source = SourceCode(input.toString(),
+          uri: name,
+          selectionStart: selectionStart,
+          selectionLength: selectionLength);
+      var output = formatter.formatSource(source);
+      options.reporter
+          .afterFile(null, name, output, changed: source.text != output.text);
+      return;
+    } on FormatterException catch (err) {
+      stderr.writeln(err.message());
+      exitCode = 65; // sysexits.h: EX_DATAERR
+    } 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''');
+      exitCode = 70; // sysexits.h: EX_SOFTWARE
+    }
+  });
+}
+
+/// Formats all of the files and directories given by [paths].
+void formatPaths(FormatterOptions options, List<String> paths) {
+  for (var path in paths) {
+    var directory = Directory(path);
+    if (directory.existsSync()) {
+      if (!processDirectory(options, directory)) {
+        exitCode = 65;
+      }
+      continue;
+    }
+
+    var file = File(path);
+    if (file.existsSync()) {
+      if (!processFile(options, file)) {
+        exitCode = 65;
+      }
+    } else {
+      stderr.writeln('No file or directory found at "$path".');
+    }
+  }
+}
+
 /// Runs the formatter on every .dart file in [path] (and its subdirectories),
 /// and replaces them with their formatted output.
 ///
@@ -24,8 +86,11 @@
   var success = true;
   var shownHiddenPaths = <String>{};
 
-  for (var entry in directory.listSync(
-      recursive: true, followLinks: options.followLinks)) {
+  var entries =
+      directory.listSync(recursive: true, followLinks: options.followLinks);
+  entries.sort((a, b) => a.path.compareTo(b.path));
+
+  for (var entry in entries) {
     var relative = p.relative(entry.path, from: directory.path);
 
     if (entry is Link) {
diff --git a/test/command_line_test.dart b/test/command_line_test.dart
index ce17327..9bbaf07 100644
--- a/test/command_line_test.dart
+++ b/test/command_line_test.dart
@@ -13,11 +13,26 @@
 import 'utils.dart';
 
 void main() {
-  test('exits with 0 on success', () async {
-    await d.dir('code', [d.file('a.dart', unformattedSource)]).create();
+  test('formats a directory', () async {
+    await d.dir('code', [
+      d.file('a.dart', unformattedSource),
+      d.file('b.dart', formattedSource),
+      d.file('c.dart', unformattedSource)
+    ]).create();
 
     var process = await runFormatterOnDir();
+    await expectLater(
+        process.stdout, emits(startsWith('Formatting directory')));
+
+    // Prints the formatting result.
+    await expectLater(process.stdout, emits(formattedOutput));
+    await expectLater(process.stdout, emits(formattedOutput));
+    await expectLater(process.stdout, emits(formattedOutput));
     await process.shouldExit(0);
+
+    // Does not overwrite by default.
+    await d.dir('code', [d.file('a.dart', unformattedSource)]).validate();
+    await d.dir('code', [d.file('c.dart', unformattedSource)]).validate();
   });
 
   test('exits with 64 on a command line argument error', () async {
@@ -77,19 +92,6 @@
     await expectLater(process.stdout, emits(''));
     await expectLater(process.stdout,
         emits('Usage:   dartfmt [options...] [files or directories...]'));
-    await expectLater(process.stdout, neverEmits('Other options:'));
-    await process.shouldExit(0);
-  });
-
-  test('--help --verbose', () async {
-    var process = await runFormatter(['--help', '--verbose']);
-    await expectLater(
-        process.stdout, emits('Idiomatically formats Dart source code.'));
-    await expectLater(process.stdout, emits(''));
-    await expectLater(process.stdout,
-        emits('Usage:   dartfmt [options...] [files or directories...]'));
-    await expectLater(process.stdout, emitsThrough('Other options:'));
-    await expectLater(process.stdout, emits(startsWith('-i, --indent')));
     await process.shouldExit(0);
   });
 
@@ -222,7 +224,7 @@
       var process = await runFormatter(['--indent', 'notanum']);
       await process.shouldExit(64);
 
-      process = await runFormatter(['--preserve', '-4']);
+      process = await runFormatter(['--indent', '-4']);
       await process.shouldExit(64);
     });
   });
@@ -326,7 +328,7 @@
       await process.stdin.close();
 
       // No trailing newline at the end.
-      expect(await process.stdout.next, formattedSource.trimRight());
+      expect(await process.stdout.next, formattedOutput);
       await process.shouldExit(0);
     });
 
diff --git a/test/io_test.dart b/test/io_test.dart
index da5839c..7f4ce8b 100644
--- a/test/io_test.dart
+++ b/test/io_test.dart
@@ -12,7 +12,7 @@
 import 'package:test_descriptor/test_descriptor.dart' as d;
 import 'package:test/test.dart';
 
-import 'package:dart_style/src/formatter_options.dart';
+import 'package:dart_style/src/cli/formatter_options.dart';
 
 import 'utils.dart';
 
diff --git a/test/utils.dart b/test/utils.dart
index 946a965..4f99ccf 100644
--- a/test/utils.dart
+++ b/test/utils.dart
@@ -18,6 +18,10 @@
 const unformattedSource = 'void  main()  =>  print("hello") ;';
 const formattedSource = 'void main() => print("hello");\n';
 
+/// The same as formatted source but without a trailing newline because
+/// [TestProcess] filters those when it strips command line output into lines.
+const formattedOutput = 'void main() => print("hello");';
+
 final _indentPattern = RegExp(r'\(indent (\d+)\)');
 final _fixPattern = RegExp(r'\(fix ([a-x-]+)\)');
 
diff --git a/tool/grind.dart b/tool/grind.dart
index b6b6ee4..fc2f019 100644
--- a/tool/grind.dart
+++ b/tool/grind.dart
@@ -114,11 +114,12 @@
   pubspec = pubspec.replaceAll(_versionPattern, 'version: $bumped');
   pubspecFile.writeAsStringSync(pubspec);
 
-  // Update the version constant in bin/format.dart.
-  var binFormatFile = getFile('bin/format.dart');
-  var binFormat = binFormatFile.readAsStringSync().replaceAll(
-      RegExp(r'const version = "[^"]+";'), 'const version = "$bumped";');
-  binFormatFile.writeAsStringSync(binFormat);
+  // Update the version constant in formatter_options.dart.
+  var versionFile = getFile('lib/src/cli/formatter_options.dart');
+  var versionSource = versionFile.readAsStringSync().replaceAll(
+      RegExp(r'const dartStyleVersion = "[^"]+";'),
+      'const dartStyleVersion = "$bumped";');
+  versionFile.writeAsStringSync(versionSource);
 
   // Update the version in the CHANGELOG.
   var changelogFile = getFile('CHANGELOG.md');