Merge pull request dart-lang/markdown#131 from srawlins/add-gh-pages-script

Add update-gh-pages script
diff --git a/pkgs/markdown/CHANGELOG.md b/pkgs/markdown/CHANGELOG.md
index 416778a..1a17056 100644
--- a/pkgs/markdown/CHANGELOG.md
+++ b/pkgs/markdown/CHANGELOG.md
@@ -4,6 +4,8 @@
   * `dart bin/markdown.dart --version` now shows the package version number.
   * The playground app now shows the version number.
 * Improve autolink parsing.
+* Added new table syntax: `TableSyntax`. This can be used by passing
+  `const TableSyntax()` to `markdownToHtml()`'s `blockSyntaxes:` argument.
 * For development: added tool/travis.sh.
 
 ## 0.11.0+1
diff --git a/pkgs/markdown/example/app.dart b/pkgs/markdown/example/app.dart
index 9e0fd37..1455dc6 100644
--- a/pkgs/markdown/example/app.dart
+++ b/pkgs/markdown/example/app.dart
@@ -23,6 +23,7 @@
       savedMarkdown.isNotEmpty &&
       savedMarkdown != introText) {
     markdownInput.value = savedMarkdown;
+    markdownInput.focus();
     _renderMarkdown();
   } else {
     _typeItOut(introText, 82);
@@ -40,6 +41,10 @@
 }
 
 void _typeItOut(String msg, int pos) {
+  Timer timer;
+  markdownInput.onKeyUp.listen((_) {
+    timer?.cancel();
+  });
   addCharacter() {
     if (pos > msg.length) {
       return;
@@ -48,10 +53,10 @@
     markdownInput.focus();
     _renderMarkdown();
     pos++;
-    new Timer(typing, addCharacter);
+    timer = new Timer(typing, addCharacter);
   }
 
-  new Timer(typing, addCharacter);
+  timer = new Timer(typing, addCharacter);
 }
 
 class NullTreeSanitizer implements NodeTreeSanitizer {
diff --git a/pkgs/markdown/lib/src/block_parser.dart b/pkgs/markdown/lib/src/block_parser.dart
index efab2a4..c26056a 100644
--- a/pkgs/markdown/lib/src/block_parser.dart
+++ b/pkgs/markdown/lib/src/block_parser.dart
@@ -48,6 +48,9 @@
 final _olPattern =
     new RegExp(r'^([ ]{0,3})(\d{1,9})([\.)])(([ \t])([ \t]*)(.*))?$');
 
+/// A line of hyphens separated by at least one pipe.
+final _tablePattern = new RegExp(r'^[ ]{0,3}\|?(:?\-+:?\|)+(:?\-+:?)?$');
+
 /// Maintains the internal state needed to parse a series of lines into blocks
 /// of Markdown suitable for further inline parsing.
 class BlockParser {
@@ -113,8 +116,9 @@
   ///
   /// `peek(1)` is equivalent to [next].
   String peek(int linesAhead) {
-    if (linesAhead < 0)
+    if (linesAhead < 0) {
       throw new ArgumentError('Invalid linesAhead: $linesAhead; must be >= 0.');
+    }
     // Don't read past the end.
     if (_pos >= lines.length - linesAhead) return null;
     return lines[_pos + linesAhead];
@@ -706,6 +710,75 @@
   const OrderedListSyntax();
 }
 
+/// Parses tables.
+class TableSyntax extends BlockSyntax {
+  static final _pipePattern = new RegExp(r'\s*\|\s*');
+
+  bool get canEndBlock => false;
+
+  const TableSyntax();
+
+  bool canParse(BlockParser parser) {
+    // Note: matches *next* line, not the current one. We're looking for the
+    // bar separating the head row from the body rows.
+    return parser.matchesNext(_tablePattern);
+  }
+
+  /// Parses a table into its three parts:
+  ///
+  /// * a head row of head cells (`<th>` cells)
+  /// * a divider of hyphens and pipes (not rendered)
+  /// * many body rows of body cells (`<td>` cells)
+  Node parse(BlockParser parser) {
+    var alignments = parseAlignments(parser.next);
+    var head = new Element('thead', [parseRow(parser, alignments, 'th')]);
+
+    // Advance past the divider of hyphens.
+    parser.advance();
+
+    var rows = <Element>[];
+    while (!parser.isDone && !parser.matches(_emptyPattern)) {
+      rows.add(parseRow(parser, alignments, 'td'));
+    }
+    var body = new Element('tbody', rows);
+
+    return new Element('table', [head, body]);
+  }
+
+  List<String> parseAlignments(String line) {
+    line = line
+        .replaceFirst(new RegExp(r'^\|'), '')
+        .replaceFirst(new RegExp(r'\|$'), '');
+    return line.split('|').map((column) {
+      if (column.startsWith(':') && column.endsWith(':')) return 'center';
+      if (column.startsWith(':')) return 'left';
+      if (column.endsWith(':')) return 'right';
+      return null;
+    }).toList();
+  }
+
+  Node parseRow(BlockParser parser, List<String> alignments, String cellType) {
+    var line = parser.current
+        .replaceFirst(new RegExp(r'^\|\s*'), '')
+        .replaceFirst(new RegExp(r'\s*\|$'), '');
+    var cells = line.split(_pipePattern);
+    parser.advance();
+    var row = <Element>[];
+
+    for (String cell in cells) {
+      var contents = new UnparsedContent(cell);
+      row.add(new Element(cellType, [contents]));
+    }
+
+    for (var i = 0; i < row.length && i < alignments.length; i++) {
+      if (alignments[i] == null) continue;
+      row[i].attributes['style'] = 'text-align: ${alignments[i]};';
+    }
+
+    return new Element('tr', row);
+  }
+}
+
 /// Parses paragraphs of regular text.
 class ParagraphSyntax extends BlockSyntax {
   static final _reflinkDefinitionStart = new RegExp(r'[ ]{0,3}\[');
diff --git a/pkgs/markdown/test/extensions/tables.unit b/pkgs/markdown/test/extensions/tables.unit
new file mode 100644
index 0000000..c1d6934
--- /dev/null
+++ b/pkgs/markdown/test/extensions/tables.unit
@@ -0,0 +1,63 @@
+>>> basic table
+head | cells
+-----|------
+body | cells
+
+<<<
+<table><thead><tr><th>head</th><th>cells</th></tr></thead><tbody><tr><td>body</td><td>cells</td></tr></tbody></table>
+>>> multiple rows
+head | cells
+-----|------
+body | cells
+more | cells
+
+<<<
+<table><thead><tr><th>head</th><th>cells</th></tr></thead><tbody><tr><td>body</td><td>cells</td></tr><tr><td>more</td><td>cells</td></tr></tbody></table>
+>>> rows wrapped in pipes
+| head | cells |
+|------|-------|
+| body | cells |
+
+<<<
+<table><thead><tr><th>head</th><th>cells</th></tr></thead><tbody><tr><td>body</td><td>cells</td></tr></tbody></table>
+>>> cells with inline syntax
+head `code` | _cells_
+------------|--------
+*text*      | <span>text</span>
+<<<
+<table><thead><tr><th>head <code>code</code></th><th><em>cells</em></th></tr></thead><tbody><tr><td><em>text</em></td><td><span>text</span></td></tr></tbody></table>
+>>> cells are parsed before inline syntax
+header | _foo | bar_
+-------|------------
+text   | text
+<<<
+<table><thead><tr><th>header</th><th>_foo</th><th>bar_</th></tr></thead><tbody><tr><td>text</td><td>text</td></tr></tbody></table>
+>>> cells contain reference links
+header | header
+-------|--------
+text   | [link][here]
+
+[here]: http://url
+<<<
+<table><thead><tr><th>header</th><th>header</th></tr></thead><tbody><tr><td>text</td><td><a href="http://url">link</a></td></tr></tbody></table>
+>>> one column tables
+head
+-----|-----
+body
+<<<
+<table><thead><tr><th>head</th></tr></thead><tbody><tr><td>body</td></tr></tbody></table>
+>>> varying cells per row
+head | foo | bar
+-----|-----
+body
+row with | two cells
+<<<
+<table><thead><tr><th>head</th><th>foo</th><th>bar</th></tr></thead><tbody><tr><td>body</td></tr><tr><td>row with</td><td>two cells</td></tr></tbody></table>
+>>> left, center, and right alignment
+head | cells | here
+:----|:-----:|----:
+body | cells | here
+too | many | cells | here
+
+<<<
+<table><thead><tr><th style="text-align: left;">head</th><th style="text-align: center;">cells</th><th style="text-align: right;">here</th></tr></thead><tbody><tr><td style="text-align: left;">body</td><td style="text-align: center;">cells</td><td style="text-align: right;">here</td></tr><tr><td style="text-align: left;">too</td><td style="text-align: center;">many</td><td style="text-align: right;">cells</td><td>here</td></tr></tbody></table>
diff --git a/pkgs/markdown/test/markdown_test.dart b/pkgs/markdown/test/markdown_test.dart
index 053e481..701ae63 100644
--- a/pkgs/markdown/test/markdown_test.dart
+++ b/pkgs/markdown/test/markdown_test.dart
@@ -148,4 +148,6 @@
 
   testFile('extensions/setext_headers_with_ids.unit',
       blockSyntaxes: [const SetextHeaderWithIdSyntax()]);
+
+  testFile('extensions/tables.unit', blockSyntaxes: [const TableSyntax()]);
 }