inlinehilite.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. """
  2. Inline Highlighting.
  3. pymdownx.inlinehilite
  4. An alternative inline code extension that highlights code. Can
  5. use CodeHilite to source its settings or pymdownx.highlight.
  6. `:::javascript var test = 0;`
  7. - or -
  8. `#!javascript var test = 0;`
  9. Copyright 2014 - 2017 Isaac Muse <isaacmuse@gmail.com>
  10. """
  11. from markdown import Extension
  12. from markdown.inlinepatterns import InlineProcessor
  13. from markdown import util as md_util
  14. import xml.etree.ElementTree as etree
  15. import functools
  16. ESCAPED_BSLASH = '%s%s%s' % (md_util.STX, ord('\\'), md_util.ETX)
  17. DOUBLE_BSLASH = '\\\\'
  18. BACKTICK_CODE_RE = r'''(?x)
  19. (?:
  20. (?<!\\)(?P<escapes>(?:\\{2})+)(?=`+) | # Process code escapes before code
  21. (?<!\\)(?P<tic>`+)
  22. ((?:\:{3,}|\#!)(?P<lang>[\w#.+-]*)\s+)? # Optional language
  23. (?P<code>.+?) # Code
  24. (?<!`)(?P=tic)(?!`) # Closing
  25. )
  26. '''
  27. def _escape(txt):
  28. """Basic html escaping."""
  29. txt = txt.replace('&', '&amp;')
  30. txt = txt.replace('<', '&lt;')
  31. txt = txt.replace('>', '&gt;')
  32. return txt
  33. def _test(language, test_language=None):
  34. """Test language."""
  35. return test_language is None or test_language == '*' or language == test_language
  36. def _formatter(source, language, md, class_name="", fmt=None):
  37. """Formatter wrapper."""
  38. return fmt(source, language, class_name, md)
  39. class InlineHilitePattern(InlineProcessor):
  40. """Handle the inline code patterns."""
  41. def __init__(self, pattern, config, md):
  42. """Initialize."""
  43. self.config = config
  44. InlineProcessor.__init__(self, pattern, md)
  45. self.md = md
  46. self.formatters = [
  47. {
  48. "name": "inlinehilite",
  49. "test": _test,
  50. "formatter": self.highlight_code
  51. }
  52. ]
  53. # Custom Fences
  54. custom_inline = self.config.get('custom_inline', [])
  55. for custom in custom_inline:
  56. name = custom.get('name')
  57. class_name = custom.get('class')
  58. inline_format = custom.get('format', self.highlight_code)
  59. if name is not None and class_name is not None:
  60. self.extend_custom_inline(
  61. name,
  62. functools.partial(_formatter, class_name=class_name, fmt=inline_format)
  63. )
  64. self.get_hl_settings = False
  65. def extend_custom_inline(self, name, formatter):
  66. """Extend SuperFences with the given name, language, and formatter."""
  67. obj = {
  68. "name": name,
  69. "test": functools.partial(_test, test_language=name),
  70. "formatter": formatter
  71. }
  72. if name == '*':
  73. self.formatters[0] = obj
  74. else:
  75. self.formatters.append(obj)
  76. def get_settings(self):
  77. """Check for Highlight extension settings."""
  78. if not self.get_hl_settings:
  79. self.get_hl_settings = True
  80. self.style_plain_text = self.config['style_plain_text']
  81. config = None
  82. self.highlighter = None
  83. for ext in self.md.registeredExtensions:
  84. try:
  85. config = getattr(ext, "get_pymdownx_highlight_settings")()
  86. self.highlighter = getattr(ext, "get_pymdownx_highlighter")()
  87. break
  88. except AttributeError:
  89. pass
  90. css_class = self.config['css_class']
  91. self.css_class = css_class if css_class else config['css_class']
  92. self.extend_pygments_lang = config.get('extend_pygments_lang', None)
  93. self.guess_lang = config['guess_lang']
  94. self.pygments_style = config['pygments_style']
  95. self.use_pygments = config['use_pygments']
  96. self.noclasses = config['noclasses']
  97. def highlight_code(self, src, language, classname=None, md=None):
  98. """Syntax highlight the inline code block."""
  99. process_text = self.style_plain_text or language or self.guess_lang
  100. if process_text:
  101. el = self.highlighter(
  102. guess_lang=self.guess_lang,
  103. pygments_style=self.pygments_style,
  104. use_pygments=self.use_pygments,
  105. noclasses=self.noclasses,
  106. extend_pygments_lang=self.extend_pygments_lang
  107. ).highlight(src, language, self.css_class, inline=True)
  108. el.text = self.md.htmlStash.store(el.text)
  109. else:
  110. el = etree.Element('code')
  111. el.text = self.md.htmlStash.store(_escape(src))
  112. return el
  113. def handle_code(self, lang, src):
  114. """Handle code block."""
  115. for entry in reversed(self.formatters):
  116. if entry["test"](lang):
  117. value = entry["formatter"](src, lang, self.md)
  118. if isinstance(value, str):
  119. value = self.md.htmlStash.store(value)
  120. return value
  121. def handleMatch(self, m, data):
  122. """Handle the pattern match."""
  123. if m.group('escapes'):
  124. return m.group('escapes').replace(DOUBLE_BSLASH, ESCAPED_BSLASH), m.start(0), m.end(0)
  125. else:
  126. lang = m.group('lang') if m.group('lang') else ''
  127. src = m.group('code').strip()
  128. self.get_settings()
  129. return self.handle_code(lang, src), m.start(0), m.end(0)
  130. class InlineHiliteExtension(Extension):
  131. """Add inline highlighting extension to Markdown class."""
  132. def __init__(self, *args, **kwargs):
  133. """Initialize."""
  134. self.inlinehilite = []
  135. self.config = {
  136. 'style_plain_text': [
  137. False,
  138. "Process inline code even when a language is not specified "
  139. "or langauge is specified as 'text'. "
  140. "When 'False', no classes will be added to 'text' code blocks"
  141. "and no scoping will performed. The content will just be escaped."
  142. "- Default: False"
  143. ],
  144. 'css_class': [
  145. '',
  146. "Set class name for wrapper element. The default of Highlight will be used"
  147. "if nothing is set. - "
  148. "Default: ''"
  149. ],
  150. 'custom_inline': [[], "Custom inline - default []"]
  151. }
  152. super(InlineHiliteExtension, self).__init__(*args, **kwargs)
  153. def extendMarkdown(self, md):
  154. """Add support for `:::language code` and `#!language code` highlighting."""
  155. config = self.getConfigs()
  156. md.inlinePatterns.register(InlineHilitePattern(BACKTICK_CODE_RE, config, md), "backtick", 190)
  157. md.registerExtensions(["pymdownx.highlight"], {"pymdownx.highlight": {"_enabled": False}})
  158. def makeExtension(*args, **kwargs):
  159. """Return extension."""
  160. return InlineHiliteExtension(*args, **kwargs)