gitiles_ext_blocks.py 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. # Copyright 2015 The Chromium Authors. All rights reserved.
  2. # Use of this source code is governed by a BSD-style license that can be
  3. # found in the LICENSE file.
  4. """Implements Gitiles' notification, aside and promotion blocks.
  5. This extention makes the Markdown parser recognize the Gitiles' extended
  6. blocks notation. The syntax is explained at:
  7. https://gerrit.googlesource.com/gitiles/+/master/Documentation/markdown.md#Notification_aside_promotion-blocks
  8. """
  9. from markdown.blockprocessors import BlockProcessor
  10. from markdown.extensions import Extension
  11. from markdown.util import etree
  12. import re
  13. class _GitilesExtBlockProcessor(BlockProcessor):
  14. """Process Gitiles' notification, aside and promotion blocks."""
  15. RE_START = re.compile(r'^\*\*\* (note|aside|promo) *\n')
  16. RE_END = re.compile(r'\n\*\*\* *\n?$')
  17. def __init__(self, *args, **kwargs):
  18. self._last_parent = None
  19. BlockProcessor.__init__(self, *args, **kwargs)
  20. def test(self, parent, block):
  21. return self.RE_START.search(block) or self.RE_END.search(block)
  22. def run(self, parent, blocks):
  23. raw_block = blocks.pop(0)
  24. match_start = self.RE_START.search(raw_block)
  25. if match_start:
  26. # Opening a new block.
  27. rest = raw_block[match_start.end():]
  28. if self._last_parent:
  29. # Inconsistent state (nested starting markers). Ignore the marker
  30. # and keep going.
  31. blocks.insert(0, rest)
  32. return
  33. div = etree.SubElement(parent, 'div')
  34. # Setting the class name is sufficient, because doc.css already has
  35. # styles for these classes.
  36. div.set('class', match_start.group(1))
  37. self._last_parent = parent
  38. blocks.insert(0, rest)
  39. self.parser.parseBlocks(div, blocks)
  40. return
  41. match_end = self.RE_END.search(raw_block)
  42. if match_end:
  43. # Ending an existing block.
  44. # Process the text preceding the ending marker in the current context
  45. # (i.e. within the div block).
  46. rest = raw_block[:match_end.start()]
  47. self.parser.parseBlocks(parent, [rest])
  48. if not self._last_parent:
  49. # Inconsistent state (the ending marker is found but there is no
  50. # matching starting marker).
  51. # Let's continue as if we did not see the ending marker.
  52. return
  53. last_parent = self._last_parent
  54. self._last_parent = None
  55. self.parser.parseBlocks(last_parent, blocks)
  56. return
  57. class _GitilesExtBlockExtension(Extension):
  58. """Add Gitiles' extended blocks to Markdown."""
  59. def extendMarkdown(self, md):
  60. md.parser.blockprocessors.add('gitilesextblocks',
  61. _GitilesExtBlockProcessor(md.parser),
  62. '_begin')
  63. def makeExtension(*args, **kwargs):
  64. return _GitilesExtBlockExtension(*args, **kwargs)