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