Index: tools/md_browser/gitiles_ext_blocks.py |
diff --git a/tools/md_browser/gitiles_ext_blocks.py b/tools/md_browser/gitiles_ext_blocks.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b1a53795e1750d12737e6a32144106c9d9ba5cac |
--- /dev/null |
+++ b/tools/md_browser/gitiles_ext_blocks.py |
@@ -0,0 +1,84 @@ |
+# Copyright 2015 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+"""Implements Gitiles' notification, aside and promotion blocks. |
+ |
+This extention makes the Markdown parser recognize the Gitiles' extended |
+blocks notation. The syntax is explained at: |
+ |
+https://gerrit.googlesource.com/gitiles/+/master/Documentation/markdown.md#Notification_aside_promotion-blocks |
+""" |
+ |
+from markdown.blockprocessors import BlockProcessor |
+from markdown.extensions import Extension |
+from markdown.util import etree |
+import re |
+ |
+ |
+class _GitilesExtBlockProcessor(BlockProcessor): |
+ """Process Gitiles' notification, aside and promotion blocks.""" |
+ |
+ RE_START = re.compile(r'^\*\*\* (note|aside|promo) *\n') |
+ RE_END = re.compile(r'\n\*\*\* *\n?$') |
+ |
+ def __init__(self, *args, **kwargs): |
+ self._last_parent = None |
+ BlockProcessor.__init__(self, *args, **kwargs) |
+ |
+ def test(self, parent, block): |
+ return self.RE_START.search(block) or self.RE_END.search(block) |
+ |
+ def run(self, parent, blocks): |
+ raw_block = blocks.pop(0) |
+ match_start = self.RE_START.search(raw_block) |
+ if match_start: |
+ # Opening a new block. |
+ rest = raw_block[match_start.end():] |
+ |
+ if self._last_parent: |
+ # Inconsistent state (nested starting markers). Ignore the marker |
+ # and keep going. |
+ blocks.insert(0, rest) |
+ return |
+ |
+ div = etree.SubElement(parent, 'div') |
+ # Setting the class name is sufficient, because doc.css already has |
+ # styles for these classes. |
+ div.set('class', match_start.group(1)) |
+ self._last_parent = parent |
+ blocks.insert(0, rest) |
+ self.parser.parseBlocks(div, blocks) |
+ return |
+ |
+ match_end = self.RE_END.search(raw_block) |
+ if match_end: |
+ # Ending an existing block. |
+ |
+ # Process the text preceding the ending marker in the current context |
+ # (i.e. within the div block). |
+ rest = raw_block[:match_end.start()] |
+ self.parser.parseBlocks(parent, [rest]) |
+ |
+ if not self._last_parent: |
+ # Inconsistent state (the ending marker is found but there is no |
+ # matching starting marker). |
+ # Let's continue as if we did not see the ending marker. |
+ return |
+ |
+ last_parent = self._last_parent |
+ self._last_parent = None |
+ self.parser.parseBlocks(last_parent, blocks) |
+ return |
+ |
+ |
+class _GitilesExtBlockExtension(Extension): |
+ """Add Gitiles' extended blocks to Markdown.""" |
+ def extendMarkdown(self, md, md_globals): |
+ md.parser.blockprocessors.add('gitilesextblocks', |
+ _GitilesExtBlockProcessor(md.parser), |
+ '_begin') |
+ |
+ |
+def makeExtension(*args, **kwargs): |
+ return _GitilesExtBlockExtension(*args, **kwargs) |