code.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import re
  2. import markdown
  3. from markdown.extensions.attr_list import AttrListExtension
  4. from markdown.extensions.fenced_code import FencedBlockPreprocessor
  5. from markdown.extensions.codehilite import CodeHilite, CodeHiliteExtension
  6. from markdown.serializers import _escape_attrib_html
  7. class CodeBlockExtension(markdown.Extension):
  8. def extendMarkdown(self, md):
  9. md.registerExtension(self)
  10. md.preprocessors.register(
  11. CodeBlockPreprocessor(md, self.getConfigs()), "misago_code_bbcode", 24
  12. )
  13. class CodeBlockPreprocessor(FencedBlockPreprocessor):
  14. FENCED_BLOCK_RE = re.compile(
  15. r"""
  16. \[code(=("?)(?P<lang>.*?)("?))?](([ ]*\n)+)?(?P<code>.*?)((\s|\n)+)?\[/code\]
  17. """,
  18. re.IGNORECASE | re.MULTILINE | re.DOTALL | re.VERBOSE,
  19. )
  20. def run(self, lines):
  21. """Match and store Fenced Code Blocks in the HtmlStash."""
  22. # Check for dependent extensions
  23. if not self.checked_for_deps:
  24. for ext in self.md.registeredExtensions:
  25. if isinstance(ext, CodeHiliteExtension):
  26. self.codehilite_conf = ext.getConfigs()
  27. if isinstance(ext, AttrListExtension):
  28. self.use_attr_list = True
  29. self.checked_for_deps = True
  30. text = "\n".join(lines)
  31. while 1:
  32. m = self.FENCED_BLOCK_RE.search(text)
  33. if m:
  34. lang, id, classes, config = None, "", [], {}
  35. if m.group("lang"):
  36. lang = m.group("lang")
  37. # If config is not empty, then the codehighlite extension
  38. # is enabled, so we call it to highlight the code
  39. if (
  40. self.codehilite_conf
  41. and self.codehilite_conf["use_pygments"]
  42. and config.get("use_pygments", True)
  43. ):
  44. local_config = self.codehilite_conf.copy()
  45. local_config.update(config)
  46. # Combine classes with cssclass. Ensure cssclass is at end
  47. # as pygments appends a suffix under certain circumstances.
  48. # Ignore ID as Pygments does not offer an option to set it.
  49. if classes:
  50. local_config["css_class"] = "{} {}".format(
  51. " ".join(classes), local_config["css_class"]
  52. )
  53. highliter = CodeHilite(
  54. m.group("code"),
  55. lang=lang,
  56. style=local_config.pop("pygments_style", "default"),
  57. **local_config,
  58. )
  59. code = highliter.hilite(shebang=False)
  60. else:
  61. id_attr = lang_attr = class_attr = kv_pairs = ""
  62. if lang:
  63. lang_attr = f' class="{_escape_attrib_html(lang)}"'
  64. if classes:
  65. class_attr = (
  66. f' class="{_escape_attrib_html(" ".join(classes))}"'
  67. )
  68. if id:
  69. id_attr = f' id="{_escape_attrib_html(id)}"'
  70. if (
  71. self.use_attr_list
  72. and config
  73. and not config.get("use_pygments", False)
  74. ):
  75. # Only assign key/value pairs to code element if attr_list ext is enabled, key/value pairs
  76. # were defined on the code block, and the `use_pygments` key was not set to True. The
  77. # `use_pygments` key could be either set to False or not defined. It is omitted from output.
  78. kv_pairs = "".join(
  79. f' {k}="{_escape_attrib_html(v)}"'
  80. for k, v in config.items()
  81. if k != "use_pygments"
  82. )
  83. code = self._escape(m.group("code"))
  84. code = f"<pre{id_attr}{class_attr}><code{lang_attr}{kv_pairs}>{code}</code></pre>"
  85. placeholder = self.md.htmlStash.store(code)
  86. text = f"{text[:m.start()]}\n{placeholder}\n{text[m.end():]}"
  87. else:
  88. break
  89. return text.split("\n")