dpaste/dpaste/highlight.py

208 lines
6 KiB
Python
Raw Permalink Normal View History

2018-03-12 23:04:18 +11:00
from logging import getLogger
2018-06-22 20:37:04 +10:00
from django.apps import apps
from django.template.defaultfilters import escape, linebreaksbr
from django.template.loader import render_to_string
2018-04-06 05:18:22 +10:00
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
2018-03-12 23:04:18 +11:00
from pygments import highlight
from pygments.formatters.html import HtmlFormatter
from pygments.lexers import get_lexer_by_name
from pygments.lexers.python import PythonLexer
from pygments.util import ClassNotFound
logger = getLogger(__file__)
config = apps.get_app_config("dpaste")
2018-03-12 23:04:18 +11:00
# -----------------------------------------------------------------------------
# Highlight Code Snippets
# -----------------------------------------------------------------------------
class Highlighter(object):
template_name = "dpaste/highlight/code.html"
def highlight(self, code_string, lexer_name=None):
"""Subclasses need to override this."""
return code_string
@staticmethod
def get_lexer_display_name(lexer_name, fallback=_("(Deprecated Lexer)")):
2018-06-22 20:37:04 +10:00
for l in config.TEXT_FORMATTER + config.CODE_FORMATTER:
if l[0] == lexer_name:
return l[1]
return fallback
2018-12-20 01:03:15 +11:00
def render(self, code_string, lexer_name, direction=None, **kwargs):
highlighted_string = self.highlight(code_string, lexer_name=lexer_name)
context = {
"highlighted": highlighted_string,
"highlighted_splitted": highlighted_string.splitlines(),
"lexer_name": lexer_name,
"lexer_display_name": self.get_lexer_display_name(lexer_name),
"direction": direction,
}
context.update(kwargs)
return render_to_string(self.template_name, context)
2018-03-12 23:04:18 +11:00
class PlainTextHighlighter(Highlighter):
"""Plain Text. Just replace linebreaks."""
template_name = "dpaste/highlight/text.html"
def highlight(self, code_string, **kwargs):
return linebreaksbr(code_string)
2018-04-06 05:18:22 +10:00
class MarkdownHighlighter(PlainTextHighlighter):
"""Markdown"""
extensions = (
"tables",
"fenced-code",
"footnotes",
"autolink,",
"strikethrough",
"underline",
"quote",
"superscript",
"math",
)
render_flags = ("skip-html",)
2018-04-06 05:18:22 +10:00
def highlight(self, code_string, **kwargs):
2018-04-06 05:18:22 +10:00
import misaka
return mark_safe(
misaka.html(
2021-12-14 18:39:08 +11:00
code_string,
extensions=self.extensions,
render_flags=self.render_flags,
)
)
2018-04-06 05:18:22 +10:00
2018-04-06 05:47:29 +10:00
class RestructuredTextHighlighter(PlainTextHighlighter):
"""Restructured Text"""
rst_part_name = "html_body"
2018-04-06 05:47:29 +10:00
publish_args = {
"writer_name": "html5_polyglot",
"settings_overrides": {
"raw_enabled": False,
"file_insertion_enabled": False,
"halt_level": 5,
"report_level": 2,
"warning_stream": "/dev/null",
},
2018-04-06 05:47:29 +10:00
}
def highlight(self, code_string, **kwargs):
2018-04-06 05:47:29 +10:00
from docutils.core import publish_parts
self.publish_args["source"] = code_string
2018-04-06 05:47:29 +10:00
parts = publish_parts(**self.publish_args)
return mark_safe(parts[self.rst_part_name])
2018-04-07 17:14:43 +10:00
2018-04-06 05:18:22 +10:00
# -----------------------------------------------------------------------------
2018-06-23 22:31:19 +10:00
class NakedHtmlFormatter(HtmlFormatter):
"""Pygments HTML formatter with no further HTML tags."""
2018-06-23 22:31:19 +10:00
def wrap(self, source, outfile):
return self._wrap_code(source)
2018-06-23 22:31:19 +10:00
def _wrap_code(self, source):
2018-12-12 04:56:30 +11:00
yield from source
2018-06-23 22:31:19 +10:00
class PlainCodeHighlighter(Highlighter):
"""
Plain Code. No highlighting but Pygments like span tags around each line.
"""
def highlight(self, code_string, **kwargs):
return "\n".join(
[
'<span class="plain">{}</span>'.format(escape(l) or "&#8203;")
2018-06-23 22:31:19 +10:00
for l in code_string.splitlines()
]
)
class PygmentsHighlighter(Highlighter):
"""
2018-06-22 20:37:04 +10:00
Highlight code string with Pygments. The lexer is automatically
determined by the lexer name.
"""
2018-04-07 17:14:43 +10:00
formatter = NakedHtmlFormatter()
2018-04-06 03:34:26 +10:00
lexer = None
lexer_fallback = PythonLexer()
def highlight(self, code_string, lexer_name):
2018-04-06 03:34:26 +10:00
if not self.lexer:
try:
self.lexer = get_lexer_by_name(lexer_name)
except ClassNotFound:
logger.warning("Lexer for given name %s not found", lexer_name)
2018-04-06 03:34:26 +10:00
self.lexer = self.lexer_fallback
2018-04-07 17:14:43 +10:00
return highlight(code_string, self.lexer, self.formatter)
2018-04-06 03:34:26 +10:00
class SolidityHighlighter(PygmentsHighlighter):
"""Solidity Specific Highlighter. This uses a 3rd party Pygments lexer."""
def __init__(self):
2018-04-06 03:37:33 +10:00
# SolidityLexer does not necessarily need to be installed
# since its imported here and not used later.
2018-04-06 03:34:26 +10:00
from pygments_lexer_solidity import SolidityLexer
2018-04-06 03:34:26 +10:00
self.lexer = SolidityLexer()
2018-03-12 23:04:18 +11:00
2017-09-13 17:26:14 +10:00
def get_highlighter_class(lexer_name):
"""
Get Highlighter for lexer name.
2018-03-12 23:04:18 +11:00
If the found lexer tuple does not provide a Highlighter class,
use the generic Pygments highlighter.
2018-03-12 23:04:18 +11:00
If no suitable highlighter is found, return the generic
PlainCode Highlighter.
"""
2018-06-22 20:37:04 +10:00
for c in config.TEXT_FORMATTER + config.CODE_FORMATTER:
if c[0] == lexer_name:
if len(c) == 3:
return c[2]
return PygmentsHighlighter
return PlainCodeHighlighter
# -----------------------------------------------------------------------------
# Lexer List
# -----------------------------------------------------------------------------
2018-06-22 20:37:04 +10:00
# Generate a list of Form choices of all lexer.
LEXER_CHOICES = (
(_("Text"), [i[:2] for i in config.TEXT_FORMATTER]),
(_("Code"), [i[:2] for i in config.CODE_FORMATTER]),
)
# List of all Lexer Keys
LEXER_KEYS = [i[0] for i in config.TEXT_FORMATTER] + [
i[0] for i in config.CODE_FORMATTER
]
# The default lexer which we fallback in case of
# an error or if not supplied in an API call.
2018-06-22 20:37:04 +10:00
LEXER_DEFAULT = config.LEXER_DEFAULT
# Lexers which have wordwrap enabled by default
2018-06-22 20:37:04 +10:00
LEXER_WORDWRAP = config.LEXER_WORDWRAP