Merge pull request dart-lang/markdown#106 from dart-lang/better_tools

Better tools
diff --git a/pkgs/markdown/lib/src/block_parser.dart b/pkgs/markdown/lib/src/block_parser.dart
index 6e1cf9f..bc53d03 100644
--- a/pkgs/markdown/lib/src/block_parser.dart
+++ b/pkgs/markdown/lib/src/block_parser.dart
@@ -598,7 +598,7 @@
     }
 
     endItem();
-    var itemNodes = <Node>[];
+    var itemNodes = <Element>[];
 
     var anyEmptyLines = removeTrailingEmptyLines(items);
     var anyEmptyLinesBetweenBlocks = false;
diff --git a/pkgs/markdown/tool/common_mark_stats.dart b/pkgs/markdown/tool/common_mark_stats.dart
index 2bcb4c3..9d54293 100644
--- a/pkgs/markdown/tool/common_mark_stats.dart
+++ b/pkgs/markdown/tool/common_mark_stats.dart
@@ -1,11 +1,10 @@
-library markdown.tool.common_mark_stats;
-
+import 'dart:async';
 import 'dart:collection';
 import 'dart:convert';
 import 'dart:io';
 import 'dart:mirrors';
 
-import 'package:args/args.dart' show ArgParser;
+import 'package:args/args.dart';
 import 'package:collection/collection.dart';
 import 'package:html/dom.dart' as dom;
 import 'package:html/parser.dart' show parseFragment;
@@ -16,23 +15,52 @@
 
 // Locate the "test" directory. Use mirrors so that this works with the test
 // package, which loads this suite into an isolate.
-String get _currentDir => p.dirname(currentMirrorSystem()
-    .findLibrary(#markdown.tool.common_mark_stats)
-    .uri
-    .path);
+String get _currentDir => p
+    .dirname((reflect(main) as ClosureMirror).function.location.sourceUri.path);
 
-void main(List<String> args) {
+main(List<String> args) async {
   final parser = new ArgParser()
-    ..addOption('section', help: 'Restrict tests to one section')
-    ..addFlag('raw', defaultsTo: false, help: 'raw JSON format')
-    ..addFlag('verbose', defaultsTo: false, help: 'verbose output');
-  var options = parser.parse(args);
+    ..addOption('section',
+        help: 'Restrict tests to one section, provided after the option.')
+    ..addFlag('raw',
+        defaultsTo: false, help: 'raw JSON format', negatable: false)
+    ..addFlag('update-files',
+        defaultsTo: false,
+        help: 'Update stats files in $_currentDir',
+        negatable: false)
+    ..addFlag('verbose',
+        defaultsTo: false, help: 'verbose output', negatable: false)
+    ..addFlag('help', defaultsTo: false, negatable: false);
 
-  var specifiedSection = options['section'];
-  var raw = options['raw'];
-  var verbose = options['verbose'];
+  ArgResults options;
 
-  var sections = loadCommonMarkSections();
+  try {
+    options = parser.parse(args);
+  } on FormatException catch (e) {
+    stderr.writeln(e);
+    print(parser.usage);
+    exitCode = 64;
+    return;
+  }
+
+  if (options['help']) {
+    print(parser.usage);
+    return;
+  }
+
+  var specifiedSection = options['section'] as String;
+  var raw = options['raw'] as bool;
+  var verbose = options['verbose'] as bool;
+  var updateFiles = options['update-files'] as bool;
+
+  if (updateFiles && (raw || verbose || (specifiedSection != null))) {
+    stderr.writeln('The `update-files` flag must be used by itself');
+    print(parser.usage);
+    exitCode = 64; // unix standard improper usage
+    return;
+  }
+
+  var sections = _loadCommonMarkSections();
 
   var scores = new SplayTreeMap<String, SplayTreeMap<int, bool>>(
       compareAsciiLowerCaseNatural);
@@ -71,17 +99,12 @@
     }
   });
 
-  if (raw) {
-    var encoder = const JsonEncoder.withIndent(' ', _convert);
-    try {
-      print(encoder.convert(scores));
-    } on JsonUnsupportedObjectError catch (e) {
-      print(e.cause);
-      print(e.unsupportedObject.runtimeType);
-      rethrow;
-    }
-  } else {
-    _printFriendly(scores);
+  if (raw || updateFiles) {
+    _printRaw(scores, updateFiles);
+  }
+
+  if (!raw || updateFiles) {
+    _printFriendly(scores, updateFiles);
   }
 }
 
@@ -97,12 +120,47 @@
   return obj;
 }
 
-void _printFriendly(SplayTreeMap<String, SplayTreeMap<int, bool>> scores) {
+Future _printRaw(scores, bool updateFiles) async {
+  IOSink sink;
+  if (updateFiles) {
+    var path = p.join(_currentDir, 'common_mark_stats.json');
+    print('Updating $path');
+    var file = new File(path);
+    sink = file.openWrite();
+  } else {
+    sink = stdout;
+  }
+
+  var encoder = const JsonEncoder.withIndent(' ', _convert);
+  try {
+    sink.writeln(encoder.convert(scores));
+  } on JsonUnsupportedObjectError catch (e) {
+    stderr.writeln(e.cause);
+    stderr.writeln(e.unsupportedObject.runtimeType);
+    rethrow;
+  }
+
+  await sink.flush();
+  await sink.close();
+}
+
+Future _printFriendly(SplayTreeMap<String, SplayTreeMap<int, bool>> scores,
+    bool updateFiles) async {
   const countWidth = 4;
 
   var totalValid = 0;
   var totalExamples = 0;
 
+  IOSink sink;
+  if (updateFiles) {
+    var path = p.join(_currentDir, 'common_mark_stats.txt');
+    print('Updating $path');
+    var file = new File(path);
+    sink = file.openWrite();
+  } else {
+    sink = stdout;
+  }
+
   scores.forEach((section, map) {
     var total = map.values.length;
     totalExamples += total;
@@ -113,16 +171,19 @@
 
     var pct = (100 * sectionValidCount / total).toStringAsFixed(1).padLeft(5);
 
-    print('${sectionValidCount.toString().padLeft(countWidth)} '
+    sink.writeln('${sectionValidCount.toString().padLeft(countWidth)} '
         'of ${total.toString().padLeft(countWidth)} '
         '– ${pct}%  $section');
   });
 
   var pct = (100 * totalValid / totalExamples).toStringAsFixed(1).padLeft(5);
 
-  print('${totalValid.toString().padLeft(countWidth)} '
+  sink.writeln('${totalValid.toString().padLeft(countWidth)} '
       'of ${totalExamples.toString().padLeft(countWidth)} '
       '– ${pct}%  TOTAL');
+
+  await sink.flush();
+  await sink.close();
 }
 
 /// Compare two DOM trees for equality.
@@ -178,7 +239,7 @@
   return true;
 }
 
-Map<String, List<CommonMarkTestCase>> loadCommonMarkSections() {
+Map<String, List<CommonMarkTestCase>> _loadCommonMarkSections() {
   var testFile = new File(p.join(_currentDir, _commonMarkTests));
   var testsJson = testFile.readAsStringSync();
 
diff --git a/pkgs/markdown/tool/common_mark_stats.json b/pkgs/markdown/tool/common_mark_stats.json
index 7f783f3..c07aa12 100644
--- a/pkgs/markdown/tool/common_mark_stats.json
+++ b/pkgs/markdown/tool/common_mark_stats.json
@@ -64,12 +64,12 @@
   "189": true,
   "190": true,
   "191": true,
-  "192": false,
-  "193": false,
-  "194": false,
+  "192": true,
+  "193": true,
+  "194": true,
   "195": true,
   "196": true,
-  "197": true,
+  "197": false,
   "198": false,
   "199": true,
   "200": true,
@@ -79,11 +79,11 @@
   "204": true,
   "205": true,
   "206": true,
-  "207": false,
+  "207": true,
   "208": true,
-  "209": true,
-  "210": false,
-  "211": false,
+  "209": false,
+  "210": true,
+  "211": true,
   "212": true
  },
  "Code spans": {
@@ -517,7 +517,7 @@
   "239": false,
   "240": false,
   "241": false,
-  "242": true,
+  "242": false,
   "243": false,
   "244": false,
   "245": true,
@@ -525,9 +525,9 @@
   "247": true,
   "248": true,
   "249": true,
-  "250": false,
-  "251": false,
-  "252": false,
+  "250": true,
+  "251": true,
+  "252": true,
   "253": false,
   "254": true,
   "255": false,
@@ -548,10 +548,10 @@
   "268": false,
   "269": false,
   "270": false,
-  "271": false,
+  "271": true,
   "272": false,
   "273": false,
-  "274": false,
+  "274": true,
   "275": false,
   "276": false,
   "277": false,
@@ -610,16 +610,16 @@
   "57": true,
   "58": true,
   "59": true,
-  "60": false,
+  "60": true,
   "61": false,
-  "62": false,
+  "62": true,
   "63": false,
   "64": true,
   "65": true,
-  "66": false,
-  "67": false,
+  "66": true,
+  "67": true,
   "68": false,
-  "69": false,
+  "69": true,
   "70": true,
   "71": true,
   "72": true,
@@ -647,7 +647,7 @@
   "616": true
  },
  "Thematic breaks": {
-  "11": false,
+  "11": true,
   "12": true,
   "13": true,
   "14": true,