Separate out reusable stats code
diff --git a/tool/stats.dart b/tool/stats.dart
index d488dc5..8572c8d 100644
--- a/tool/stats.dart
+++ b/tool/stats.dart
@@ -2,19 +2,12 @@
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
-import 'dart:mirrors';
import 'package:args/args.dart';
import 'package:collection/collection.dart';
-import 'package:html/dom.dart';
-import 'package:html/parser.dart' show parseFragment;
-import 'package:markdown/markdown.dart' show markdownToHtml, ExtensionSet;
import 'package:path/path.dart' as p;
-// Locate the "tool" directory. Use mirrors so that this works with the test
-// package, which loads this suite into an isolate.
-String get _currentDir => p
- .dirname((reflect(main) as ClosureMirror).function.location.sourceUri.path);
+import 'stats_lib.dart';
Future main(List<String> args) async {
final parser = new ArgParser()
@@ -24,7 +17,7 @@
defaultsTo: false, help: 'raw JSON format', negatable: false)
..addFlag('update-files',
defaultsTo: false,
- help: 'Update stats files in $_currentDir',
+ help: 'Update stats files in $toolDir',
negatable: false)
..addFlag('verbose',
defaultsTo: false,
@@ -35,7 +28,8 @@
help: 'Print details for "loose" matches.',
negatable: false)
..addOption('flavor',
- allowed: ['common_mark', 'gfm'], defaultsTo: 'common_mark')
+ allowed: [Config.commonMarkConfig.prefix, Config.gfmConfig.prefix],
+ defaultsTo: Config.commonMarkConfig.prefix)
..addFlag('help', defaultsTo: false, negatable: false);
ArgResults options;
@@ -69,14 +63,19 @@
final testPrefix = options['flavor'] as String;
- var baseUrl = 'http://spec.commonmark.org/0.28/';
- ExtensionSet extensionSet;
- if (testPrefix == 'gfm') {
- extensionSet = ExtensionSet.gitHub;
- baseUrl = 'https://github.github.com/gfm/';
+ Config config;
+ switch (testPrefix) {
+ case 'gfm':
+ config = Config.gfmConfig;
+ break;
+ case 'common_mark':
+ config = Config.commonMarkConfig;
+ break;
+ default:
+ throw new ArgumentError('Does not support `$testPrefix`.');
}
- var sections = _loadCommonMarkSections(testPrefix);
+ var sections = loadCommonMarkSections(testPrefix);
var scores = new SplayTreeMap<String, SplayTreeMap<int, CompareLevel>>(
compareAsciiLowerCaseNatural);
@@ -89,9 +88,8 @@
var nestedMap = scores.putIfAbsent(
section, () => new SplayTreeMap<int, CompareLevel>());
- nestedMap[e.example] = _compareResult(
- baseUrl, e, verbose, verboseLooseMatch,
- extensionSet: extensionSet);
+ nestedMap[e.example] = compareResult(config, e,
+ verboseFail: verbose, verboseLooseMatch: verboseLooseMatch);
}
});
@@ -104,59 +102,6 @@
}
}
-CompareLevel _compareResult(String baseUrl, CommonMarkTestCase expected,
- bool verboseFail, bool verboseLooseMatch,
- {ExtensionSet extensionSet}) {
- String output;
- try {
- output = markdownToHtml(expected.markdown, extensionSet: extensionSet);
- } catch (err, stackTrace) {
- if (verboseFail) {
- printVerboseFailure(baseUrl, 'ERROR', expected, expected.html,
- 'Thrown: $err\n$stackTrace');
- }
-
- return CompareLevel.error;
- }
-
- if (expected.html == output) {
- return CompareLevel.strict;
- }
-
- var expectedParsed = parseFragment(expected.html);
- var actual = parseFragment(output);
-
- var looseMatch = _compareHtml(expectedParsed.children, actual.children);
-
- if (!looseMatch && verboseFail) {
- printVerboseFailure(
- baseUrl, 'FAIL', expected, expectedParsed.outerHtml, actual.outerHtml);
- }
-
- if (looseMatch && verboseLooseMatch) {
- printVerboseFailure(baseUrl, 'LOOSE', expected, output, actual.outerHtml);
- }
-
- return looseMatch ? CompareLevel.loose : CompareLevel.fail;
-}
-
-String indent(String s) => s.splitMapJoin('\n', onNonMatch: (n) => ' $n');
-
-void printVerboseFailure(String baseUrl, String message,
- CommonMarkTestCase test, String expected, String actual) {
- print('$message: $baseUrl#example-${test.example} '
- '@ ${test.section}');
- print('input:');
- print(indent(test.markdown));
- print('expected:');
- print(indent(expected));
- print('actual:');
- print(indent(actual));
- print('-----------------------');
-}
-
-enum CompareLevel { strict, loose, fail, error }
-
Object _convert(obj) {
if (obj is CompareLevel) {
switch (obj) {
@@ -186,9 +131,8 @@
Future _printRaw(String testPrefix, Map scores, bool updateFiles) async {
IOSink sink;
if (updateFiles) {
- var path = p.join(_currentDir, '${testPrefix}_stats.json');
- print('Updating $path');
- var file = new File(path);
+ var file = getStatsFile(testPrefix);
+ print('Updating ${file.path}');
sink = file.openWrite();
} else {
sink = stdout;
@@ -218,7 +162,7 @@
IOSink sink;
if (updateFiles) {
- var path = p.join(_currentDir, '${testPrefix}_stats.txt');
+ var path = p.join(toolDir, '${testPrefix}_stats.txt');
print('Updating $path');
var file = new File(path);
sink = file.openWrite();
@@ -256,99 +200,3 @@
await sink.flush();
await sink.close();
}
-
-/// Compare two DOM trees for equality.
-bool _compareHtml(
- List<Element> expectedElements, List<Element> actualElements) {
- if (expectedElements.length != actualElements.length) {
- return false;
- }
-
- for (var childNum = 0; childNum < expectedElements.length; childNum++) {
- var expected = expectedElements[childNum];
- var actual = actualElements[childNum];
-
- if (expected.runtimeType != actual.runtimeType) {
- return false;
- }
-
- if (expected.localName != actual.localName) {
- return false;
- }
-
- if (expected.attributes.length != actual.attributes.length) {
- return false;
- }
-
- var expectedAttrKeys = expected.attributes.keys.toList();
- expectedAttrKeys.sort();
-
- var actualAttrKeys = actual.attributes.keys.toList();
- actualAttrKeys.sort();
-
- for (var attrNum = 0; attrNum < actualAttrKeys.length; attrNum++) {
- var expectedAttrKey = expectedAttrKeys[attrNum];
- var actualAttrKey = actualAttrKeys[attrNum];
-
- if (expectedAttrKey != actualAttrKey) {
- return false;
- }
-
- if (expected.attributes[expectedAttrKey] !=
- actual.attributes[actualAttrKey]) {
- return false;
- }
- }
-
- var childrenEqual = _compareHtml(expected.children, actual.children);
-
- if (!childrenEqual) {
- return false;
- }
- }
-
- return true;
-}
-
-Map<String, List<CommonMarkTestCase>> _loadCommonMarkSections(
- String testPrefix) {
- var testFile = new File(p.join(_currentDir, '${testPrefix}_tests.json'));
- var testsJson = testFile.readAsStringSync();
-
- var testArray = JSON.decode(testsJson) as List<Map<String, dynamic>>;
-
- var sections = new Map<String, List<CommonMarkTestCase>>();
-
- for (var exampleMap in testArray) {
- var exampleTest = new CommonMarkTestCase.fromJson(exampleMap);
-
- var sectionList =
- sections.putIfAbsent(exampleTest.section, () => <CommonMarkTestCase>[]);
-
- sectionList.add(exampleTest);
- }
-
- return sections;
-}
-
-class CommonMarkTestCase {
- final String markdown;
- final String section;
- final int example;
- final String html;
- final int startLine;
- final int endLine;
-
- CommonMarkTestCase(this.example, this.section, this.startLine, this.endLine,
- this.markdown, this.html);
-
- factory CommonMarkTestCase.fromJson(Map<String, dynamic> json) {
- return new CommonMarkTestCase(
- json['example'] as int,
- json['section'] as String,
- json['start_line'] as int,
- json['end_line'] as int,
- json['markdown'] as String,
- json['html'] as String);
- }
-}
diff --git a/tool/stats_lib.dart b/tool/stats_lib.dart
new file mode 100644
index 0000000..8d06e79
--- /dev/null
+++ b/tool/stats_lib.dart
@@ -0,0 +1,189 @@
+import 'dart:convert';
+import 'dart:io';
+import 'dart:mirrors';
+
+import 'package:path/path.dart' as p;
+
+import 'package:html/dom.dart';
+import 'package:html/parser.dart' show parseFragment;
+import 'package:markdown/markdown.dart' show markdownToHtml, ExtensionSet;
+
+// Locate the "tool" directory. Use mirrors so that this works with the test
+// package, which loads this suite into an isolate.
+String get toolDir =>
+ p.dirname((reflect(loadCommonMarkSections) as ClosureMirror)
+ .function
+ .location
+ .sourceUri
+ .path);
+
+File getStatsFile(String prefix) =>
+ new File(p.join(toolDir, '${prefix}_stats.json'));
+
+Map<String, List<CommonMarkTestCase>> loadCommonMarkSections(
+ String testPrefix) {
+ var testFile = new File(p.join(toolDir, '${testPrefix}_tests.json'));
+ var testsJson = testFile.readAsStringSync();
+
+ var testArray = JSON.decode(testsJson) as List<Map<String, dynamic>>;
+
+ var sections = new Map<String, List<CommonMarkTestCase>>();
+
+ for (var exampleMap in testArray) {
+ var exampleTest = new CommonMarkTestCase.fromJson(exampleMap);
+
+ var sectionList =
+ sections.putIfAbsent(exampleTest.section, () => <CommonMarkTestCase>[]);
+
+ sectionList.add(exampleTest);
+ }
+
+ return sections;
+}
+
+class Config {
+ static final Config commonMarkConfig =
+ new Config._('common_mark', 'http://spec.commonmark.org/0.28/', null);
+ static final Config gfmConfig = new Config._(
+ 'gfm', 'https://github.github.com/gfm/', ExtensionSet.gitHub);
+
+ final String prefix;
+ final String baseUrl;
+ final ExtensionSet extensionSet;
+
+ Config._(this.prefix, this.baseUrl, this.extensionSet);
+}
+
+class CommonMarkTestCase {
+ final String markdown;
+ final String section;
+ final int example;
+ final String html;
+ final int startLine;
+ final int endLine;
+
+ CommonMarkTestCase(this.example, this.section, this.startLine, this.endLine,
+ this.markdown, this.html);
+
+ factory CommonMarkTestCase.fromJson(Map<String, dynamic> json) {
+ return new CommonMarkTestCase(
+ json['example'] as int,
+ json['section'] as String,
+ json['start_line'] as int,
+ json['end_line'] as int,
+ json['markdown'] as String,
+ json['html'] as String);
+ }
+}
+
+enum CompareLevel { strict, loose, fail, error }
+
+CompareLevel compareResult(Config config, CommonMarkTestCase expected,
+ {bool throwOnError: false,
+ bool verboseFail: false,
+ bool verboseLooseMatch: false}) {
+ String output;
+ try {
+ output =
+ markdownToHtml(expected.markdown, extensionSet: config.extensionSet);
+ } catch (err, stackTrace) {
+ if (throwOnError) {
+ rethrow;
+ }
+ if (verboseFail) {
+ _printVerboseFailure(config.baseUrl, 'ERROR', expected, expected.html,
+ 'Thrown: $err\n$stackTrace');
+ }
+
+ return CompareLevel.error;
+ }
+
+ if (expected.html == output) {
+ return CompareLevel.strict;
+ }
+
+ var expectedParsed = parseFragment(expected.html);
+ var actual = parseFragment(output);
+
+ var looseMatch = _compareHtml(expectedParsed.children, actual.children);
+
+ if (!looseMatch && verboseFail) {
+ _printVerboseFailure(config.baseUrl, 'FAIL', expected,
+ expectedParsed.outerHtml, actual.outerHtml);
+ }
+
+ if (looseMatch && verboseLooseMatch) {
+ _printVerboseFailure(
+ config.baseUrl, 'LOOSE', expected, output, actual.outerHtml);
+ }
+
+ return looseMatch ? CompareLevel.loose : CompareLevel.fail;
+}
+
+String _indent(String s) => s.splitMapJoin('\n', onNonMatch: (n) => ' $n');
+
+void _printVerboseFailure(String baseUrl, String message,
+ CommonMarkTestCase test, String expected, String actual) {
+ print('$message: $baseUrl#example-${test.example} '
+ '@ ${test.section}');
+ print('input:');
+ print(_indent(test.markdown));
+ print('expected:');
+ print(_indent(expected));
+ print('actual:');
+ print(_indent(actual));
+ print('-----------------------');
+}
+
+/// Compare two DOM trees for equality.
+bool _compareHtml(
+ List<Element> expectedElements, List<Element> actualElements) {
+ if (expectedElements.length != actualElements.length) {
+ return false;
+ }
+
+ for (var childNum = 0; childNum < expectedElements.length; childNum++) {
+ var expected = expectedElements[childNum];
+ var actual = actualElements[childNum];
+
+ if (expected.runtimeType != actual.runtimeType) {
+ return false;
+ }
+
+ if (expected.localName != actual.localName) {
+ return false;
+ }
+
+ if (expected.attributes.length != actual.attributes.length) {
+ return false;
+ }
+
+ var expectedAttrKeys = expected.attributes.keys.toList();
+ expectedAttrKeys.sort();
+
+ var actualAttrKeys = actual.attributes.keys.toList();
+ actualAttrKeys.sort();
+
+ for (var attrNum = 0; attrNum < actualAttrKeys.length; attrNum++) {
+ var expectedAttrKey = expectedAttrKeys[attrNum];
+ var actualAttrKey = actualAttrKeys[attrNum];
+
+ if (expectedAttrKey != actualAttrKey) {
+ return false;
+ }
+
+ if (expected.attributes[expectedAttrKey] !=
+ actual.attributes[actualAttrKey]) {
+ return false;
+ }
+ }
+
+ var childrenEqual = _compareHtml(expected.children, actual.children);
+
+ if (!childrenEqual) {
+ return false;
+ }
+ }
+
+ return true;
+}