markup.py 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  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 re
  11. import mistune
  12. from flask import url_for
  13. from jinja2 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 = re.compile(r'@(\w+)', re.I)
  22. def userify(match):
  23. value = match.group(1)
  24. user = "<a href='{url}'>@{user}</a>".format(
  25. url=url_for("user.profile", username=value, _external=False),
  26. user=value
  27. )
  28. return user
  29. class FlaskBBRenderer(mistune.Renderer):
  30. """Markdown with some syntactic sugar, such as @user gettting linked
  31. to the user's profile.
  32. """
  33. def __init__(self, **kwargs):
  34. super(FlaskBBRenderer, self).__init__(**kwargs)
  35. def paragraph(self, text):
  36. """Render paragraph tags, autolinking user handles."""
  37. text = _re_user.sub(userify, text)
  38. return super(FlaskBBRenderer, self).paragraph(text)
  39. def block_code(self, code, lang):
  40. if lang:
  41. try:
  42. lexer = get_lexer_by_name(lang, stripall=True)
  43. except ClassNotFound:
  44. lexer = None
  45. else:
  46. lexer = None
  47. if not lexer:
  48. return '\n<pre><code>%s</code></pre>\n' % \
  49. mistune.escape(code)
  50. formatter = HtmlFormatter()
  51. return highlight(code, lexer, formatter)
  52. @impl
  53. def flaskbb_load_post_markdown_class():
  54. return FlaskBBRenderer
  55. @impl
  56. def flaskbb_load_nonpost_markdown_class():
  57. return FlaskBBRenderer
  58. @impl
  59. def flaskbb_jinja_directives(app):
  60. render_classes = app.pluggy.hook.flaskbb_load_post_markdown_class(app=app)
  61. app.jinja_env.filters['markup'] = make_renderer(render_classes)
  62. render_classes = app.pluggy.hook.flaskbb_load_nonpost_markdown_class(
  63. app=app
  64. )
  65. app.jinja_env.filters['nonpost_markup'] = make_renderer(render_classes)
  66. def make_renderer(classes):
  67. RenderCls = type('FlaskBBRenderer', tuple(classes), {})
  68. markup = mistune.Markdown(renderer=RenderCls(escape=True, hard_wrap=True))
  69. return lambda text: Markup(markup.render(text))