Merge branch 'optimize-regexps' of https://github.com/lrhn/dart-markdown into regexp
diff --git a/pkgs/markdown/.gitignore b/pkgs/markdown/.gitignore
index aeb4e13..492ac23 100644
--- a/pkgs/markdown/.gitignore
+++ b/pkgs/markdown/.gitignore
@@ -1,5 +1,4 @@
+.idea
+.packages
+.pub
 packages
-pubspec.lock
-.project
-.children
-out
\ No newline at end of file
diff --git a/pkgs/markdown/CHANGELOG.md b/pkgs/markdown/CHANGELOG.md
index ffc8430..fdbb0d6 100644
--- a/pkgs/markdown/CHANGELOG.md
+++ b/pkgs/markdown/CHANGELOG.md
@@ -1,4 +1,20 @@
-## v0.7.1+2
+## 0.8.0
+
+* Switch tests to use [test][] instead of [unittest][].
+* Remove (probably unused) `resolved` field from `LinkSyntax`.
+
+[test]: https://pub.dartlang.org/packages/test
+[unittest]: https://pub.dartlang.org/packages/unittest
+
+## 0.7.2
+
+* Allow resolving links that contain inline syntax (#42).
+
+## 0.7.1+3
+
+* Updated homepage.
+
+## 0.7.1+2
 
 * Formatted code.
 
diff --git a/pkgs/markdown/benchmark/benchmark.dart b/pkgs/markdown/benchmark/benchmark.dart
new file mode 100644
index 0000000..124a4f7
--- /dev/null
+++ b/pkgs/markdown/benchmark/benchmark.dart
@@ -0,0 +1,73 @@
+// 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.
+
+library markdown.benchmark.benchmark;
+
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+
+import 'package:markdown/markdown.dart';
+
+const numTrials = 100;
+const runsPerTrial = 50;
+
+final source = loadFile("input.md");
+final expected = loadFile("output.html");
+
+void main(List<String> args) {
+  var best = 99999999.0;
+
+  // Run the benchmark several times. This ensures the VM is warmed up and lets
+  // us see how much variance there is.
+  for (var i = 0; i <= numTrials; i++) {
+    var start = new DateTime.now();
+
+    // For a single benchmark, convert the source multiple times.
+    var result;
+    for (var j = 0; j < runsPerTrial; j++) {
+      result = markdownToHtml(source);
+    }
+
+    var elapsed =
+        new DateTime.now().difference(start).inMilliseconds / runsPerTrial;
+
+    // Keep track of the best run so far.
+    if (elapsed >= best) continue;
+    best = elapsed;
+
+    // Sanity check to make sure the output is what we expect and to make sure
+    // the VM doesn't optimize "dead" code away.
+    if (result != expected) {
+      print("Incorrect output:\n$result");
+      exit(1);
+    }
+
+    // Don't print the first run. It's always terrible since the VM hasn't
+    // warmed up yet.
+    if (i == 0) continue;
+    printResult("Run ${padLeft('#$i', 3)}", elapsed);
+  }
+
+  printResult("Best   ", best);
+}
+
+String loadFile(String name) {
+  var path = p.join(p.dirname(p.fromUri(Platform.script)), name);
+  return new File(path).readAsStringSync();
+}
+
+void printResult(String label, double time) {
+  print("$label: ${padLeft(time.toStringAsFixed(2), 4)}ms "
+      "${'=' * ((time * 20).toInt())}");
+}
+
+String padLeft(input, int length) {
+  var result = input.toString();
+  if (result.length < length) {
+    result = " " * (length - result.length) + result;
+  }
+
+  return result;
+}
diff --git a/pkgs/markdown/benchmark/input.md b/pkgs/markdown/benchmark/input.md
new file mode 100644
index 0000000..31f5f57
--- /dev/null
+++ b/pkgs/markdown/benchmark/input.md
@@ -0,0 +1,496 @@
+**TODO: Add more examples to cover all of the syntax.**
+
+This input was taken from the test package's README to get a representative
+sample of real-world markdown:
+
+## Writing Tests
+
+Tests are specified using the top-level [`test()`][test] function, and test
+assertions are made using [`expect()`][expect]:
+
+[test]: http://www.dartdocs.org/documentation/test/latest/index.html#test/test@id_test
+[expect]: http://www.dartdocs.org/documentation/test/latest/index.html#test/test@id_expect
+
+```dart
+import "package:test/test.dart";
+
+void main() {
+  test("String.split() splits the string on the delimiter", () {
+    var string = "foo,bar,baz";
+    expect(string.split(","), equals(["foo", "bar", "baz"]));
+  });
+
+  test("String.trim() removes surrounding whitespace", () {
+    var string = "  foo ";
+    expect(string.trim(), equals("foo"));
+  });
+}
+```
+
+Tests can be grouped together using the [`group()`] function. Each group's
+description is added to the beginning of its test's descriptions.
+
+```dart
+import "package:test/test.dart";
+
+void main() {
+  group("String", () {
+    test(".split() splits the string on the delimiter", () {
+      var string = "foo,bar,baz";
+      expect(string.split(","), equals(["foo", "bar", "baz"]));
+    });
+
+    test(".trim() removes surrounding whitespace", () {
+      var string = "  foo ";
+      expect(string.trim(), equals("foo"));
+    });
+  });
+
+  group("int", () {
+    test(".remainder() returns the remainder of division", () {
+      expect(11.remainder(3), equals(2));
+    });
+
+    test(".toRadixString() returns a hex string", () {
+      expect(11.toRadixString(16), equals("b"));
+    });
+  });
+}
+```
+
+Any matchers from the [`matcher`][matcher] package can be used with `expect()`
+to do complex validations:
+
+[matcher]: http://www.dartdocs.org/documentation/matcher/latest/index.html#matcher/matcher
+
+```dart
+import "package:test/test.dart";
+
+void main() {
+  test(".split() splits the string on the delimiter", () {
+    expect("foo,bar,baz", allOf([
+      contains("foo"),
+      isNot(startsWith("bar")),
+      endsWith("baz")
+    ]));
+  });
+}
+```
+
+## Running Tests
+
+A single test file can be run just using `pub run test:test path/to/test.dart`
+(on Dart 1.10, this can be shortened to `pub run test path/to/test.dart`).
+
+![Single file being run via pub run"](https://raw.githubusercontent.com/dart-lang/test/master/image/test1.gif)
+
+Many tests can be run at a time using `pub run test:test path/to/dir`.
+
+![Directory being run via "pub run".](https://raw.githubusercontent.com/dart-lang/test/master/image/test2.gif)
+
+It's also possible to run a test on the Dart VM only by invoking it using `dart
+path/to/test.dart`, but this doesn't load the full test runner and will be
+missing some features.
+
+The test runner considers any file that ends with `_test.dart` to be a test
+file. If you don't pass any paths, it will run all the test files in your
+`test/` directory, making it easy to test your entire application at once.
+
+By default, tests are run in the Dart VM, but you can run them in the browser as
+well by passing `pub run test:test -p chrome path/to/test.dart`.
+`test` will take care of starting the browser and loading the tests, and all
+the results will be reported on the command line just like for VM tests. In
+fact, you can even run tests on both platforms with a single command: `pub run
+test:test -p "chrome,vm" path/to/test.dart`.
+
+### Restricting Tests to Certain Platforms
+
+Some test files only make sense to run on particular platforms. They may use
+`dart:html` or `dart:io`, they might test Windows' particular filesystem
+behavior, or they might use a feature that's only available in Chrome. The
+[`@TestOn`][TestOn] annotation makes it easy to declare exactly which platforms
+a test file should run on. Just put it at the top of your file, before any
+`library` or `import` declarations:
+
+```dart
+@TestOn("vm")
+
+import "dart:io";
+
+import "package:test/test.dart";
+
+void main() {
+  // ...
+}
+```
+
+[TestOn]: http://www.dartdocs.org/documentation/test/latest/index.html#test/test.TestOn
+
+The string you pass to `@TestOn` is what's called a "platform selector", and it
+specifies exactly which platforms a test can run on. It can be as simple as the
+name of a platform, or a more complex Dart-like boolean expression involving
+these platform names.
+
+### Platform Selector Syntax
+
+Platform selectors can contain identifiers, parentheses, and operators. When
+loading a test, each identifier is set to `true` or `false` based on the current
+platform, and the test is only loaded if the platform selector returns `true`.
+The operators `||`, `&&`, `!`, and `? :` all work just like they do in Dart. The
+valid identifiers are:
+
+* `vm`: Whether the test is running on the command-line Dart VM.
+
+* `dartium`: Whether the test is running on Dartium.
+
+* `content-shell`: Whether the test is running on the headless Dartium content
+  shell.
+
+* `chrome`: Whether the test is running on Google Chrome.
+
+* `phantomjs`: Whether the test is running on
+  [PhantomJS](http://phantomjs.org/).
+
+* `firefox`: Whether the test is running on Mozilla Firefox.
+
+* `safari`: Whether the test is running on Apple Safari.
+
+* `ie`: Whether the test is running on Microsoft Internet Explorer.
+
+* `dart-vm`: Whether the test is running on the Dart VM in any context,
+  including Dartium. It's identical to `!js`.
+
+* `browser`: Whether the test is running in any browser.
+
+* `js`: Whether the test has been compiled to JS. This is identical to
+  `!dart-vm`.
+
+* `blink`: Whether the test is running in a browser that uses the Blink
+  rendering engine.
+
+* `windows`: Whether the test is running on Windows. If `vm` is false, this will
+  be `false` as well.
+
+* `mac-os`: Whether the test is running on Mac OS. If `vm` is false, this will
+  be `false` as well.
+
+* `linux`: Whether the test is running on Linux. If `vm` is false, this will be
+  `false` as well.
+
+* `android`: Whether the test is running on Android. If `vm` is false, this will
+  be `false` as well, which means that this *won't* be true if the test is
+  running on an Android browser.
+
+* `posix`: Whether the test is running on a POSIX operating system. This is
+  equivalent to `!windows`.
+
+For example, if you wanted to run a test on every browser but Chrome, you would
+write `@TestOn("browser && !chrome")`.
+
+### Running Tests on Dartium
+
+Tests can be run on [Dartium][] by passing the `-p dartium` flag. If you're
+using the Dart Editor, the test runner will be able to find Dartium
+automatically. On Mac OS, you can also [install it using Homebrew][homebrew].
+Otherwise, make sure there's an executable called `dartium` (on Mac OS or Linux)
+or `dartium.exe` (on Windows) on your system path.
+
+[Dartium]: https://www.dartlang.org/tools/dartium/
+[homebrew]: https://github.com/dart-lang/homebrew-dart
+
+Similarly, tests can be run on the headless Dartium content shell by passing `-p
+content-shell`. The content shell is installed along with Dartium when using
+Homebrew. Otherwise, you can downloaded it manually [from this
+page][content_shell]; if you do, make sure the executable named `content_shell`
+(on Mac OS or Linux) or `content_shell.exe` (on Windows) is on your system path.
+
+[content_shell]: http://gsdview.appspot.com/dart-archive/channels/stable/release/latest/dartium/
+
+[In the future][issue 63], there will be a more explicit way to configure the
+location of both the Dartium and content shell executables.
+
+[issue 63]: https://github.com/dart-lang/test/issues/63
+
+## Asynchronous Tests
+
+Tests written with `async`/`await` will work automatically. The test runner
+won't consider the test finished until the returned `Future` completes.
+
+```dart
+import "dart:async";
+
+import "package:test/test.dart";
+
+void main() {
+  test("new Future.value() returns the value", () async {
+    var value = await new Future.value(10);
+    expect(value, equals(10));
+  });
+}
+```
+
+There are also a number of useful functions and matchers for more advanced
+asynchrony. The [`completion()`][completion] matcher can be used to test
+`Futures`; it ensures that the test doesn't finish until the `Future` completes,
+and runs a matcher against that `Future`'s value.
+
+[completion]: http://www.dartdocs.org/documentation/test/latest/index.html#test/test@id_completion
+
+```dart
+import "dart:async";
+
+import "package:test/test.dart";
+
+void main() {
+  test("new Future.value() returns the value", () {
+    expect(new Future.value(10), completion(equals(10)));
+  });
+}
+```
+
+The [`throwsA()`][throwsA] matcher and the various `throwsExceptionType`
+matchers work with both synchronous callbacks and asynchronous `Future`s. They
+ensure that a particular type of exception is thrown:
+
+[throwsA]: http://www.dartdocs.org/documentation/test/latest/index.html#test/test@id_throwsA
+
+```dart
+import "dart:async";
+
+import "package:test/test.dart";
+
+void main() {
+  test("new Future.error() throws the error", () {
+    expect(new Future.error("oh no"), throwsA(equals("oh no")));
+    expect(new Future.error(new StateError("bad state")), throwsStateError);
+  });
+}
+```
+
+The [`expectAsync()`][expectAsync] function wraps another function and has two
+jobs. First, it asserts that the wrapped function is called a certain number of
+times, and will cause the test to fail if it's called too often; second, it
+keeps the test from finishing until the function is called the requisite number
+of times.
+
+```dart
+import "dart:async";
+
+import "package:test/test.dart";
+
+void main() {
+  test("Stream.fromIterable() emits the values in the iterable", () {
+    var stream = new Stream.fromIterable([1, 2, 3]);
+
+    stream.listen(expectAsync((number) {
+      expect(number, inInclusiveRange(1, 3));
+    }, count: 3));
+  });
+}
+```
+
+[expectAsync]: http://www.dartdocs.org/documentation/test/latest/index.html#test/test@id_expectAsync
+
+## Running Tests with Custom HTML
+
+By default, the test runner will generate its own empty HTML file for browser
+tests. However, tests that need custom HTML can create their own files. These
+files have three requirements:
+
+* They must have the same name as the test, with `.dart` replaced by `.html`.
+
+* They must contain a `link` tag with `rel="x-dart-test"` and an `href`
+  attribute pointing to the test script.
+
+* They must contain `<script src="packages/test/dart.js"></script>`.
+
+For example, if you had a test called `custom_html_test.dart`, you might write
+the following HTML file:
+
+```html
+<!doctype html>
+<!-- custom_html_test.html -->
+<html>
+  <head>
+    <title>Custom HTML Test</title>
+    <link rel="x-dart-test" href="custom_html_test.dart">
+    <script src="packages/test/dart.js"></script>
+  </head>
+  <body>
+    // ...
+  </body>
+</html>
+```
+
+## Configuring Tests
+
+### Skipping Tests
+
+If a test, group, or entire suite isn't working yet and you just want it to stop
+complaining, you can mark it as "skipped". The test or tests won't be run, and,
+if you supply a reason why, that reason will be printed. In general, skipping
+tests indicates that they should run but is temporarily not working. If they're
+is fundamentally incompatible with a platform, [`@TestOn`/`testOn`][TestOn]
+should be used instead.
+
+[TestOn]: #restricting-tests-to-certain-platforms
+
+To skip a test suite, put a `@Skip` annotation at the top of the file:
+
+```dart
+@Skip("currently failing (see issue 1234)")
+
+import "package:test/test.dart";
+
+void main() {
+  // ...
+}
+```
+
+The string you pass should describe why the test is skipped. You don't have to
+include it, but it's a good idea to document why the test isn't running.
+
+Groups and individual tests can be skipped by passing the `skip` parameter. This
+can be either `true` or a String describing why the test is skipped. For example:
+
+```dart
+import "package:test/test.dart";
+
+void main() {
+  group("complicated algorithm tests", () {
+    // ...
+  }, skip: "the algorithm isn't quite right");
+
+  test("error-checking test", () {
+    // ...
+  }, skip: "TODO: add error-checking.");
+}
+```
+
+### Timeouts
+
+By default, tests will time out after 30 seconds of inactivity. However, this
+can be configured on a per-test, -group, or -suite basis. To change the timeout
+for a test suite, put a `@Timeout` annotation at the top of the file:
+
+```dart
+@Timeout(const Duration(seconds: 45))
+
+import "package:test/test.dart";
+
+void main() {
+  // ...
+}
+```
+
+In addition to setting an absolute timeout, you can set the timeout relative to
+the default using `@Timeout.factor`. For example, `@Timeout.factor(1.5)` will
+set the timeout to one and a half times as long as the default—45 seconds.
+
+Timeouts can be set for tests and groups using the `timeout` parameter. This
+parameter takes a `Timeout` object just like the annotation. For example:
+
+```dart
+import "package:test/test.dart";
+
+void main() {
+  group("slow tests", () {
+    // ...
+
+    test("even slower test", () {
+      // ...
+    }, timeout: new Timeout.factor(2))
+  }, timeout: new Timeout(new Duration(minutes: 1)));
+}
+```
+
+Nested timeouts apply in order from outermost to innermost. That means that
+"even slower test" will take two minutes to time out, since it multiplies the
+group's timeout by 2.
+
+### Platform-Specific Configuration
+
+Sometimes a test may need to be configured differently for different platforms.
+Windows might run your code slower than other platforms, or your DOM
+manipulation might not work right on Safari yet. For these cases, you can use
+the `@OnPlatform` annotation and the `onPlatform` named parameter to `test()`
+and `group()`. For example:
+
+```dart
+@OnPlatform(const {
+  // Give Windows some extra wiggle-room before timing out.
+  "windows": const Timeout.factor(2)
+})
+
+import "package:test/test.dart";
+
+void main() {
+  test("do a thing", () {
+    // ...
+  }, onPlatform: {
+    "safari": new Skip("Safari is currently broken (see #1234)")
+  });
+}
+```
+
+Both the annotation and the parameter take a map. The map's keys are [platform
+selectors](#platform-selector-syntax) which describe the platforms for which the
+specialized configuration applies. Its values are instances of some of the same
+annotation classes that can be used for a suite: `Skip` and `Timeout`. A value
+can also be a list of these values.
+
+If multiple platforms match, the configuration is applied in order from first to
+last, just as they would in nested groups. This means that for configuration
+like duration-based timeouts, the last matching value wins.
+
+## Testing With `barback`
+
+Packages using the `barback` transformer system may need to test code that's
+created or modified using transformers. The test runner handles this using the
+`--pub-serve` option, which tells it to load the test code from a `pub serve`
+instance rather than from the filesystem.
+
+Before using the `--pub-serve` option, add the `test/pub_serve` transformer to
+your `pubspec.yaml`. This transformer adds the necessary bootstrapping code that
+allows the test runner to load your tests properly:
+
+```yaml
+transformers:
+- test/pub_serve:
+    $include: test/**_test{.*,}.dart
+```
+
+Note that if you're using the test runner along with [`polymer`][polymer], you
+have to make sure that the `test/pub_serve` transformer comes *after* the
+`polymer` transformer:
+
+[polymer]: https://www.dartlang.org/polymer/
+
+```yaml
+transformers:
+- polymer
+- test/pub_serve:
+    $include: test/**_test{.*,}.dart
+```
+
+Then, start up `pub serve`. Make sure to pay attention to which port it's using
+to serve your `test/` directory:
+
+```shell
+$ pub serve
+Loading source assets...
+Loading test/pub_serve transformers...
+Serving my_app web on http://localhost:8080
+Serving my_app test on http://localhost:8081
+Build completed successfully
+```
+
+In this case, the port is `8081`. In another terminal, pass this port to
+`--pub-serve` and otherwise invoke `pub run test:test` as normal:
+
+```shell
+$ pub run test:test --pub-serve=8081 -p chrome
+"pub serve" is compiling test/my_app_test.dart...
+"pub serve" is compiling test/utils_test.dart...
+00:00 +42: All tests passed!
+```
diff --git a/pkgs/markdown/benchmark/output.html b/pkgs/markdown/benchmark/output.html
new file mode 100644
index 0000000..6c49aae
--- /dev/null
+++ b/pkgs/markdown/benchmark/output.html
@@ -0,0 +1,364 @@
+<p><strong>TODO: Add more examples to cover all of the syntax.</strong></p>
+<p>This input was taken from the test package's README to get a representative
+sample of real-world markdown:</p>
+<h2>Writing Tests</h2>
+<p>Tests are specified using the top-level <a href="http://www.dartdocs.org/documentation/test/latest/index.html#test/test@id_test"><code>test()</code></a> function, and test
+assertions are made using <a href="http://www.dartdocs.org/documentation/test/latest/index.html#test/test@id_expect"><code>expect()</code></a>:</p>
+<pre class="dart"><code>import "package:test/test.dart";
+
+void main() {
+  test("String.split() splits the string on the delimiter", () {
+    var string = "foo,bar,baz";
+    expect(string.split(","), equals(["foo", "bar", "baz"]));
+  });
+
+  test("String.trim() removes surrounding whitespace", () {
+    var string = "  foo ";
+    expect(string.trim(), equals("foo"));
+  });
+}
+</code></pre>
+<p>Tests can be grouped together using the [`group()`] function. Each group's
+description is added to the beginning of its test's descriptions.</p>
+<pre class="dart"><code>import "package:test/test.dart";
+
+void main() {
+  group("String", () {
+    test(".split() splits the string on the delimiter", () {
+      var string = "foo,bar,baz";
+      expect(string.split(","), equals(["foo", "bar", "baz"]));
+    });
+
+    test(".trim() removes surrounding whitespace", () {
+      var string = "  foo ";
+      expect(string.trim(), equals("foo"));
+    });
+  });
+
+  group("int", () {
+    test(".remainder() returns the remainder of division", () {
+      expect(11.remainder(3), equals(2));
+    });
+
+    test(".toRadixString() returns a hex string", () {
+      expect(11.toRadixString(16), equals("b"));
+    });
+  });
+}
+</code></pre>
+<p>Any matchers from the <a href="http://www.dartdocs.org/documentation/matcher/latest/index.html#matcher/matcher"><code>matcher</code></a> package can be used with <code>expect()</code>
+to do complex validations:</p>
+<pre class="dart"><code>import "package:test/test.dart";
+
+void main() {
+  test(".split() splits the string on the delimiter", () {
+    expect("foo,bar,baz", allOf([
+      contains("foo"),
+      isNot(startsWith("bar")),
+      endsWith("baz")
+    ]));
+  });
+}
+</code></pre>
+<h2>Running Tests</h2>
+<p>A single test file can be run just using <code>pub run test:test path/to/test.dart</code>
+(on Dart 1.10, this can be shortened to <code>pub run test path/to/test.dart</code>).</p>
+<p><a href="https://raw.githubusercontent.com/dart-lang/test/master/image/test1.gif"><img alt="Single file being run via pub run"" src="https://raw.githubusercontent.com/dart-lang/test/master/image/test1.gif"></img></a></p>
+<p>Many tests can be run at a time using <code>pub run test:test path/to/dir</code>.</p>
+<p><a href="https://raw.githubusercontent.com/dart-lang/test/master/image/test2.gif"><img alt="Directory being run via "pub run"." src="https://raw.githubusercontent.com/dart-lang/test/master/image/test2.gif"></img></a></p>
+<p>It's also possible to run a test on the Dart VM only by invoking it using <code>dart
+path/to/test.dart</code>, but this doesn't load the full test runner and will be
+missing some features.</p>
+<p>The test runner considers any file that ends with <code>_test.dart</code> to be a test
+file. If you don't pass any paths, it will run all the test files in your
+<code>test/</code> directory, making it easy to test your entire application at once.</p>
+<p>By default, tests are run in the Dart VM, but you can run them in the browser as
+well by passing <code>pub run test:test -p chrome path/to/test.dart</code>.
+<code>test</code> will take care of starting the browser and loading the tests, and all
+the results will be reported on the command line just like for VM tests. In
+fact, you can even run tests on both platforms with a single command: <code>pub run
+test:test -p "chrome,vm" path/to/test.dart</code>.</p>
+<h3>Restricting Tests to Certain Platforms</h3>
+<p>Some test files only make sense to run on particular platforms. They may use
+<code>dart:html</code> or <code>dart:io</code>, they might test Windows' particular filesystem
+behavior, or they might use a feature that's only available in Chrome. The
+<a href="#restricting-tests-to-certain-platforms"><code>@TestOn</code></a> annotation makes it easy to declare exactly which platforms
+a test file should run on. Just put it at the top of your file, before any
+<code>library</code> or <code>import</code> declarations:</p>
+<pre class="dart"><code>@TestOn("vm")
+
+import "dart:io";
+
+import "package:test/test.dart";
+
+void main() {
+  // ...
+}
+</code></pre>
+<p>The string you pass to <code>@TestOn</code> is what's called a "platform selector", and it
+specifies exactly which platforms a test can run on. It can be as simple as the
+name of a platform, or a more complex Dart-like boolean expression involving
+these platform names.</p>
+<h3>Platform Selector Syntax</h3>
+<p>Platform selectors can contain identifiers, parentheses, and operators. When
+loading a test, each identifier is set to <code>true</code> or <code>false</code> based on the current
+platform, and the test is only loaded if the platform selector returns <code>true</code>.
+The operators <code>||</code>, <code>&amp;&amp;</code>, <code>!</code>, and <code>? :</code> all work just like they do in Dart. The
+valid identifiers are:</p><ul><li>
+<p><code>vm</code>: Whether the test is running on the command-line Dart VM.</p></li><li>
+<p><code>dartium</code>: Whether the test is running on Dartium.</p></li><li>
+<p><code>content-shell</code>: Whether the test is running on the headless Dartium content
+  shell.</p></li><li>
+<p><code>chrome</code>: Whether the test is running on Google Chrome.</p></li><li>
+<p><code>phantomjs</code>: Whether the test is running on
+  <a href="http://phantomjs.org/">PhantomJS</a>.</p></li><li>
+<p><code>firefox</code>: Whether the test is running on Mozilla Firefox.</p></li><li>
+<p><code>safari</code>: Whether the test is running on Apple Safari.</p></li><li>
+<p><code>ie</code>: Whether the test is running on Microsoft Internet Explorer.</p></li><li>
+<p><code>dart-vm</code>: Whether the test is running on the Dart VM in any context,
+  including Dartium. It's identical to <code>!js</code>.</p></li><li>
+<p><code>browser</code>: Whether the test is running in any browser.</p></li><li>
+<p><code>js</code>: Whether the test has been compiled to JS. This is identical to
+  <code>!dart-vm</code>.</p></li><li>
+<p><code>blink</code>: Whether the test is running in a browser that uses the Blink
+  rendering engine.</p></li><li>
+<p><code>windows</code>: Whether the test is running on Windows. If <code>vm</code> is false, this will
+  be <code>false</code> as well.</p></li><li>
+<p><code>mac-os</code>: Whether the test is running on Mac OS. If <code>vm</code> is false, this will
+  be <code>false</code> as well.</p></li><li>
+<p><code>linux</code>: Whether the test is running on Linux. If <code>vm</code> is false, this will be
+  <code>false</code> as well.</p></li><li>
+<p><code>android</code>: Whether the test is running on Android. If <code>vm</code> is false, this will
+  be <code>false</code> as well, which means that this <em>won't</em> be true if the test is
+  running on an Android browser.</p></li><li>
+<p><code>posix</code>: Whether the test is running on a POSIX operating system. This is
+  equivalent to <code>!windows</code>.</p></li></ul>
+<p>For example, if you wanted to run a test on every browser but Chrome, you would
+write <code>@TestOn("browser &amp;&amp; !chrome")</code>.</p>
+<h3>Running Tests on Dartium</h3>
+<p>Tests can be run on <a href="https://www.dartlang.org/tools/dartium/">Dartium</a> by passing the <code>-p dartium</code> flag. If you're
+using the Dart Editor, the test runner will be able to find Dartium
+automatically. On Mac OS, you can also <a href="https://github.com/dart-lang/homebrew-dart">install it using Homebrew</a>.
+Otherwise, make sure there's an executable called <code>dartium</code> (on Mac OS or Linux)
+or <code>dartium.exe</code> (on Windows) on your system path.</p>
+<p>Similarly, tests can be run on the headless Dartium content shell by passing <code>-p
+content-shell</code>. The content shell is installed along with Dartium when using
+Homebrew. Otherwise, you can downloaded it manually <a href="http://gsdview.appspot.com/dart-archive/channels/stable/release/latest/dartium/">from this
+page</a>; if you do, make sure the executable named <code>content_shell</code>
+(on Mac OS or Linux) or <code>content_shell.exe</code> (on Windows) is on your system path.</p>
+<p><a href="https://github.com/dart-lang/test/issues/63">In the future</a>, there will be a more explicit way to configure the
+location of both the Dartium and content shell executables.</p>
+<h2>Asynchronous Tests</h2>
+<p>Tests written with <code>async</code>/<code>await</code> will work automatically. The test runner
+won't consider the test finished until the returned <code>Future</code> completes.</p>
+<pre class="dart"><code>import "dart:async";
+
+import "package:test/test.dart";
+
+void main() {
+  test("new Future.value() returns the value", () async {
+    var value = await new Future.value(10);
+    expect(value, equals(10));
+  });
+}
+</code></pre>
+<p>There are also a number of useful functions and matchers for more advanced
+asynchrony. The <a href="http://www.dartdocs.org/documentation/test/latest/index.html#test/test@id_completion"><code>completion()</code></a> matcher can be used to test
+<code>Futures</code>; it ensures that the test doesn't finish until the <code>Future</code> completes,
+and runs a matcher against that <code>Future</code>'s value.</p>
+<pre class="dart"><code>import "dart:async";
+
+import "package:test/test.dart";
+
+void main() {
+  test("new Future.value() returns the value", () {
+    expect(new Future.value(10), completion(equals(10)));
+  });
+}
+</code></pre>
+<p>The <a href="http://www.dartdocs.org/documentation/test/latest/index.html#test/test@id_throwsA"><code>throwsA()</code></a> matcher and the various <code>throwsExceptionType</code>
+matchers work with both synchronous callbacks and asynchronous <code>Future</code>s. They
+ensure that a particular type of exception is thrown:</p>
+<pre class="dart"><code>import "dart:async";
+
+import "package:test/test.dart";
+
+void main() {
+  test("new Future.error() throws the error", () {
+    expect(new Future.error("oh no"), throwsA(equals("oh no")));
+    expect(new Future.error(new StateError("bad state")), throwsStateError);
+  });
+}
+</code></pre>
+<p>The <a href="http://www.dartdocs.org/documentation/test/latest/index.html#test/test@id_expectAsync"><code>expectAsync()</code></a> function wraps another function and has two
+jobs. First, it asserts that the wrapped function is called a certain number of
+times, and will cause the test to fail if it's called too often; second, it
+keeps the test from finishing until the function is called the requisite number
+of times.</p>
+<pre class="dart"><code>import "dart:async";
+
+import "package:test/test.dart";
+
+void main() {
+  test("Stream.fromIterable() emits the values in the iterable", () {
+    var stream = new Stream.fromIterable([1, 2, 3]);
+
+    stream.listen(expectAsync((number) {
+      expect(number, inInclusiveRange(1, 3));
+    }, count: 3));
+  });
+}
+</code></pre>
+<h2>Running Tests with Custom HTML</h2>
+<p>By default, the test runner will generate its own empty HTML file for browser
+tests. However, tests that need custom HTML can create their own files. These
+files have three requirements:</p><ul><li>
+<p>They must have the same name as the test, with <code>.dart</code> replaced by <code>.html</code>.</p></li><li>
+<p>They must contain a <code>link</code> tag with <code>rel="x-dart-test"</code> and an <code>href</code>
+  attribute pointing to the test script.</p></li><li>
+<p>They must contain <code>&lt;script src="packages/test/dart.js"&gt;&lt;/script&gt;</code>.</p></li></ul>
+<p>For example, if you had a test called <code>custom_html_test.dart</code>, you might write
+the following HTML file:</p>
+<pre class="html"><code>&lt;!doctype html&gt;
+&lt;!-- custom_html_test.html --&gt;
+&lt;html&gt;
+  &lt;head&gt;
+    &lt;title&gt;Custom HTML Test&lt;/title&gt;
+    &lt;link rel="x-dart-test" href="custom_html_test.dart"&gt;
+    &lt;script src="packages/test/dart.js"&gt;&lt;/script&gt;
+  &lt;/head&gt;
+  &lt;body&gt;
+    // ...
+  &lt;/body&gt;
+&lt;/html&gt;
+</code></pre>
+<h2>Configuring Tests</h2>
+<h3>Skipping Tests</h3>
+<p>If a test, group, or entire suite isn't working yet and you just want it to stop
+complaining, you can mark it as "skipped". The test or tests won't be run, and,
+if you supply a reason why, that reason will be printed. In general, skipping
+tests indicates that they should run but is temporarily not working. If they're
+is fundamentally incompatible with a platform, <a href="#restricting-tests-to-certain-platforms"><code>@TestOn</code>/<code>testOn</code></a>
+should be used instead.</p>
+<p>To skip a test suite, put a <code>@Skip</code> annotation at the top of the file:</p>
+<pre class="dart"><code>@Skip("currently failing (see issue 1234)")
+
+import "package:test/test.dart";
+
+void main() {
+  // ...
+}
+</code></pre>
+<p>The string you pass should describe why the test is skipped. You don't have to
+include it, but it's a good idea to document why the test isn't running.</p>
+<p>Groups and individual tests can be skipped by passing the <code>skip</code> parameter. This
+can be either <code>true</code> or a String describing why the test is skipped. For example:</p>
+<pre class="dart"><code>import "package:test/test.dart";
+
+void main() {
+  group("complicated algorithm tests", () {
+    // ...
+  }, skip: "the algorithm isn't quite right");
+
+  test("error-checking test", () {
+    // ...
+  }, skip: "TODO: add error-checking.");
+}
+</code></pre>
+<h3>Timeouts</h3>
+<p>By default, tests will time out after 30 seconds of inactivity. However, this
+can be configured on a per-test, -group, or -suite basis. To change the timeout
+for a test suite, put a <code>@Timeout</code> annotation at the top of the file:</p>
+<pre class="dart"><code>@Timeout(const Duration(seconds: 45))
+
+import "package:test/test.dart";
+
+void main() {
+  // ...
+}
+</code></pre>
+<p>In addition to setting an absolute timeout, you can set the timeout relative to
+the default using <code>@Timeout.factor</code>. For example, <code>@Timeout.factor(1.5)</code> will
+set the timeout to one and a half times as long as the default—45 seconds.</p>
+<p>Timeouts can be set for tests and groups using the <code>timeout</code> parameter. This
+parameter takes a <code>Timeout</code> object just like the annotation. For example:</p>
+<pre class="dart"><code>import "package:test/test.dart";
+
+void main() {
+  group("slow tests", () {
+    // ...
+
+    test("even slower test", () {
+      // ...
+    }, timeout: new Timeout.factor(2))
+  }, timeout: new Timeout(new Duration(minutes: 1)));
+}
+</code></pre>
+<p>Nested timeouts apply in order from outermost to innermost. That means that
+"even slower test" will take two minutes to time out, since it multiplies the
+group's timeout by 2.</p>
+<h3>Platform-Specific Configuration</h3>
+<p>Sometimes a test may need to be configured differently for different platforms.
+Windows might run your code slower than other platforms, or your DOM
+manipulation might not work right on Safari yet. For these cases, you can use
+the <code>@OnPlatform</code> annotation and the <code>onPlatform</code> named parameter to <code>test()</code>
+and <code>group()</code>. For example:</p>
+<pre class="dart"><code>@OnPlatform(const {
+  // Give Windows some extra wiggle-room before timing out.
+  "windows": const Timeout.factor(2)
+})
+
+import "package:test/test.dart";
+
+void main() {
+  test("do a thing", () {
+    // ...
+  }, onPlatform: {
+    "safari": new Skip("Safari is currently broken (see #1234)")
+  });
+}
+</code></pre>
+<p>Both the annotation and the parameter take a map. The map's keys are <a href="#platform-selector-syntax">platform
+selectors</a> which describe the platforms for which the
+specialized configuration applies. Its values are instances of some of the same
+annotation classes that can be used for a suite: <code>Skip</code> and <code>Timeout</code>. A value
+can also be a list of these values.</p>
+<p>If multiple platforms match, the configuration is applied in order from first to
+last, just as they would in nested groups. This means that for configuration
+like duration-based timeouts, the last matching value wins.</p>
+<h2>Testing With <code>barback</code></h2>
+<p>Packages using the <code>barback</code> transformer system may need to test code that's
+created or modified using transformers. The test runner handles this using the
+<code>--pub-serve</code> option, which tells it to load the test code from a <code>pub serve</code>
+instance rather than from the filesystem.</p>
+<p>Before using the <code>--pub-serve</code> option, add the <code>test/pub_serve</code> transformer to
+your <code>pubspec.yaml</code>. This transformer adds the necessary bootstrapping code that
+allows the test runner to load your tests properly:</p>
+<pre class="yaml"><code>transformers:
+- test/pub_serve:
+    $include: test/**_test{.*,}.dart
+</code></pre>
+<p>Note that if you're using the test runner along with <a href="https://www.dartlang.org/polymer/"><code>polymer</code></a>, you
+have to make sure that the <code>test/pub_serve</code> transformer comes <em>after</em> the
+<code>polymer</code> transformer:</p>
+<pre class="yaml"><code>transformers:
+- polymer
+- test/pub_serve:
+    $include: test/**_test{.*,}.dart
+</code></pre>
+<p>Then, start up <code>pub serve</code>. Make sure to pay attention to which port it's using
+to serve your <code>test/</code> directory:</p>
+<pre class="shell"><code>$ pub serve
+Loading source assets...
+Loading test/pub_serve transformers...
+Serving my_app web on http://localhost:8080
+Serving my_app test on http://localhost:8081
+Build completed successfully
+</code></pre>
+<p>In this case, the port is <code>8081</code>. In another terminal, pass this port to
+<code>--pub-serve</code> and otherwise invoke <code>pub run test:test</code> as normal:</p>
+<pre class="shell"><code>$ pub run test:test --pub-serve=8081 -p chrome
+"pub serve" is compiling test/my_app_test.dart...
+"pub serve" is compiling test/utils_test.dart...
+00:00 +42: All tests passed!
+</code></pre>
\ No newline at end of file
diff --git a/pkgs/markdown/lib/src/ast.dart b/pkgs/markdown/lib/src/ast.dart
index 3290efb..6da7ab4 100644
--- a/pkgs/markdown/lib/src/ast.dart
+++ b/pkgs/markdown/lib/src/ast.dart
@@ -2,7 +2,7 @@
 // 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.
 
-library markdown.ast;
+library markdown.src.ast;
 
 typedef Node Resolver(String name);
 
@@ -22,21 +22,21 @@
 
   Element.empty(this.tag)
       : children = null,
-        attributes = <String, String>{};
+        attributes = {};
 
   Element.withTag(this.tag)
       : children = [],
-        attributes = <String, String>{};
+        attributes = {};
 
   Element.text(this.tag, String text)
       : children = [new Text(text)],
-        attributes = <String, String>{};
+        attributes = {};
 
   bool get isEmpty => children == null;
 
   void accept(NodeVisitor visitor) {
     if (visitor.visitElementBefore(this)) {
-      for (final child in children) child.accept(visitor);
+      for (var child in children) child.accept(visitor);
       visitor.visitElementAfter(this);
     }
   }
diff --git a/pkgs/markdown/lib/src/block_parser.dart b/pkgs/markdown/lib/src/block_parser.dart
index 6a390dd..5bdc111 100644
--- a/pkgs/markdown/lib/src/block_parser.dart
+++ b/pkgs/markdown/lib/src/block_parser.dart
@@ -9,43 +9,43 @@
 import 'util.dart';
 
 /// The line contains only whitespace or is empty.
-final _RE_EMPTY = new RegExp(r'^(?:[ \t]*)$');
+final _emptyPattern = new RegExp(r'^(?:[ \t]*)$');
 
 /// A series of `=` or `-` (on the next line) define setext-style headers.
-final _RE_SETEXT = new RegExp(r'^(=+|-+)$');
+final _setextPattern = new RegExp(r'^(=+|-+)$');
 
 /// Leading (and trailing) `#` define atx-style headers.
 ///
-/// Stats with 1-6 unescaped `#` characters which must not be followed by a
+/// Starts with 1-6 unescaped `#` characters which must not be followed by a
 /// non-space character. Line may end with any number of `#` characters,.
-final _RE_HEADER = new RegExp(r'^(#{1,6})[ \x09\x0b\x0c](.*?)#*$');
+final _headerPattern = new RegExp(r'^(#{1,6})[ \x09\x0b\x0c](.*?)#*$');
 
 /// The line starts with `>` with one optional space after.
-final _RE_BLOCKQUOTE = new RegExp(r'^[ ]{0,3}>[ ]?(.*)$');
+final _blockquotePattern = new RegExp(r'^[ ]{0,3}>[ ]?(.*)$');
 
 /// A line indented four spaces. Used for code blocks and lists.
-final _RE_INDENT = new RegExp(r'^(?:    |\t)(.*)$');
+final _indentPattern = new RegExp(r'^(?:    |\t)(.*)$');
 
 /// Fenced code block.
-final _RE_CODE = new RegExp(r'^[ ]{0,3}(`{3,}|~{3,})(.*)$');
+final _codePattern = new RegExp(r'^[ ]{0,3}(`{3,}|~{3,})(.*)$');
 
 /// Three or more hyphens, asterisks or underscores by themselves. Note that
 /// a line like `----` is valid as both HR and SETEXT. In case of a tie,
 /// SETEXT should win.
-final _RE_HR = new RegExp(r'^ {0,3}([-*_]) *\1 *\1(?:\1| )*$');
+final _hrPattern = new RegExp(r'^ {0,3}([-*_]) *\1 *\1(?:\1| )*$');
 
 /// Really hacky way to detect block-level embedded HTML. Just looks for
 /// "<somename".
-final _RE_HTML = new RegExp(r'^<[ ]*\w+[ >]');
+final _htmlPattern = new RegExp(r'^<[ ]*\w+[ >]');
 
 /// A line starting with one of these markers: `-`, `*`, `+`. May have up to
 /// three leading spaces before the marker and any number of spaces or tabs
 /// after.
-final _RE_UL = new RegExp(r'^[ ]{0,3}[*+-][ \t]+(.*)$');
+final _ulPattern = new RegExp(r'^[ ]{0,3}[*+-][ \t]+(.*)$');
 
 /// A line starting with a number like `123.`. May have up to three leading
 /// spaces before the marker and any number of spaces or tabs after.
-final _RE_OL = new RegExp(r'^[ ]{0,3}\d+\.[ \t]+(.*)$');
+final _olPattern = new RegExp(r'^[ ]{0,3}\d+\.[ \t]+(.*)$');
 
 /// Maintains the internal state needed to parse a series of lines into blocks
 /// of markdown suitable for further inline parsing.
@@ -121,10 +121,10 @@
 
   List<String> parseChildLines(BlockParser parser) {
     // Grab all of the lines that form the blockquote, stripping off the ">".
-    final childLines = <String>[];
+    var childLines = <String>[];
 
     while (!parser.isDone) {
-      final match = pattern.firstMatch(parser.current);
+      var match = pattern.firstMatch(parser.current);
       if (match == null) break;
       childLines.add(match[1]);
       parser.advance();
@@ -141,7 +141,7 @@
 }
 
 class EmptyBlockSyntax extends BlockSyntax {
-  RegExp get pattern => _RE_EMPTY;
+  RegExp get pattern => _emptyPattern;
 
   const EmptyBlockSyntax();
 
@@ -160,14 +160,14 @@
   bool canParse(BlockParser parser) {
     // Note: matches *next* line, not the current one. We're looking for the
     // underlining after this line.
-    return parser.matchesNext(_RE_SETEXT);
+    return parser.matchesNext(_setextPattern);
   }
 
   Node parse(BlockParser parser) {
-    final match = _RE_SETEXT.firstMatch(parser.next);
+    var match = _setextPattern.firstMatch(parser.next);
 
-    final tag = (match[1][0] == '=') ? 'h1' : 'h2';
-    final contents = parser.document.parseInline(parser.current);
+    var tag = (match[1][0] == '=') ? 'h1' : 'h2';
+    var contents = parser.document.parseInline(parser.current);
     parser.advance();
     parser.advance();
 
@@ -177,30 +177,30 @@
 
 /// Parses atx-style headers: `## Header ##`.
 class HeaderSyntax extends BlockSyntax {
-  RegExp get pattern => _RE_HEADER;
+  RegExp get pattern => _headerPattern;
 
   const HeaderSyntax();
 
   Node parse(BlockParser parser) {
-    final match = pattern.firstMatch(parser.current);
+    var match = pattern.firstMatch(parser.current);
     parser.advance();
-    final level = match[1].length;
-    final contents = parser.document.parseInline(match[2].trim());
+    var level = match[1].length;
+    var contents = parser.document.parseInline(match[2].trim());
     return new Element('h$level', contents);
   }
 }
 
 /// Parses email-style blockquotes: `> quote`.
 class BlockquoteSyntax extends BlockSyntax {
-  RegExp get pattern => _RE_BLOCKQUOTE;
+  RegExp get pattern => _blockquotePattern;
 
   const BlockquoteSyntax();
 
   Node parse(BlockParser parser) {
-    final childLines = parseChildLines(parser);
+    var childLines = parseChildLines(parser);
 
     // Recursively parse the contents of the blockquote.
-    final children = parser.document.parseLines(childLines);
+    var children = parser.document.parseLines(childLines);
 
     return new Element('blockquote', children);
   }
@@ -208,12 +208,12 @@
 
 /// Parses preformatted code blocks that are indented four spaces.
 class CodeBlockSyntax extends BlockSyntax {
-  RegExp get pattern => _RE_INDENT;
+  RegExp get pattern => _indentPattern;
 
   const CodeBlockSyntax();
 
   List<String> parseChildLines(BlockParser parser) {
-    final childLines = <String>[];
+    var childLines = <String>[];
 
     while (!parser.isDone) {
       var match = pattern.firstMatch(parser.current);
@@ -239,30 +239,32 @@
   }
 
   Node parse(BlockParser parser) {
-    final childLines = parseChildLines(parser);
+    var childLines = parseChildLines(parser);
 
     // The Markdown tests expect a trailing newline.
     childLines.add('');
 
     // Escape the code.
-    final escaped = escapeHtml(childLines.join('\n'));
+    var escaped = escapeHtml(childLines.join('\n'));
 
     return new Element('pre', [new Element.text('code', escaped)]);
   }
 }
 
 /// Parses preformatted code blocks between two ~~~ or ``` sequences.
-/// [Pandoc's markdown documentation](http://johnmacfarlane.net/pandoc/demo/example9/pandocs-markdown.html).
+///
+/// See [Pandoc's documentation](http://johnmacfarlane.net/pandoc/demo/example9/pandocs-markdown.html).
 class FencedCodeBlockSyntax extends BlockSyntax {
-  RegExp get pattern => _RE_CODE;
+  RegExp get pattern => _codePattern;
 
   const FencedCodeBlockSyntax();
 
   List<String> parseChildLines(BlockParser parser, [String endBlock]) {
     if (endBlock == null) endBlock = '';
 
-    final childLines = <String>[];
+    var childLines = <String>[];
     parser.advance();
+
     while (!parser.isDone) {
       var match = pattern.firstMatch(parser.current);
       if (match == null || !match[1].startsWith(endBlock)) {
@@ -273,6 +275,7 @@
         break;
       }
     }
+
     return childLines;
   }
 
@@ -282,25 +285,24 @@
     var endBlock = match.group(1);
     var syntax = match.group(2);
 
-    final childLines = parseChildLines(parser, endBlock);
+    var childLines = parseChildLines(parser, endBlock);
 
     // The Markdown tests expect a trailing newline.
     childLines.add('');
 
     // Escape the code.
-    final escaped = escapeHtml(childLines.join('\n'));
+    var escaped = escapeHtml(childLines.join('\n'));
 
     var element = new Element('pre', [new Element.text('code', escaped)]);
-    if (syntax != '') {
-      element.attributes['class'] = syntax;
-    }
+    if (syntax != '') element.attributes['class'] = syntax;
+
     return element;
   }
 }
 
 /// Parses horizontal rules like `---`, `_ _ _`, `*  *  *`, etc.
 class HorizontalRuleSyntax extends BlockSyntax {
-  RegExp get pattern => _RE_HR;
+  RegExp get pattern => _hrPattern;
 
   const HorizontalRuleSyntax();
 
@@ -321,17 +323,17 @@
 /// 3.  Absolutely no HTML parsing or validation is done. We're a markdown
 ///     parser not an HTML parser!
 class BlockHtmlSyntax extends BlockSyntax {
-  RegExp get pattern => _RE_HTML;
+  RegExp get pattern => _htmlPattern;
 
   bool get canEndBlock => false;
 
   const BlockHtmlSyntax();
 
   Node parse(BlockParser parser) {
-    final childLines = [];
+    var childLines = <String>[];
 
     // Eat until we hit a blank line.
-    while (!parser.isDone && !parser.matches(_RE_EMPTY)) {
+    while (!parser.isDone && !parser.matches(_emptyPattern)) {
       childLines.add(parser.current);
       parser.advance();
     }
@@ -356,7 +358,7 @@
   const ListSyntax();
 
   Node parse(BlockParser parser) {
-    final items = <ListItem>[];
+    var items = <ListItem>[];
     var childLines = <String>[];
 
     endItem() {
@@ -373,14 +375,14 @@
     }
 
     while (!parser.isDone) {
-      if (tryMatch(_RE_EMPTY)) {
+      if (tryMatch(_emptyPattern)) {
         // Add a blank line to the current list item.
         childLines.add('');
-      } else if (tryMatch(_RE_UL) || tryMatch(_RE_OL)) {
+      } else if (tryMatch(_ulPattern) || tryMatch(_olPattern)) {
         // End the current list item and start a new one.
         endItem();
         childLines.add(match[1]);
-      } else if (tryMatch(_RE_INDENT)) {
+      } else if (tryMatch(_indentPattern)) {
         // Strip off indent and add to current item.
         childLines.add(match[1]);
       } else if (BlockSyntax.isAtBlockEnd(parser)) {
@@ -437,9 +439,9 @@
     // Remove any trailing empty lines and note which items are separated by
     // empty lines. Do this before seeing which items are single-line so that
     // trailing empty lines on the last item don't force it into being a block.
-    for (int i = 0; i < items.length; i++) {
-      for (int j = items[i].lines.length - 1; j > 0; j--) {
-        if (_RE_EMPTY.firstMatch(items[i].lines[j]) != null) {
+    for (var i = 0; i < items.length; i++) {
+      for (var j = items[i].lines.length - 1; j > 0; j--) {
+        if (_emptyPattern.firstMatch(items[i].lines[j]) != null) {
           // Found an empty line. Item and one after it are blocks.
           if (i < items.length - 1) {
             items[i].forceBlock = true;
@@ -453,22 +455,22 @@
     }
 
     // Convert the list items to Nodes.
-    final itemNodes = <Node>[];
-    for (final item in items) {
-      bool blockItem = item.forceBlock || (item.lines.length > 1);
+    var itemNodes = <Node>[];
+    for (var item in items) {
+      var blockItem = item.forceBlock || (item.lines.length > 1);
 
       // See if it matches some block parser.
-      final blocksInList = [
-        _RE_BLOCKQUOTE,
-        _RE_HEADER,
-        _RE_HR,
-        _RE_INDENT,
-        _RE_UL,
-        _RE_OL
+      var blocksInList = [
+        _blockquotePattern,
+        _headerPattern,
+        _hrPattern,
+        _indentPattern,
+        _ulPattern,
+        _olPattern
       ];
 
       if (!blockItem) {
-        for (final pattern in blocksInList) {
+        for (var pattern in blocksInList) {
           if (pattern.firstMatch(item.lines[0]) != null) {
             blockItem = true;
             break;
@@ -479,11 +481,11 @@
       // Parse the item as a block or inline.
       if (blockItem) {
         // Block list item.
-        final children = parser.document.parseLines(item.lines);
+        var children = parser.document.parseLines(item.lines);
         itemNodes.add(new Element('li', children));
       } else {
         // Raw list item.
-        final contents = parser.document.parseInline(item.lines[0]);
+        var contents = parser.document.parseInline(item.lines[0]);
         itemNodes.add(new Element('li', contents));
       }
     }
@@ -494,7 +496,7 @@
 
 /// Parses unordered lists.
 class UnorderedListSyntax extends ListSyntax {
-  RegExp get pattern => _RE_UL;
+  RegExp get pattern => _ulPattern;
   String get listTag => 'ul';
 
   const UnorderedListSyntax();
@@ -502,7 +504,7 @@
 
 /// Parses ordered lists.
 class OrderedListSyntax extends ListSyntax {
-  RegExp get pattern => _RE_OL;
+  RegExp get pattern => _olPattern;
   String get listTag => 'ol';
 
   const OrderedListSyntax();
@@ -517,7 +519,7 @@
   bool canParse(BlockParser parser) => true;
 
   Node parse(BlockParser parser) {
-    final childLines = [];
+    var childLines = <String>[];
 
     // Eat until we hit something that ends a paragraph.
     while (!BlockSyntax.isAtBlockEnd(parser)) {
@@ -525,7 +527,7 @@
       parser.advance();
     }
 
-    final contents = parser.document.parseInline(childLines.join('\n'));
+    var contents = parser.document.parseInline(childLines.join('\n'));
     return new Element('p', contents);
   }
 }
diff --git a/pkgs/markdown/lib/src/document.dart b/pkgs/markdown/lib/src/document.dart
index 79f7c31..71a29d9 100644
--- a/pkgs/markdown/lib/src/document.dart
+++ b/pkgs/markdown/lib/src/document.dart
@@ -1,4 +1,4 @@
-library markdown.document;
+library markdown.src.document;
 
 import 'ast.dart';
 import 'block_parser.dart';
@@ -19,16 +19,16 @@
     // [id]: http:foo.com "some title"
     // Where there may whitespace in there, and where the title may be in
     // single quotes, double quotes, or parentheses.
-    final indent = r'^[ ]{0,3}'; // Leading indentation.
-    final id = r'\[([^\]]+)\]'; // Reference id in [brackets].
-    final quote = r'"[^"]+"'; // Title in "double quotes".
-    final apos = r"'[^']+'"; // Title in 'single quotes'.
-    final paren = r"\([^)]+\)"; // Title in (parentheses).
-    final pattern = new RegExp(
-        '$indent$id:\\s+(\\S+)\\s*($quote|$apos|$paren|)\\s*\$');
+    var indent = r'^[ ]{0,3}'; // Leading indentation.
+    var id = r'\[([^\]]+)\]'; // Reference id in [brackets].
+    var quote = r'"[^"]+"'; // Title in "double quotes".
+    var apos = r"'[^']+'"; // Title in 'single quotes'.
+    var paren = r"\([^)]+\)"; // Title in (parentheses).
+    var pattern =
+        new RegExp('$indent$id:\\s+(\\S+)\\s*($quote|$apos|$paren|)\\s*\$');
 
-    for (int i = 0; i < lines.length; i++) {
-      final match = pattern.firstMatch(lines[i]);
+    for (var i = 0; i < lines.length; i++) {
+      var match = pattern.firstMatch(lines[i]);
       if (match != null) {
         // Parse the link.
         var id = match[1];
@@ -57,13 +57,13 @@
 
   /// Parse the given [lines] of markdown to a series of AST nodes.
   List<Node> parseLines(List<String> lines) {
-    final parser = new BlockParser(lines, this);
+    var parser = new BlockParser(lines, this);
 
-    final blocks = [];
+    var blocks = <Node>[];
     while (!parser.isDone) {
-      for (final syntax in BlockSyntax.syntaxes) {
+      for (var syntax in BlockSyntax.syntaxes) {
         if (syntax.canParse(parser)) {
-          final block = syntax.parse(parser);
+          var block = syntax.parse(parser);
           if (block != null) blocks.add(block);
           break;
         }
diff --git a/pkgs/markdown/lib/src/html_renderer.dart b/pkgs/markdown/lib/src/html_renderer.dart
index b2d96e7..edb09a0 100644
--- a/pkgs/markdown/lib/src/html_renderer.dart
+++ b/pkgs/markdown/lib/src/html_renderer.dart
@@ -2,38 +2,37 @@
 // 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.
 
-library markdown.html_renderer;
+library markdown.src.html_renderer;
 
 import 'ast.dart';
 import 'document.dart';
 import 'inline_parser.dart';
 
 /// Converts the given string of markdown to HTML.
-String markdownToHtml(String markdown, {List<InlineSyntax> inlineSyntaxes,
-    Resolver linkResolver, Resolver imageLinkResolver,
+String markdownToHtml(String markdown,
+    {List<InlineSyntax> inlineSyntaxes,
+    Resolver linkResolver,
+    Resolver imageLinkResolver,
     bool inlineOnly: false}) {
   var document = new Document(
       inlineSyntaxes: inlineSyntaxes,
       imageLinkResolver: imageLinkResolver,
       linkResolver: linkResolver);
 
-  if (inlineOnly) {
-    return renderToHtml(document.parseInline(markdown));
-  } else {
-    // Replace windows line endings with unix line endings, and split.
-    var lines = markdown.replaceAll('\r\n', '\n').split('\n');
-    document.parseRefLinks(lines);
-    var blocks = document.parseLines(lines);
-    return renderToHtml(blocks);
-  }
+  if (inlineOnly) return renderToHtml(document.parseInline(markdown));
+
+  // Replace windows line endings with unix line endings, and split.
+  var lines = markdown.replaceAll('\r\n', '\n').split('\n');
+  document.parseRefLinks(lines);
+
+  return renderToHtml(document.parseLines(lines));
 }
 
 String renderToHtml(List<Node> nodes) => new HtmlRenderer().render(nodes);
 
 /// Translates a parsed AST to HTML.
 class HtmlRenderer implements NodeVisitor {
-  static final _BLOCK_TAGS = new RegExp(
-      'blockquote|h1|h2|h3|h4|h5|h6|hr|p|pre');
+  static final _blockTags = new RegExp('blockquote|h1|h2|h3|h4|h5|h6|hr|p|pre');
 
   StringBuffer buffer;
 
@@ -53,18 +52,17 @@
 
   bool visitElementBefore(Element element) {
     // Hackish. Separate block-level elements with newlines.
-    if (!buffer.isEmpty && _BLOCK_TAGS.firstMatch(element.tag) != null) {
+    if (!buffer.isEmpty && _blockTags.firstMatch(element.tag) != null) {
       buffer.write('\n');
     }
 
     buffer.write('<${element.tag}');
 
     // Sort the keys so that we generate stable output.
-    // TODO(rnystrom): This assumes keys returns a fresh mutable
-    // collection.
-    final attributeNames = element.attributes.keys.toList();
+    var attributeNames = element.attributes.keys.toList();
     attributeNames.sort((a, b) => a.compareTo(b));
-    for (final name in attributeNames) {
+
+    for (var name in attributeNames) {
       buffer.write(' $name="${element.attributes[name]}"');
     }
 
diff --git a/pkgs/markdown/lib/src/inline_parser.dart b/pkgs/markdown/lib/src/inline_parser.dart
index 7638cdd..fe7c4dc 100644
--- a/pkgs/markdown/lib/src/inline_parser.dart
+++ b/pkgs/markdown/lib/src/inline_parser.dart
@@ -2,7 +2,7 @@
 // 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.
 
-library markdown.inline_parser;
+library markdown.src.inline_parser;
 
 import 'ast.dart';
 import 'document.dart';
@@ -26,7 +26,6 @@
     new TextSyntax(r'\s*[A-Za-z0-9]+'),
 
     // The real syntaxes.
-
     new AutolinkSyntax(),
     new LinkSyntax(),
     new ImageLinkSyntax(),
@@ -75,13 +74,15 @@
   final List<TagState> _stack;
 
   InlineParser(this.source, this.document) : _stack = <TagState>[] {
-    /// User specified syntaxes will be the first syntaxes to be evaluated.
+    // User specified syntaxes are the first syntaxes to be evaluated.
     if (document.inlineSyntaxes != null) {
       syntaxes.addAll(document.inlineSyntaxes);
     }
+
     syntaxes.addAll(_defaultSyntaxes);
+
     // Custom link resolvers goes after the generic text syntax.
-    syntaxes.insertAll(1, <InlineSyntax>[
+    syntaxes.insertAll(1, [
       new LinkSyntax(linkResolver: document.linkResolver),
       new ImageLinkSyntax(linkResolver: document.imageLinkResolver)
     ]);
@@ -92,25 +93,28 @@
     _stack.add(new TagState(0, 0, null));
 
     while (!isDone) {
-      bool matched = false;
+      var matched = false;
 
       // See if any of the current tags on the stack match. We don't allow tags
-      // of the same kind to nest, so this takes priority over other possible // matches.
-      for (int i = _stack.length - 1; i > 0; i--) {
+      // of the same kind to nest, so this takes priority over other possible
+      // matches.
+      for (var i = _stack.length - 1; i > 0; i--) {
         if (_stack[i].tryMatch(this)) {
           matched = true;
           break;
         }
       }
+
       if (matched) continue;
 
       // See if the current text matches any defined markdown syntax.
-      for (final syntax in syntaxes) {
+      for (var syntax in syntaxes) {
         if (syntax.tryMatch(this)) {
           matched = true;
           break;
         }
       }
+
       if (matched) continue;
 
       // If we got here, it's just text.
@@ -127,17 +131,17 @@
   }
 
   void writeTextRange(int start, int end) {
-    if (end > start) {
-      final text = source.substring(start, end);
-      final nodes = _stack.last.children;
+    if (end <= start) return;
 
-      // If the previous node is text too, just append.
-      if ((nodes.length > 0) && (nodes.last is Text)) {
-        final newNode = new Text('${nodes.last.text}$text');
-        nodes[nodes.length - 1] = newNode;
-      } else {
-        nodes.add(new Text(text));
-      }
+    var text = source.substring(start, end);
+    var nodes = _stack.last.children;
+
+    // If the previous node is text too, just append.
+    if (nodes.length > 0 && nodes.last is Text) {
+      var textNode = nodes.last as Text;
+      nodes[nodes.length - 1] = new Text('${textNode.text}$text');
+    } else {
+      nodes.add(new Text(text));
     }
   }
 
@@ -147,6 +151,7 @@
 
   // TODO(rnystrom): Only need this because RegExp doesn't let you start
   // searching from a given offset.
+  @deprecated
   String get currentSource => source.substring(pos, source.length);
 
   bool get isDone => pos == source.length;
@@ -168,16 +173,15 @@
   InlineSyntax(String pattern) : pattern = new RegExp(pattern, multiLine: true);
 
   bool tryMatch(InlineParser parser) {
-    final startMatch = pattern.firstMatch(parser.currentSource);
-    if ((startMatch != null) && (startMatch.start == 0)) {
+    var startMatch = pattern.matchAsPrefix(parser.source, parser.pos);
+    if (startMatch != null) {
       // Write any existing plain text up to this point.
       parser.writeText();
 
-      if (onMatch(parser, startMatch)) {
-        parser.consume(startMatch[0].length);
-      }
+      if (onMatch(parser, startMatch)) parser.consume(startMatch[0].length);
       return true;
     }
+
     return false;
   }
 
@@ -187,9 +191,10 @@
 /// Matches stuff that should just be passed through as straight text.
 class TextSyntax extends InlineSyntax {
   final String substitute;
+
   TextSyntax(String pattern, {String sub})
-      : super(pattern),
-        substitute = sub;
+      : substitute = sub,
+        super(pattern);
 
   bool onMatch(InlineParser parser, Match match) {
     if (substitute == null) {
@@ -210,10 +215,9 @@
   // TODO(rnystrom): Make case insensitive.
 
   bool onMatch(InlineParser parser, Match match) {
-    final url = match[1];
-
-    final anchor = new Element.text('a', escapeHtml(url))
-      ..attributes['href'] = url;
+    var url = match[1];
+    var anchor = new Element.text('a', escapeHtml(url));
+    anchor.attributes['href'] = url;
     parser.addNode(anchor);
 
     return true;
@@ -226,15 +230,13 @@
   final RegExp endPattern;
   final String tag;
 
-  TagSyntax(String pattern, {String tag, String end})
-      : super(pattern),
-        endPattern = new RegExp((end != null) ? end : pattern, multiLine: true),
-        tag = tag;
-  // TODO(rnystrom): Doing this.field doesn't seem to work with named args.
+  TagSyntax(String pattern, {this.tag, String end})
+      : endPattern = new RegExp((end != null) ? end : pattern, multiLine: true),
+        super(pattern);
 
   bool onMatch(InlineParser parser, Match match) {
-    parser._stack.add(
-        new TagState(parser.pos, parser.pos + match[0].length, this));
+    parser._stack
+        .add(new TagState(parser.pos, parser.pos + match[0].length, this));
     return true;
   }
 
@@ -248,16 +250,13 @@
 class LinkSyntax extends TagSyntax {
   final Resolver linkResolver;
 
-  /// Weather or not this link was resolved by a [Resolver]
-  bool resolved = false;
-
   /// The regex for the end of a link needs to handle both reference style and
   /// inline styles as well as optional titles for inline links. To make that
   /// a bit more palatable, this breaks it into pieces.
   static get linkPattern {
-    final refLink = r'\s?\[([^\]]*)\]'; // "[id]" reflink id.
-    final title = r'(?:[ ]*"([^"]+)"|)'; // Optional title in quotes.
-    final inlineLink = '\\s?\\(([^ )]+)$title\\)'; // "(url "title")" link.
+    var refLink = r'\s?\[([^\]]*)\]'; // "[id]" reflink id.
+    var title = r'(?:[ ]*"([^"]+)"|)'; // Optional title in quotes.
+    var inlineLink = '\\s?\\(([^ )]+)$title\\)'; // "(url "title")" link.
     return '\](?:($refLink|$inlineLink)|)';
 
     // The groups matched by this are:
@@ -277,35 +276,39 @@
     // link at all. Instead, we allow users of the library to specify a special
     // resolver function ([linkResolver]) that may choose to handle
     // this. Otherwise, it's just treated as plain text.
-    if (isNullOrEmpty(match[1])) {
+    if (match[1] == null) {
       if (linkResolver == null) return null;
 
-      // Only allow implicit links if the content is just text.
-      // TODO(rnystrom): Do we want to relax this?
-      if (state.children.any((child) => child is! Text)) return null;
-      // If there are multiple children, but they are all text, send the
-      // combined text to linkResolver.
-      var textToResolve =
-          state.children.fold('', (oldVal, child) => oldVal + child.text);
+      // Treat the contents as unparsed text even if they happen to match. This
+      // way, we can handle things like [LINK_WITH_UNDERSCORES] as a link and
+      // not get confused by the emphasis.
+      var textToResolve = parser.source.substring(state.endPos, parser.pos);
 
       // See if we have a resolver that will generate a link for us.
-      resolved = true;
       return linkResolver(textToResolve);
     } else {
-      Link link = getLink(parser, match, state);
-      if (link == null) return null;
-
-      final Element node = new Element('a', state.children)
-        ..attributes["href"] = escapeHtml(link.url)
-        ..attributes['title'] = escapeHtml(link.title);
-
-      cleanMap(node.attributes);
-      return node;
+      return _createElement(parser, match, state);
     }
   }
 
+  /// Given that [match] has matched both a title and URL, creates an `<a>`
+  /// [Element] for it.
+  Element _createElement(InlineParser parser, Match match, TagState state) {
+    var link = getLink(parser, match, state);
+    if (link == null) return null;
+
+    var element = new Element('a', state.children);
+
+    element.attributes["href"] = escapeHtml(link.url);
+    if (link.title != null) {
+      element.attributes['title'] = escapeHtml(link.title);
+    }
+
+    return element;
+  }
+
   Link getLink(InlineParser parser, Match match, TagState state) {
-    if ((match[3] != null) && (match[3] != '')) {
+    if (match[3] != null && match[3] != '') {
       // Inline link like [foo](url).
       var url = match[3];
       var title = match[4];
@@ -319,10 +322,12 @@
     } else {
       var id;
       // Reference link like [foo] [bar].
-      if (match[2] == '')
-          // The id is empty ("[]") so infer it from the contents.
-          id = parser.source.substring(state.startPos + 1, parser.pos);
-      else id = match[2];
+      if (match[2] == '') {
+        // The id is empty ("[]") so infer it from the contents.
+        id = parser.source.substring(state.startPos + 1, parser.pos);
+      } else {
+        id = match[2];
+      }
 
       // References are case-insensitive.
       id = id.toLowerCase();
@@ -331,8 +336,9 @@
   }
 
   bool onMatchEnd(InlineParser parser, Match match, TagState state) {
-    Node node = createNode(parser, match, state);
+    var node = createNode(parser, match, state);
     if (node == null) return false;
+
     parser.addNode(node);
     return true;
   }
@@ -341,28 +347,29 @@
 /// Matches images like `![alternate text](url "optional title")` and
 /// `![alternate text][url reference]`.
 class ImageLinkSyntax extends LinkSyntax {
-  final Resolver linkResolver;
-  ImageLinkSyntax({this.linkResolver}) : super(pattern: r'!\[');
+  ImageLinkSyntax({Resolver linkResolver})
+      : super(linkResolver: linkResolver, pattern: r'!\[');
 
-  Node createNode(InlineParser parser, Match match, TagState state) {
-    var node = super.createNode(parser, match, state);
-    if (resolved) return node;
-    if (node == null) return null;
+  /// Creates an <a> element from the given complete [match].
+  Element _createElement(InlineParser parser, Match match, TagState state) {
+    var element = super._createElement(parser, match, state);
+    if (element == null) return null;
 
-    final Element imageElement = new Element.withTag("img")
-      ..attributes["src"] = node.attributes["href"]
-      ..attributes["title"] = node.attributes["title"]
-      ..attributes["alt"] = node.children
-      .map((e) => isNullOrEmpty(e) || e is! Text ? '' : e.text)
-      .join(' ');
+    var image = new Element.withTag("img");
+    image.attributes["src"] = element.attributes["href"];
 
-    cleanMap(imageElement.attributes);
+    if (element.attributes.containsKey("title")) {
+      image.attributes["title"] = element.attributes["title"];
+    }
 
-    node.children
+    var alt = element.children.map((e) => e is! Text ? "" : e.text).join(" ");
+    if (alt != "") image.attributes["alt"] = alt;
+
+    element.children
       ..clear()
-      ..add(imageElement);
+      ..add(image);
 
-    return node;
+    return element;
   }
 }
 
@@ -396,8 +403,8 @@
   /// Attempts to close this tag by matching the current text against its end
   /// pattern.
   bool tryMatch(InlineParser parser) {
-    Match endMatch = syntax.endPattern.firstMatch(parser.currentSource);
-    if ((endMatch != null) && (endMatch.start == 0)) {
+    var endMatch = syntax.endPattern.matchAsPrefix(parser.source, parser.pos);
+    if (endMatch != null) {
       // Close the tag.
       close(parser, endMatch);
       return true;
@@ -414,14 +421,14 @@
     // means they are mismatched. Mismatched tags are treated as plain text in
     // markdown. So for each tag above this one, we write its start tag as text
     // and then adds its children to this one's children.
-    int index = parser._stack.indexOf(this);
+    var index = parser._stack.indexOf(this);
 
     // Remove the unmatched children.
-    final unmatchedTags = parser._stack.sublist(index + 1);
+    var unmatchedTags = parser._stack.sublist(index + 1);
     parser._stack.removeRange(index + 1, parser._stack.length);
 
     // Flatten them out onto this tag.
-    for (final unmatched in unmatchedTags) {
+    for (var unmatched in unmatchedTags) {
       // Write the start tag as text.
       parser.writeTextRange(unmatched.startPos, unmatched.endPos);
 
diff --git a/pkgs/markdown/lib/src/util.dart b/pkgs/markdown/lib/src/util.dart
index 4b5f6d5..e254d26 100644
--- a/pkgs/markdown/lib/src/util.dart
+++ b/pkgs/markdown/lib/src/util.dart
@@ -1,20 +1,9 @@
-library markdown.util;
+library markdown.src.util;
 
 /// Replaces `<`, `&`, and `>`, with their HTML entity equivalents.
 String escapeHtml(String html) {
-  if (html == '' || html == null) return null;
   return html
       .replaceAll('&', '&amp;')
       .replaceAll('<', '&lt;')
       .replaceAll('>', '&gt;');
 }
-
-/// Removes null or empty values from [map].
-void cleanMap(Map map) {
-  map.keys.where((e) => isNullOrEmpty(map[e])).toList().forEach(map.remove);
-}
-
-/// Returns true if an object is null or an empty string.
-bool isNullOrEmpty(object) {
-  return object == null || object == '';
-}
diff --git a/pkgs/markdown/pubspec.lock b/pkgs/markdown/pubspec.lock
new file mode 100644
index 0000000..b8a4fd6
--- /dev/null
+++ b/pkgs/markdown/pubspec.lock
@@ -0,0 +1,131 @@
+# Generated by pub
+# See http://pub.dartlang.org/doc/glossary.html#lockfile
+packages:
+  analyzer:
+    description: analyzer
+    source: hosted
+    version: "0.26.1+3"
+  args:
+    description: args
+    source: hosted
+    version: "0.13.2"
+  async:
+    description: async
+    source: hosted
+    version: "1.3.0"
+  barback:
+    description: barback
+    source: hosted
+    version: "0.15.2+6"
+  charcode:
+    description: charcode
+    source: hosted
+    version: "1.1.0"
+  collection:
+    description: collection
+    source: hosted
+    version: "1.1.2"
+  crypto:
+    description: crypto
+    source: hosted
+    version: "0.9.0"
+  csslib:
+    description: csslib
+    source: hosted
+    version: "0.12.1"
+  glob:
+    description: glob
+    source: hosted
+    version: "1.0.5"
+  html:
+    description: html
+    source: hosted
+    version: "0.12.2"
+  http_multi_server:
+    description: http_multi_server
+    source: hosted
+    version: "1.3.2"
+  http_parser:
+    description: http_parser
+    source: hosted
+    version: "1.0.0"
+  logging:
+    description: logging
+    source: hosted
+    version: "0.11.1+1"
+  matcher:
+    description: matcher
+    source: hosted
+    version: "0.12.0+1"
+  mime:
+    description: mime
+    source: hosted
+    version: "0.9.3"
+  package_config:
+    description: package_config
+    source: hosted
+    version: "0.1.3"
+  path:
+    description: path
+    source: hosted
+    version: "1.3.6"
+  plugin:
+    description: plugin
+    source: hosted
+    version: "0.1.0"
+  pool:
+    description: pool
+    source: hosted
+    version: "1.1.0"
+  pub_semver:
+    description: pub_semver
+    source: hosted
+    version: "1.2.1"
+  shelf:
+    description: shelf
+    source: hosted
+    version: "0.6.3"
+  shelf_static:
+    description: shelf_static
+    source: hosted
+    version: "0.2.3+1"
+  shelf_web_socket:
+    description: shelf_web_socket
+    source: hosted
+    version: "0.0.1+4"
+  source_map_stack_trace:
+    description: source_map_stack_trace
+    source: hosted
+    version: "1.0.4"
+  source_maps:
+    description: source_maps
+    source: hosted
+    version: "0.10.1"
+  source_span:
+    description: source_span
+    source: hosted
+    version: "1.1.4"
+  stack_trace:
+    description: stack_trace
+    source: hosted
+    version: "1.4.1"
+  string_scanner:
+    description: string_scanner
+    source: hosted
+    version: "0.1.3+1"
+  test:
+    description: test
+    source: hosted
+    version: "0.12.4+7"
+  utf:
+    description: utf
+    source: hosted
+    version: "0.9.0+2"
+  watcher:
+    description: watcher
+    source: hosted
+    version: "0.9.7"
+  yaml:
+    description: yaml
+    source: hosted
+    version: "2.1.3"
diff --git a/pkgs/markdown/pubspec.yaml b/pkgs/markdown/pubspec.yaml
index 6b4b7af..5861600 100644
--- a/pkgs/markdown/pubspec.yaml
+++ b/pkgs/markdown/pubspec.yaml
@@ -1,9 +1,9 @@
 name: markdown
-version: 0.7.2-dev
+version: 0.8.0-dev
 author: Dart Team <misc@dartlang.org>
 description: A library for converting markdown to HTML.
-homepage: https://github.com/dpeek/dart-markdown
+homepage: https://github.com/dart-lang/markdown
 environment:
-  sdk: '>=1.0.0 <2.0.0'
+  sdk: '>=1.8.0 <2.0.0'
 dev_dependencies:
-  unittest: '>=0.9.0 <0.12.0'
+  test: '^0.12.4+1'
diff --git a/pkgs/markdown/test/markdown_test.dart b/pkgs/markdown/test/markdown_test.dart
index b41d4b2..b747289 100644
--- a/pkgs/markdown/test/markdown_test.dart
+++ b/pkgs/markdown/test/markdown_test.dart
@@ -3,19 +3,25 @@
 // BSD-style license that can be found in the LICENSE file.
 
 /// Unit tests for markdown.
-library markdownTests;
+library markdown.test.markdown_test;
 
-import 'package:unittest/unittest.dart';
+import 'package:test/test.dart';
+
 import 'package:markdown/markdown.dart';
 
+import 'util.dart';
+
 /// Most of these tests are based on observing how showdown behaves:
 /// http://softwaremaniacs.org/playground/showdown-highlight/
 void main() {
   group('Paragraphs', () {
-    validate('consecutive lines form a single paragraph', '''
+    validate(
+        'consecutive lines form a single paragraph',
+        '''
         This is the first line.
         This is the second line.
-        ''', '''
+        ''',
+        '''
         <p>This is the first line.
         This is the second line.</p>
         ''');
@@ -25,175 +31,247 @@
     // code significantly cleaner, we should consider ourselves free to change
     // these tests.
 
-    validate('are terminated by a header', '''
+    validate(
+        'are terminated by a header',
+        '''
         para
         # header
-        ''', '''
+        ''',
+        '''
         <p>para</p>
         <h1>header</h1>
         ''');
 
-    validate('are terminated by a setext header', '''
+    validate(
+        'are terminated by a setext header',
+        '''
         para
         header
         ==
-        ''', '''
+        ''',
+        '''
         <p>para</p>
         <h1>header</h1>
         ''');
 
-    validate('are terminated by a hr', '''
+    validate(
+        'are terminated by a hr',
+        '''
         para
         ___
-        ''', '''
+        ''',
+        '''
         <p>para</p>
         <hr />
         ''');
 
-    validate('consume an unordered list', '''
+    validate(
+        'consume an unordered list',
+        '''
         para
         * list
-        ''', '''
+        ''',
+        '''
         <p>para
         * list</p>
         ''');
 
-    validate('consume an ordered list', '''
+    validate(
+        'consume an ordered list',
+        '''
         para
         1. list
-        ''', '''
+        ''',
+        '''
         <p>para
         1. list</p>
         ''');
 
     // Windows line endings have a \r\n format
     // instead of the unix \n format.
-    validate('take account of windows line endings', '''
+    validate(
+        'take account of windows line endings',
+        '''
         line1\r\n\r\n        line2\r\n
-        ''', '''
+        ''',
+        '''
         <p>line1</p>
         <p>line2</p>
         ''');
   });
 
   group('Setext headers', () {
-    validate('h1', '''
+    validate(
+        'h1',
+        '''
         text
         ===
-        ''', '''
+        ''',
+        '''
         <h1>text</h1>
         ''');
 
-    validate('h2', '''
+    validate(
+        'h2',
+        '''
         text
         ---
-        ''', '''
+        ''',
+        '''
         <h2>text</h2>
         ''');
 
-    validate('h1 on first line becomes text', '''
+    validate(
+        'h1 on first line becomes text',
+        '''
         ===
-        ''', '''
+        ''',
+        '''
         <p>===</p>
         ''');
 
-    validate('h2 on first line becomes text', '''
+    validate(
+        'h2 on first line becomes text',
+        '''
         -
-        ''', '''
+        ''',
+        '''
         <p>-</p>
         ''');
 
-    validate('h1 turns preceding list into text', '''
+    validate(
+        'h1 turns preceding list into text',
+        '''
         - list
         ===
-        ''', '''
+        ''',
+        '''
         <h1>- list</h1>
         ''');
 
-    validate('h2 turns preceding list into text', '''
+    validate(
+        'h2 turns preceding list into text',
+        '''
         - list
         ===
-        ''', '''
+        ''',
+        '''
         <h1>- list</h1>
         ''');
 
-    validate('h1 turns preceding blockquote into text', '''
+    validate(
+        'h1 turns preceding blockquote into text',
+        '''
         > quote
         ===
-        ''', '''
+        ''',
+        '''
         <h1>> quote</h1>
         ''');
 
-    validate('h2 turns preceding blockquote into text', '''
+    validate(
+        'h2 turns preceding blockquote into text',
+        '''
         > quote
         ===
-        ''', '''
+        ''',
+        '''
         <h1>> quote</h1>
         ''');
   });
 
   group('Headers', () {
-    validate('h1', '''
+    validate(
+        'h1',
+        '''
         # header
-        ''', '''
+        ''',
+        '''
         <h1>header</h1>
         ''');
 
-    validate('h2', '''
+    validate(
+        'h2',
+        '''
         ## header
-        ''', '''
+        ''',
+        '''
         <h2>header</h2>
         ''');
 
-    validate('h3', '''
+    validate(
+        'h3',
+        '''
         ### header
-        ''', '''
+        ''',
+        '''
         <h3>header</h3>
         ''');
 
-    validate('h4', '''
+    validate(
+        'h4',
+        '''
         #### header
-        ''', '''
+        ''',
+        '''
         <h4>header</h4>
         ''');
 
-    validate('h5', '''
+    validate(
+        'h5',
+        '''
         ##### header
-        ''', '''
+        ''',
+        '''
         <h5>header</h5>
         ''');
 
-    validate('h6', '''
+    validate(
+        'h6',
+        '''
         ###### header
-        ''', '''
+        ''',
+        '''
         <h6>header</h6>
         ''');
 
-    validate('h7 is not a header', '''
+    validate(
+        'h7 is not a header',
+        '''
         ####### header
-        ''', '''
+        ''',
+        '''
         <p>####### header</p>
         ''');
 
-    validate('h6 must not be followed by non-space ', '''
+    validate(
+        'h6 must not be followed by non-space ',
+        '''
         ######A header
-        ''', '''
+        ''',
+        '''
         <p>######A header</p>
         ''');
 
-    validate('trailing "#" are removed', '''
+    validate(
+        'trailing "#" are removed',
+        '''
         # header ######
-        ''', '''
+        ''',
+        '''
         <h1>header</h1>
         ''');
   });
 
   group('Unordered lists', () {
-    validate('asterisk, plus and hyphen', '''
+    validate(
+        'asterisk, plus and hyphen',
+        '''
         * star
         - dash
         + plus
-        ''', '''
+        ''',
+        '''
         <ul>
           <li>star</li>
           <li>dash</li>
@@ -201,22 +279,28 @@
         </ul>
         ''');
 
-    validate('allow numbered lines after first', '''
+    validate(
+        'allow numbered lines after first',
+        '''
         * a
         1. b
-        ''', '''
+        ''',
+        '''
         <ul>
           <li>a</li>
           <li>b</li>
         </ul>
         ''');
 
-    validate('allow a tab after the marker', '''
+    validate(
+        'allow a tab after the marker',
+        '''
         *\ta
         +\tb
         -\tc
         1.\td
-        ''', '''
+        ''',
+        '''
         <ul>
           <li>a</li>
           <li>b</li>
@@ -225,23 +309,29 @@
         </ul>
         ''');
 
-    validate('wrap items in paragraphs if blank lines separate', '''
+    validate(
+        'wrap items in paragraphs if blank lines separate',
+        '''
         * one
 
         * two
-        ''', '''
+        ''',
+        '''
         <ul>
           <li><p>one</p></li>
           <li><p>two</p></li>
         </ul>
         ''');
 
-    validate('force paragraph on item before and after blank lines', '''
+    validate(
+        'force paragraph on item before and after blank lines',
+        '''
         *   one
         *   two
 
         *   three
-        ''', '''
+        ''',
+        '''
         <ul>
           <li>one</li>
           <li>
@@ -253,24 +343,30 @@
         </ul>
         ''');
 
-    validate('do not force paragraph if item is already block', '''
+    validate(
+        'do not force paragraph if item is already block',
+        '''
         * > quote
 
         * # header
-        ''', '''
+        ''',
+        '''
         <ul>
           <li><blockquote><p>quote</p></blockquote></li>
           <li><h1>header</h1></li>
         </ul>
         ''');
 
-    validate('can contain multiple paragraphs', '''
+    validate(
+        'can contain multiple paragraphs',
+        '''
         *   one
 
             two
 
         *   three
-        ''', '''
+        ''',
+        '''
         <ul>
           <li>
             <p>one</p>
@@ -282,11 +378,14 @@
         </ul>
         ''');
 
-    validate('can span newlines', '''
+    validate(
+        'can span newlines',
+        '''
         *   one
             two
         *   three
-        ''', '''
+        ''',
+        '''
         <ul>
           <li>
             <p>one
@@ -315,11 +414,14 @@
   });
 
   group('Ordered lists', () {
-    validate('start with numbers', '''
+    validate(
+        'start with numbers',
+        '''
         1. one
         45.  two
            12345. three
-        ''', '''
+        ''',
+        '''
         <ol>
           <li>one</li>
           <li>two</li>
@@ -327,10 +429,13 @@
         </ol>
         ''');
 
-    validate('allow unordered lines after first', '''
+    validate(
+        'allow unordered lines after first',
+        '''
         1. a
         * b
-        ''', '''
+        ''',
+        '''
         <ol>
           <li>a</li>
           <li>b</li>
@@ -339,30 +444,39 @@
   });
 
   group('Blockquotes', () {
-    validate('single line', '''
+    validate(
+        'single line',
+        '''
         > blah
-        ''', '''
+        ''',
+        '''
         <blockquote>
           <p>blah</p>
         </blockquote>
         ''');
 
-    validate('with two paragraphs', '''
+    validate(
+        'with two paragraphs',
+        '''
         > first
         >
         > second
-        ''', '''
+        ''',
+        '''
         <blockquote>
           <p>first</p>
           <p>second</p>
         </blockquote>
         ''');
 
-    validate('nested', '''
+    validate(
+        'nested',
+        '''
         > one
         >> two
         > > > three
-        ''', '''
+        ''',
+        '''
         <blockquote>
           <p>one</p>
           <blockquote>
@@ -376,32 +490,41 @@
   });
 
   group('Code blocks', () {
-    validate('single line', '''
+    validate(
+        'single line',
+        '''
             code
-        ''', '''
+        ''',
+        '''
         <pre><code>code</code></pre>
         ''');
 
-    validate('include leading whitespace after indentation', '''
+    validate(
+        'include leading whitespace after indentation',
+        '''
             zero
              one
               two
                three
-        ''', '''
+        ''',
+        '''
         <pre><code>zero
          one
           two
            three</code></pre>
         ''');
 
-    validate('code blocks separated by newlines form one block', '''
+    validate(
+        'code blocks separated by newlines form one block',
+        '''
             zero
             one
 
             two
 
             three
-        ''', '''
+        ''',
+        '''
         <pre><code>zero
          one
 
@@ -410,7 +533,9 @@
          three</code></pre>
         ''');
 
-    validate('code blocks separated by two newlines form multiple blocks', '''
+    validate(
+        'code blocks separated by two newlines form multiple blocks',
+        '''
             zero
             one
 
@@ -419,73 +544,95 @@
 
 
             three
-        ''', '''
+        ''',
+        '''
         <pre><code>zero
          one</code></pre>
         <pre><code>two</code></pre>
         <pre><code>three</code></pre>
         ''');
 
-    validate('escape HTML characters', '''
+    validate(
+        'escape HTML characters',
+        '''
             <&>
-        ''', '''
+        ''',
+        '''
         <pre><code>&lt;&amp;&gt;</code></pre>
         ''');
   });
 
   group('Fenced code blocks', () {
-    validate('without an optional language identifier', '''
+    validate(
+        'without an optional language identifier',
+        '''
         ```
         code
         ```
-        ''', '''
+        ''',
+        '''
         <pre><code>code
         </code></pre>
         ''');
 
-    validate('with an optional language identifier', '''
+    validate(
+        'with an optional language identifier',
+        '''
         ```dart
         code
         ```
-        ''', '''
+        ''',
+        '''
         <pre class="dart"><code>code
         </code></pre>
         ''');
 
-    validate('escape HTML characters', '''
+    validate(
+        'escape HTML characters',
+        '''
         ```
         <&>
         ```
-        ''', '''
+        ''',
+        '''
         <pre><code>&lt;&amp;&gt;
         </code></pre>
         ''');
 
-    validate('Pandoc style without language identifier', '''
+    validate(
+        'Pandoc style without language identifier',
+        '''
         ~~~~~
         code
         ~~~~~
-        ''', '''
+        ''',
+        '''
         <pre><code>code
         </code></pre>
         ''');
 
-    validate('Pandoc style with language identifier', '''
+    validate(
+        'Pandoc style with language identifier',
+        '''
         ~~~~~dart
         code
         ~~~~~
-        ''', '''
+        ''',
+        '''
         <pre class="dart"><code>code
         </code></pre>
         ''');
 
-    validate('Pandoc style with inner tildes row', '''
+    validate(
+        'Pandoc style with inner tildes row',
+        '''
         ~~~~~
         ~~~
         code
         ~~~
         ~~~~~
-        ''', '''
+        ''',
+        '''
         <pre><code>~~~
         code
         ~~~
@@ -494,68 +641,92 @@
   });
 
   group('Horizontal rules', () {
-    validate('from dashes', '''
+    validate(
+        'from dashes',
+        '''
         ---
-        ''', '''
+        ''',
+        '''
         <hr />
         ''');
 
-    validate('from asterisks', '''
+    validate(
+        'from asterisks',
+        '''
         ***
-        ''', '''
+        ''',
+        '''
         <hr />
         ''');
 
-    validate('from underscores', '''
+    validate(
+        'from underscores',
+        '''
         ___
-        ''', '''
+        ''',
+        '''
         <hr />
         ''');
 
-    validate('can include up to two spaces', '''
+    validate(
+        'can include up to two spaces',
+        '''
         _ _  _
-        ''', '''
+        ''',
+        '''
         <hr />
         ''');
   });
 
   group('Block-level HTML', () {
-    validate('single line', '''
+    validate(
+        'single line',
+        '''
         <table></table>
-        ''', '''
+        ''',
+        '''
         <table></table>
         ''');
 
-    validate('multi-line', '''
+    validate(
+        'multi-line',
+        '''
         <table>
             blah
         </table>
-        ''', '''
+        ''',
+        '''
         <table>
             blah
         </table>
         ''');
 
-    validate('blank line ends block', '''
+    validate(
+        'blank line ends block',
+        '''
         <table>
             blah
         </table>
 
         para
-        ''', '''
+        ''',
+        '''
         <table>
             blah
         </table>
         <p>para</p>
         ''');
 
-    validate('HTML can be bogus', '''
+    validate(
+        'HTML can be bogus',
+        '''
         <bogus>
         blah
         </weird>
 
         para
-        ''', '''
+        ''',
+        '''
         <bogus>
         blah
         </weird>
@@ -564,319 +735,466 @@
   });
 
   group('Strong', () {
-    validate('using asterisks', '''
+    validate(
+        'using asterisks',
+        '''
         before **strong** after
-        ''', '''
+        ''',
+        '''
         <p>before <strong>strong</strong> after</p>
         ''');
 
-    validate('using underscores', '''
+    validate(
+        'using underscores',
+        '''
         before __strong__ after
-        ''', '''
+        ''',
+        '''
         <p>before <strong>strong</strong> after</p>
         ''');
 
-    validate('unmatched asterisks', '''
+    validate(
+        'unmatched asterisks',
+        '''
         before ** after
-        ''', '''
+        ''',
+        '''
         <p>before ** after</p>
         ''');
 
-    validate('unmatched underscores', '''
+    validate(
+        'unmatched underscores',
+        '''
         before __ after
-        ''', '''
+        ''',
+        '''
         <p>before __ after</p>
         ''');
 
-    validate('multiple spans in one text', '''
+    validate(
+        'multiple spans in one text',
+        '''
         a **one** b __two__ c
-        ''', '''
+        ''',
+        '''
         <p>a <strong>one</strong> b <strong>two</strong> c</p>
         ''');
 
-    validate('multi-line', '''
+    validate(
+        'multi-line',
+        '''
         before **first
         second** after
-        ''', '''
+        ''',
+        '''
         <p>before <strong>first
         second</strong> after</p>
         ''');
   });
 
   group('Emphasis and strong', () {
-    validate('single asterisks', '''
+    validate(
+        'single asterisks',
+        '''
         before *em* after
-        ''', '''
+        ''',
+        '''
         <p>before <em>em</em> after</p>
         ''');
 
-    validate('single underscores', '''
+    validate(
+        'single underscores',
+        '''
         before _em_ after
-        ''', '''
+        ''',
+        '''
         <p>before <em>em</em> after</p>
         ''');
 
-    validate('double asterisks', '''
+    validate(
+        'double asterisks',
+        '''
         before **strong** after
-        ''', '''
+        ''',
+        '''
         <p>before <strong>strong</strong> after</p>
         ''');
 
-    validate('double underscores', '''
+    validate(
+        'double underscores',
+        '''
         before __strong__ after
-        ''', '''
+        ''',
+        '''
         <p>before <strong>strong</strong> after</p>
         ''');
 
-    validate('unmatched asterisk', '''
+    validate(
+        'unmatched asterisk',
+        '''
         before *after
-        ''', '''
+        ''',
+        '''
         <p>before *after</p>
         ''');
 
-    validate('unmatched underscore', '''
+    validate(
+        'unmatched underscore',
+        '''
         before _after
-        ''', '''
+        ''',
+        '''
         <p>before _after</p>
         ''');
 
-    validate('multiple spans in one text', '''
+    validate(
+        'multiple spans in one text',
+        '''
         a *one* b _two_ c
-        ''', '''
+        ''',
+        '''
         <p>a <em>one</em> b <em>two</em> c</p>
         ''');
 
-    validate('multi-line', '''
+    validate(
+        'multi-line',
+        '''
         before *first
         second* after
-        ''', '''
+        ''',
+        '''
         <p>before <em>first
         second</em> after</p>
         ''');
 
-    validate('not processed when surrounded by spaces', '''
+    validate(
+        'not processed when surrounded by spaces',
+        '''
         a * b * c _ d _ e
-        ''', '''
+        ''',
+        '''
         <p>a * b * c _ d _ e</p>
         ''');
 
-    validate('strong then emphasis', '''
+    validate(
+        'strong then emphasis',
+        '''
         **strong***em*
-        ''', '''
+        ''',
+        '''
         <p><strong>strong</strong><em>em</em></p>
         ''');
 
-    validate('emphasis then strong', '''
+    validate(
+        'emphasis then strong',
+        '''
         *em***strong**
-        ''', '''
+        ''',
+        '''
         <p><em>em</em><strong>strong</strong></p>
         ''');
 
-    validate('emphasis inside strong', '''
+    validate(
+        'emphasis inside strong',
+        '''
         **strong *em***
-        ''', '''
+        ''',
+        '''
         <p><strong>strong <em>em</em></strong></p>
         ''');
 
-    validate('mismatched in nested', '''
+    validate(
+        'mismatched in nested',
+        '''
         *a _b* c_
-        ''', '''
+        ''',
+        '''
         <p><em>a _b</em> c_</p>
         ''');
 
-    validate('cannot nest tags of same type', '''
+    validate(
+        'cannot nest tags of same type',
+        '''
         *a _b *c* d_ e*
-        ''', '''
+        ''',
+        '''
         <p><em>a _b </em>c<em> d_ e</em></p>
         ''');
   });
 
   group('Inline code', () {
-    validate('simple case', '''
+    validate(
+        'simple case',
+        '''
         before `source` after
-        ''', '''
+        ''',
+        '''
         <p>before <code>source</code> after</p>
         ''');
 
-    validate('unmatched backtick', '''
+    validate(
+        'unmatched backtick',
+        '''
         before ` after
-        ''', '''
+        ''',
+        '''
         <p>before ` after</p>
         ''');
-    validate('multiple spans in one text', '''
+    validate(
+        'multiple spans in one text',
+        '''
         a `one` b `two` c
-        ''', '''
+        ''',
+        '''
         <p>a <code>one</code> b <code>two</code> c</p>
         ''');
 
-    validate('multi-line', '''
+    validate(
+        'multi-line',
+        '''
         before `first
         second` after
-        ''', '''
+        ''',
+        '''
         <p>before <code>first
         second</code> after</p>
         ''');
 
-    validate('simple double backticks', '''
+    validate(
+        'simple double backticks',
+        '''
         before ``source`` after
-        ''', '''
+        ''',
+        '''
         <p>before <code>source</code> after</p>
         ''');
 
-    validate('double backticks', '''
+    validate(
+        'double backticks',
+        '''
         before ``can `contain` backticks`` after
-        ''', '''
+        ''',
+        '''
         <p>before <code>can `contain` backticks</code> after</p>
         ''');
 
-    validate('double backticks with spaces', '''
+    validate(
+        'double backticks with spaces',
+        '''
         before `` `tick` `` after
-        ''', '''
+        ''',
+        '''
         <p>before <code>`tick`</code> after</p>
         ''');
 
-    validate('multiline double backticks with spaces', '''
+    validate(
+        'multiline double backticks with spaces',
+        '''
         before ``in `tick`
         another`` after
-        ''', '''
+        ''',
+        '''
         <p>before <code>in `tick`
         another</code> after</p>
         ''');
 
-    validate('ignore markup inside code', '''
+    validate(
+        'ignore markup inside code',
+        '''
         before `*b* _c_` after
-        ''', '''
+        ''',
+        '''
         <p>before <code>*b* _c_</code> after</p>
         ''');
 
-    validate('escape HTML characters', '''
+    validate(
+        'escape HTML characters',
+        '''
         `<&>`
-        ''', '''
+        ''',
+        '''
         <p><code>&lt;&amp;&gt;</code></p>
         ''');
 
-    validate('escape HTML tags', '''
+    validate(
+        'escape HTML tags',
+        '''
         '*' `<em>`
-        ''', '''
+        ''',
+        '''
         <p>'*' <code>&lt;em&gt;</code></p>
         ''');
   });
 
   group('HTML encoding', () {
-    validate('less than and ampersand are escaped', '''
+    validate(
+        'less than and ampersand are escaped',
+        '''
         < &
-        ''', '''
+        ''',
+        '''
         <p>&lt; &amp;</p>
         ''');
-    validate('greater than is not escaped', '''
+    validate(
+        'greater than is not escaped',
+        '''
         not you >
-        ''', '''
+        ''',
+        '''
         <p>not you ></p>
         ''');
-    validate('existing entities are untouched', '''
+    validate(
+        'existing entities are untouched',
+        '''
         &amp;
-        ''', '''
+        ''',
+        '''
         <p>&amp;</p>
         ''');
   });
 
   group('Autolinks', () {
-    validate('basic link', '''
+    validate(
+        'basic link',
+        '''
         before <http://foo.com/> after
-        ''', '''
+        ''',
+        '''
         <p>before <a href="http://foo.com/">http://foo.com/</a> after</p>
         ''');
-    validate('handles ampersand in url', '''
+    validate(
+        'handles ampersand in url',
+        '''
         <http://foo.com/?a=1&b=2>
-        ''', '''
+        ''',
+        '''
         <p><a href="http://foo.com/?a=1&b=2">http://foo.com/?a=1&amp;b=2</a></p>
         ''');
   });
 
   group('Reference links', () {
-    validate('double quotes for title', '''
+    validate(
+        'double quotes for title',
+        '''
         links [are] [a] awesome
 
         [a]: http://foo.com "woo"
-        ''', '''
+        ''',
+        '''
         <p>links <a href="http://foo.com" title="woo">are</a> awesome</p>
         ''');
-    validate('single quoted title', """
+    validate(
+        'single quoted title',
+        """
         links [are] [a] awesome
 
         [a]: http://foo.com 'woo'
-        """, '''
+        """,
+        '''
         <p>links <a href="http://foo.com" title="woo">are</a> awesome</p>
         ''');
-    validate('parentheses for title', '''
+    validate(
+        'parentheses for title',
+        '''
         links [are] [a] awesome
 
         [a]: http://foo.com (woo)
-        ''', '''
+        ''',
+        '''
         <p>links <a href="http://foo.com" title="woo">are</a> awesome</p>
         ''');
-    validate('no title', '''
+    validate(
+        'no title',
+        '''
         links [are] [a] awesome
 
         [a]: http://foo.com
-        ''', '''
+        ''',
+        '''
         <p>links <a href="http://foo.com">are</a> awesome</p>
         ''');
-    validate('unknown link becomes plaintext', '''
+    validate(
+        'unknown link becomes plaintext',
+        '''
         [not] [known]
-        ''', '''
+        ''',
+        '''
         <p>[not] [known]</p>
         ''');
-    validate('can style link contents', '''
+    validate(
+        'can style link contents',
+        '''
         links [*are*] [a] awesome
 
         [a]: http://foo.com
-        ''', '''
+        ''',
+        '''
         <p>links <a href="http://foo.com"><em>are</em></a> awesome</p>
         ''');
-    validate('inline styles after a bad link are processed', '''
+    validate(
+        'inline styles after a bad link are processed',
+        '''
         [bad] `code`
-        ''', '''
+        ''',
+        '''
         <p>[bad] <code>code</code></p>
         ''');
-    validate('empty reference uses text from link', '''
+    validate(
+        'empty reference uses text from link',
+        '''
         links [are][] awesome
 
         [are]: http://foo.com
-        ''', '''
+        ''',
+        '''
         <p>links <a href="http://foo.com">are</a> awesome</p>
         ''');
-    validate('references are case-insensitive', '''
+    validate(
+        'references are case-insensitive',
+        '''
         links [ARE][] awesome
 
         [are]: http://foo.com
-        ''', '''
+        ''',
+        '''
         <p>links <a href="http://foo.com">ARE</a> awesome</p>
         ''');
   });
 
   group('Inline links', () {
-    validate('double quotes for title', '''
+    validate(
+        'double quotes for title',
+        '''
         links [are](http://foo.com "woo") awesome
-        ''', '''
+        ''',
+        '''
         <p>links <a href="http://foo.com" title="woo">are</a> awesome</p>
         ''');
-    validate('no title', '''
+    validate(
+        'no title',
+        '''
         links [are] (http://foo.com) awesome
-        ''', '''
+        ''',
+        '''
         <p>links <a href="http://foo.com">are</a> awesome</p>
         ''');
-    validate('can style link contents', '''
+    validate(
+        'can style link contents',
+        '''
         links [*are*](http://foo.com) awesome
-        ''', '''
+        ''',
+        '''
         <p>links <a href="http://foo.com"><em>are</em></a> awesome</p>
         ''');
   });
 
   group('Inline Images', () {
-    validate('image', '''
+    validate(
+        'image',
+        '''
         ![](http://foo.com/foo.png)
-        ''', '''
+        ''',
+        '''
         <p>
           <a href="http://foo.com/foo.png">
             <img src="http://foo.com/foo.png"></img>
@@ -884,9 +1202,12 @@
         </p>
         ''');
 
-    validate('alternate text', '''
+    validate(
+        'alternate text',
+        '''
         ![alternate text](http://foo.com/foo.png)
-        ''', '''
+        ''',
+        '''
         <p>
           <a href="http://foo.com/foo.png">
             <img alt="alternate text" src="http://foo.com/foo.png"></img>
@@ -894,18 +1215,24 @@
         </p>
         ''');
 
-    validate('title', '''
+    validate(
+        'title',
+        '''
         ![](http://foo.com/foo.png "optional title")
-        ''', '''
+        ''',
+        '''
         <p>
           <a href="http://foo.com/foo.png" title="optional title">
             <img src="http://foo.com/foo.png" title="optional title"></img>
           </a>
         </p>
         ''');
-    validate('invalid alt text', '''
+    validate(
+        'invalid alt text',
+        '''
         ![`alt`](http://foo.com/foo.png)
-        ''', '''
+        ''',
+        '''
         <p>
           <a href="http://foo.com/foo.png">
             <img src="http://foo.com/foo.png"></img>
@@ -915,10 +1242,13 @@
   });
 
   group('Reference Images', () {
-    validate('image', '''
+    validate(
+        'image',
+        '''
         ![][foo]
         [foo]: http://foo.com/foo.png
-        ''', '''
+        ''',
+        '''
         <p>
           <a href="http://foo.com/foo.png">
             <img src="http://foo.com/foo.png"></img>
@@ -926,10 +1256,13 @@
         </p>
         ''');
 
-    validate('alternate text', '''
+    validate(
+        'alternate text',
+        '''
         ![alternate text][foo]
         [foo]: http://foo.com/foo.png
-        ''', '''
+        ''',
+        '''
         <p>
           <a href="http://foo.com/foo.png">
             <img alt="alternate text" src="http://foo.com/foo.png"></img>
@@ -937,10 +1270,13 @@
         </p>
         ''');
 
-    validate('title', '''
+    validate(
+        'title',
+        '''
         ![][foo]
         [foo]: http://foo.com/foo.png "optional title"
-        ''', '''
+        ''',
+        '''
         <p>
           <a href="http://foo.com/foo.png" title="optional title">
             <img src="http://foo.com/foo.png" title="optional title"></img>
@@ -948,10 +1284,13 @@
         </p>
         ''');
 
-    validate('invalid alt text', '''
+    validate(
+        'invalid alt text',
+        '''
         ![`alt`][foo]
         [foo]: http://foo.com/foo.png "optional title"
-        ''', '''
+        ''',
+        '''
         <p>
           <a href="http://foo.com/foo.png" title="optional title">
             <img src="http://foo.com/foo.png" title="optional title"></img>
@@ -961,33 +1300,53 @@
   });
 
   group('Resolver', () {
-    var nyanResolver = (text) => new Text('~=[,,_${text}_,,]:3');
-    validate('simple link resolver', '''
+    Node nyanResolver(String text) => new Text('~=[,,_${text}_,,]:3');
+
+    validate(
+        'simple link resolver',
+        '''
         resolve [this] thing
-        ''', '''
+        ''',
+        '''
         <p>resolve ~=[,,_this_,,]:3 thing</p>
-        ''', linkResolver: nyanResolver);
-    validate('simple image resolver', '''
+        ''',
+        linkResolver: nyanResolver);
+    validate(
+        'simple image resolver',
+        '''
         resolve ![this] thing
-        ''', '''
+        ''',
+        '''
         <p>resolve ~=[,,_this_,,]:3 thing</p>
-        ''', imageLinkResolver: nyanResolver);
+        ''',
+        imageLinkResolver: nyanResolver);
+
+    validate(
+        'can resolve link containing inline tags',
+        '''
+        resolve [*star* _underline_] thing
+        ''',
+        '''
+        <p>resolve ~=[,,_*star* _underline__,,]:3 thing</p>
+        ''',
+        linkResolver: nyanResolver);
   });
 
   group('Custom inline syntax', () {
-    List<InlineSyntax> nyanSyntax = [
-      new TextSyntax('nyan', sub: '~=[,,_,,]:3')
-    ];
-    validate('simple inline syntax', '''
+    validate(
+        'simple inline syntax',
+        '''
         nyan
-        ''', '''
+        ''',
+        '''
         <p>~=[,,_,,]:3</p>
-        ''', inlineSyntaxes: nyanSyntax);
+        ''',
+        inlineSyntaxes: [new TextSyntax('nyan', sub: '~=[,,_,,]:3')]);
 
     validate('dart custom links', 'links [are<foo>] awesome',
         '<p>links <a>are&lt;foo></a> awesome</p>',
-        linkResolver: (text) => new Element.text(
-            'a', text.replaceAll('<', '&lt;')));
+        linkResolver: (text) =>
+            new Element.text('a', text.replaceAll('<', '&lt;')));
 
     // TODO(amouravski): need more tests here for custom syntaxes, as some
     // things are not quite working properly. The regexps are sometime a little
@@ -995,129 +1354,70 @@
   });
 
   group('Inline only', () {
-    validate('simple line', '''
+    validate(
+        'simple line',
+        '''
         This would normally create a paragraph.
-        ''', '''
+        ''',
+        '''
         This would normally create a paragraph.
-        ''', inlineOnly: true);
-    validate('strong and em', '''
+        ''',
+        inlineOnly: true);
+    validate(
+        'strong and em',
+        '''
         This would _normally_ create a **paragraph**.
-        ''', '''
+        ''',
+        '''
         This would <em>normally</em> create a <strong>paragraph</strong>.
-        ''', inlineOnly: true);
-    validate('link', '''
+        ''',
+        inlineOnly: true);
+    validate(
+        'link',
+        '''
         This [link](http://www.example.com/) will work normally.
-        ''', '''
+        ''',
+        '''
         This <a href="http://www.example.com/">link</a> will work normally.
-        ''', inlineOnly: true);
-    validate('references do not work', '''
+        ''',
+        inlineOnly: true);
+    validate(
+        'references do not work',
+        '''
         [This][] shouldn't work, though.
-        ''', '''
+        ''',
+        '''
         [This][] shouldn't work, though.
-        ''', inlineOnly: true);
-    validate('less than and ampersand are escaped', '''
+        ''',
+        inlineOnly: true);
+    validate(
+        'less than and ampersand are escaped',
+        '''
         < &
-        ''', '''
+        ''',
+        '''
         &lt; &amp;
-        ''', inlineOnly: true);
-    validate('keeps newlines', '''
+        ''',
+        inlineOnly: true);
+    validate(
+        'keeps newlines',
+        '''
         This paragraph
         continues after a newline.
-        ''', '''
+        ''',
+        '''
         This paragraph
         continues after a newline.
-        ''', inlineOnly: true);
-    validate('ignores block-level markdown syntax', '''
+        ''',
+        inlineOnly: true);
+    validate(
+        'ignores block-level markdown syntax',
+        '''
         1. This will not be an <ol>.
-        ''', '''
+        ''',
+        '''
         1. This will not be an &lt;ol>.
-        ''', inlineOnly: true);
+        ''',
+        inlineOnly: true);
   });
 }
-
-/**
- * Removes eight spaces of leading indentation from a multiline string.
- *
- * Note that this is very sensitive to how the literals are styled. They should
- * be:
- *     '''
- *     Text starts on own line. Lines up with subsequent lines.
- *     Lines are indented exactly 8 characters from the left margin.'''
- *
- * This does nothing if text is only a single line.
- */
-// TODO(nweiz): Make this auto-detect the indentation level from the first
-// non-whitespace line.
-String cleanUpLiteral(String text) {
-  var lines = text.split('\n');
-  if (lines.length <= 1) return text;
-
-  for (var j = 0; j < lines.length; j++) {
-    if (lines[j].length > 8) {
-      lines[j] = lines[j].substring(8, lines[j].length);
-    } else {
-      lines[j] = '';
-    }
-  }
-
-  return lines.join('\n');
-}
-
-void validate(String description, String markdown, String html,
-    {List<InlineSyntax> inlineSyntaxes,
-    Resolver linkResolver, Resolver imageLinkResolver,
-    bool inlineOnly: false}) {
-  test(description, () {
-    markdown = cleanUpLiteral(markdown);
-    html = cleanUpLiteral(html);
-
-    var result = markdownToHtml(markdown,
-        inlineSyntaxes: inlineSyntaxes,
-        linkResolver: linkResolver,
-        imageLinkResolver: imageLinkResolver,
-        inlineOnly: inlineOnly);
-    var passed = compareOutput(html, result);
-
-    if (!passed) {
-      // Remove trailing newline.
-      html = html.substring(0, html.length - 1);
-
-      var sb = new StringBuffer();
-      sb.writeln('Expected: ${html.replaceAll("\n", "\n          ")}');
-      sb.writeln('  Actual: ${result.replaceAll("\n", "\n          ")}');
-
-      fail(sb.toString());
-    }
-  });
-}
-
-/// Does a loose comparison of the two strings of HTML. Ignores differences in
-/// newlines and indentation.
-bool compareOutput(String a, String b) {
-  int i = 0;
-  int j = 0;
-
-  skipIgnored(String s, int i) {
-    // Ignore newlines.
-    while ((i < s.length) && (s[i] == '\n')) {
-      i++;
-      // Ignore indentation.
-      while ((i < s.length) && (s[i] == ' ')) i++;
-    }
-
-    return i;
-  }
-
-  while (true) {
-    i = skipIgnored(a, i);
-    j = skipIgnored(b, j);
-
-    // If one string runs out of non-ignored strings, the other must too.
-    if (i == a.length) return j == b.length;
-    if (j == b.length) return i == a.length;
-
-    if (a[i] != b[j]) return false;
-    i++;
-    j++;
-  }
-}
diff --git a/pkgs/markdown/test/util.dart b/pkgs/markdown/test/util.dart
new file mode 100644
index 0000000..d9a6e38
--- /dev/null
+++ b/pkgs/markdown/test/util.dart
@@ -0,0 +1,95 @@
+// 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.
+
+library markdown.test.utils;
+
+import 'package:test/test.dart';
+
+import 'package:markdown/markdown.dart';
+
+/// Removes eight spaces of leading indentation from a multiline string.
+///
+/// Note that this is very sensitive to how the literals are styled. They should
+/// be:
+///     '''
+///     Text starts on own line. Lines up with subsequent lines.
+///     Lines are indented exactly 8 characters from the left margin.'''
+///
+/// This does nothing if text is only a single line.
+// TODO(nweiz): Make this auto-detect the indentation level from the first
+// non-whitespace line.
+String cleanUpLiteral(String text) {
+  var lines = text.split('\n');
+  if (lines.length <= 1) return text;
+
+  for (var j = 0; j < lines.length; j++) {
+    if (lines[j].length > 8) {
+      lines[j] = lines[j].substring(8, lines[j].length);
+    } else {
+      lines[j] = '';
+    }
+  }
+
+  return lines.join('\n');
+}
+
+void validate(String description, String markdown, String html,
+    {List<InlineSyntax> inlineSyntaxes,
+    Resolver linkResolver,
+    Resolver imageLinkResolver,
+    bool inlineOnly: false}) {
+  test(description, () {
+    markdown = cleanUpLiteral(markdown);
+    html = cleanUpLiteral(html);
+
+    var result = markdownToHtml(markdown,
+        inlineSyntaxes: inlineSyntaxes,
+        linkResolver: linkResolver,
+        imageLinkResolver: imageLinkResolver,
+        inlineOnly: inlineOnly);
+    var passed = compareOutput(html, result);
+
+    if (!passed) {
+      // Remove trailing newline.
+      html = html.substring(0, html.length - 1);
+
+      var sb = new StringBuffer();
+      sb.writeln('Expected: ${html.replaceAll("\n", "\n          ")}');
+      sb.writeln('  Actual: ${result.replaceAll("\n", "\n          ")}');
+
+      fail(sb.toString());
+    }
+  });
+}
+
+/// Does a loose comparison of the two strings of HTML. Ignores differences in
+/// newlines and indentation.
+bool compareOutput(String a, String b) {
+  int i = 0;
+  int j = 0;
+
+  skipIgnored(String s, int i) {
+    // Ignore newlines.
+    while ((i < s.length) && (s[i] == '\n')) {
+      i++;
+      // Ignore indentation.
+      while ((i < s.length) && (s[i] == ' ')) i++;
+    }
+
+    return i;
+  }
+
+  while (true) {
+    i = skipIgnored(a, i);
+    j = skipIgnored(b, j);
+
+    // If one string runs out of non-ignored strings, the other must too.
+    if (i == a.length) return j == b.length;
+    if (j == b.length) return i == a.length;
+
+    if (a[i] != b[j]) return false;
+    i++;
+    j++;
+  }
+}