dpaste/dpaste/highlight.py
2021-12-14 08:39:08 +01:00

207 lines
6 KiB
Python

from logging import getLogger
from django.apps import apps
from django.template.defaultfilters import escape, linebreaksbr
from django.template.loader import render_to_string
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
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")
# -----------------------------------------------------------------------------
# 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)")):
for l in config.TEXT_FORMATTER + config.CODE_FORMATTER:
if l[0] == lexer_name:
return l[1]
return fallback
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)
class PlainTextHighlighter(Highlighter):
"""Plain Text. Just replace linebreaks."""
template_name = "dpaste/highlight/text.html"
def highlight(self, code_string, **kwargs):
return linebreaksbr(code_string)
class MarkdownHighlighter(PlainTextHighlighter):
"""Markdown"""
extensions = (
"tables",
"fenced-code",
"footnotes",
"autolink,",
"strikethrough",
"underline",
"quote",
"superscript",
"math",
)
render_flags = ("skip-html",)
def highlight(self, code_string, **kwargs):
import misaka
return mark_safe(
misaka.html(
code_string,
extensions=self.extensions,
render_flags=self.render_flags,
)
)
class RestructuredTextHighlighter(PlainTextHighlighter):
"""Restructured Text"""
rst_part_name = "html_body"
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",
},
}
def highlight(self, code_string, **kwargs):
from docutils.core import publish_parts
self.publish_args["source"] = code_string
parts = publish_parts(**self.publish_args)
return mark_safe(parts[self.rst_part_name])
# -----------------------------------------------------------------------------
class NakedHtmlFormatter(HtmlFormatter):
"""Pygments HTML formatter with no further HTML tags."""
def wrap(self, source, outfile):
return self._wrap_code(source)
def _wrap_code(self, source):
yield from source
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;")
for l in code_string.splitlines()
]
)
class PygmentsHighlighter(Highlighter):
"""
Highlight code string with Pygments. The lexer is automatically
determined by the lexer name.
"""
formatter = NakedHtmlFormatter()
lexer = None
lexer_fallback = PythonLexer()
def highlight(self, code_string, lexer_name):
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)
self.lexer = self.lexer_fallback
return highlight(code_string, self.lexer, self.formatter)
class SolidityHighlighter(PygmentsHighlighter):
"""Solidity Specific Highlighter. This uses a 3rd party Pygments lexer."""
def __init__(self):
# SolidityLexer does not necessarily need to be installed
# since its imported here and not used later.
from pygments_lexer_solidity import SolidityLexer
self.lexer = SolidityLexer()
def get_highlighter_class(lexer_name):
"""
Get Highlighter for lexer name.
If the found lexer tuple does not provide a Highlighter class,
use the generic Pygments highlighter.
If no suitable highlighter is found, return the generic
PlainCode Highlighter.
"""
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
# -----------------------------------------------------------------------------
# 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.
LEXER_DEFAULT = config.LEXER_DEFAULT
# Lexers which have wordwrap enabled by default
LEXER_WORDWRAP = config.LEXER_WORDWRAP