|  | # 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) |