markup.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. # -*- coding: utf-8 -*-
  2. """
  3. flaskbb.utils.markup
  4. ~~~~~~~~~~~~~~~~~~~~
  5. A module for all markup related stuff.
  6. :copyright: (c) 2016 by the FlaskBB Team.
  7. :license: BSD, see LICENSE for more details.
  8. """
  9. import logging
  10. import mistune
  11. from mistune.plugins import plugin_strikethrough, plugin_url
  12. from flask import url_for
  13. from markupsafe import Markup
  14. from pluggy import HookimplMarker
  15. from pygments import highlight
  16. from pygments.formatters import HtmlFormatter
  17. from pygments.lexers import get_lexer_by_name
  18. from pygments.util import ClassNotFound
  19. impl = HookimplMarker('flaskbb')
  20. logger = logging.getLogger(__name__)
  21. _re_user = r'(?i)@(\w+)'
  22. def parse_user_link(inline, match, state):
  23. url = url_for("user.profile", username=match.group(1), _external=False)
  24. return 'link', url, match.group(0)
  25. def plugin_userify(md):
  26. """Mistune plugin that transforms @username references to links."""
  27. md.inline.register_rule('flaskbb_user_link', _re_user, parse_user_link)
  28. md.inline.rules.append('flaskbb_user_link')
  29. DEFAULT_PLUGINS = [plugin_url, plugin_strikethrough, plugin_userify]
  30. class FlaskBBRenderer(mistune.HTMLRenderer):
  31. """Mistune renderer that uses pygments to apply code highlighting."""
  32. def __init__(self, **kwargs):
  33. super(FlaskBBRenderer, self).__init__(**kwargs)
  34. def block_code(self, code, lang=None):
  35. if lang:
  36. try:
  37. lexer = get_lexer_by_name(lang, stripall=True)
  38. except ClassNotFound:
  39. lexer = None
  40. else:
  41. lexer = None
  42. if not lexer:
  43. return '\n<pre><code>%s</code></pre>\n' % \
  44. mistune.escape(code)
  45. formatter = HtmlFormatter()
  46. return highlight(code, lexer, formatter)
  47. @impl
  48. def flaskbb_load_post_markdown_class():
  49. return FlaskBBRenderer
  50. @impl
  51. def flaskbb_load_nonpost_markdown_class():
  52. return FlaskBBRenderer
  53. @impl
  54. def flaskbb_jinja_directives(app):
  55. render_classes = app.pluggy.hook.flaskbb_load_post_markdown_class(app=app)
  56. plugins = DEFAULT_PLUGINS[:]
  57. app.pluggy.hook.flaskbb_load_post_markdown_plugins(
  58. plugins=plugins,
  59. app=app
  60. )
  61. app.jinja_env.filters['markup'] = make_renderer(render_classes, plugins)
  62. render_classes = app.pluggy.hook.flaskbb_load_nonpost_markdown_class(
  63. app=app
  64. )
  65. plugins = DEFAULT_PLUGINS[:]
  66. plugins = app.pluggy.hook.flaskbb_load_nonpost_markdown_plugins(
  67. plugins=plugins,
  68. app=app
  69. )
  70. app.jinja_env.filters['nonpost_markup'] = make_renderer(
  71. render_classes,
  72. plugins
  73. )
  74. def make_renderer(classes, plugins):
  75. RenderCls = type('FlaskBBRenderer', tuple(classes), {})
  76. markup = mistune.create_markdown(
  77. renderer=RenderCls(),
  78. plugins=plugins,
  79. escape=True,
  80. hard_wrap=True
  81. )
  82. return lambda text: Markup(markup(text))