Separate out reusable stats code
diff --git a/pkgs/markdown/tool/stats.dart b/pkgs/markdown/tool/stats.dart index d488dc5..8572c8d 100644 --- a/pkgs/markdown/tool/stats.dart +++ b/pkgs/markdown/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/pkgs/markdown/tool/stats_lib.dart b/pkgs/markdown/tool/stats_lib.dart new file mode 100644 index 0000000..8d06e79 --- /dev/null +++ b/pkgs/markdown/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; +}