summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShrikant Sharat Kandula <shrikantsharat.k@gmail.com>2022-05-09 18:26:10 +0530
committerGitHub <noreply@github.com>2022-05-09 08:56:10 -0400
commit9d90d47157f4348ad70f1a070571de87efbc6cbc (patch)
tree6f412828fe645bda6b2557cc5c531ed40bc3ab6d
parent93d17b9cc4a3f53dc2059e44a691a28797422d21 (diff)
downloadpython-markdown-9d90d47157f4348ad70f1a070571de87efbc6cbc.tar.gz
Support for custom Pygments formatter
This adds configuration support for using a custom Pygments formatter, either by giving the string name, or a custom formatter class (or callable).
-rw-r--r--.spell-dict1
-rw-r--r--docs/change_log/release-3.4.md8
-rw-r--r--docs/extensions/code_hilite.md10
-rw-r--r--markdown/extensions/codehilite.py16
-rw-r--r--tests/test_syntax/extensions/test_fenced_code.py130
5 files changed, 161 insertions, 4 deletions
diff --git a/.spell-dict b/.spell-dict
index fbe4865..6408100 100644
--- a/.spell-dict
+++ b/.spell-dict
@@ -160,3 +160,4 @@ plugin
plugins
configs
pre
+formatters
diff --git a/docs/change_log/release-3.4.md b/docs/change_log/release-3.4.md
index 025d443..6dc8401 100644
--- a/docs/change_log/release-3.4.md
+++ b/docs/change_log/release-3.4.md
@@ -32,7 +32,7 @@ In addition, tests were moved to the modern test environment.
## New features
-The following new features have been included in the 3.3 release:
+The following new features have been included in the 3.4 release:
* Use `style` attribute in tables for alignment instead of `align` for better CSS
inter-operation. The old behavior is available by setting `use_align_attribute=True` when
@@ -55,6 +55,12 @@ The following new features have been included in the 3.3 release:
parameter which can be used to set the CSS class(es) on the `<div>` that contains the
Table of Contents (#1224).
+* The Codehilite extension now supports a `pygments_formatter` option that can be set to
+ use a custom formatter class with Pygments.
+ - If set to a string like `'html'`, we get the default formatter by that name.
+ - If set to a class (or any callable), it is called with all the options to get a
+ formatter instance.
+
## Bug fixes
The following bug fixes are included in the 3.4 release:
diff --git a/docs/extensions/code_hilite.md b/docs/extensions/code_hilite.md
index caaf011..6fa6190 100644
--- a/docs/extensions/code_hilite.md
+++ b/docs/extensions/code_hilite.md
@@ -234,6 +234,15 @@ The following options are provided to configure the output:
This option only applies when `use_pygments` is `False` as Pygments does not provide an option to include a
language prefix.
+* **`pygments_formatter`**{ #pygments_formatter }:
+ This option can be used to change the Pygments formatter used for highlighting the code blocks. By default, this
+ is set to the string `'html'`, which means it'll use the default `HtmlFormatter` provided by Pygments.
+
+ This can be set to a string representing any of the other default formatters, or set to a formatter class (or
+ any callable).
+
+ To see what formatters are available and how to subclass an existing formatter, please visit [Pygments
+ documentation on this topic][pygments formatters].
* Any other Pygments' options:
@@ -250,3 +259,4 @@ markdown.markdown(some_text, extensions=['codehilite'])
[html formatter]: https://pygments.org/docs/formatters/#HtmlFormatter
[lexer]: https://pygments.org/docs/lexers/
[spec]: https://www.w3.org/TR/html5/text-level-semantics.html#the-code-element
+[pygments formatters]: https://pygments.org/docs/formatters/
diff --git a/markdown/extensions/codehilite.py b/markdown/extensions/codehilite.py
index a768b73..b92ebdc 100644
--- a/markdown/extensions/codehilite.py
+++ b/markdown/extensions/codehilite.py
@@ -23,6 +23,7 @@ try: # pragma: no cover
from pygments import highlight
from pygments.lexers import get_lexer_by_name, guess_lexer
from pygments.formatters import get_formatter_by_name
+ from pygments.util import ClassNotFound
pygments = True
except ImportError: # pragma: no cover
pygments = False
@@ -99,6 +100,7 @@ class CodeHilite:
self.guess_lang = options.pop('guess_lang', True)
self.use_pygments = options.pop('use_pygments', True)
self.lang_prefix = options.pop('lang_prefix', 'language-')
+ self.pygments_formatter = options.pop('pygments_formatter', 'html')
if 'linenos' not in options:
options['linenos'] = options.pop('linenums', None)
@@ -139,7 +141,13 @@ class CodeHilite:
lexer = get_lexer_by_name('text', **self.options)
except ValueError: # pragma: no cover
lexer = get_lexer_by_name('text', **self.options)
- formatter = get_formatter_by_name('html', **self.options)
+ if isinstance(self.pygments_formatter, str):
+ try:
+ formatter = get_formatter_by_name(self.pygments_formatter, **self.options)
+ except ClassNotFound:
+ formatter = get_formatter_by_name('html', **self.options)
+ else:
+ formatter = self.pygments_formatter(**self.options)
return highlight(self.src, lexer, formatter)
else:
# just escape and build markup usable by JS highlighting libs
@@ -279,7 +287,11 @@ class CodeHiliteExtension(Extension):
'lang_prefix': [
'language-',
'Prefix prepended to the language when use_pygments is false. Default: "language-"'
- ]
+ ],
+ 'pygments_formatter': ['html',
+ 'Use a specific formatter for Pygments hilighting.'
+ 'Default: "html"',
+ ],
}
for key, value in kwargs.items():
diff --git a/tests/test_syntax/extensions/test_fenced_code.py b/tests/test_syntax/extensions/test_fenced_code.py
index 2cdde98..f8c3e91 100644
--- a/tests/test_syntax/extensions/test_fenced_code.py
+++ b/tests/test_syntax/extensions/test_fenced_code.py
@@ -21,10 +21,12 @@ License: BSD (see LICENSE.md for details).
from markdown.test_tools import TestCase
import markdown
+import markdown.extensions.codehilite
import os
try:
import pygments # noqa
+ import pygments.formatters # noqa
has_pygments = True
except ImportError:
has_pygments = False
@@ -800,7 +802,7 @@ class TestFencedCodeWithCodehilite(TestCase):
extensions=['codehilite', 'fenced_code']
)
- def testFencedMultpleBlocksSameStyle(self):
+ def testFencedMultipleBlocksSameStyle(self):
if has_pygments:
# See also: https://github.com/Python-Markdown/markdown/issues/1240
expected = (
@@ -844,3 +846,129 @@ class TestFencedCodeWithCodehilite(TestCase):
'fenced_code'
]
)
+
+ def testCustomPygmentsFormatter(self):
+ if has_pygments:
+ class CustomFormatter(pygments.formatters.HtmlFormatter):
+ def wrap(self, source, outfile):
+ return self._wrap_div(self._wrap_code(source))
+
+ def _wrap_code(self, source):
+ yield 0, '<code>'
+ for i, t in source:
+ if i == 1:
+ t += '<br>'
+ yield i, t
+ yield 0, '</code>'
+
+ expected = '''
+ <div class="codehilite"><code>hello world
+ <br>hello another world
+ <br></code></div>
+ '''
+
+ else:
+ CustomFormatter = None
+ expected = '''
+ <pre class="codehilite"><code>hello world
+ hello another world
+ </code></pre>
+ '''
+
+ self.assertMarkdownRenders(
+ self.dedent(
+ '''
+ ```
+ hello world
+ hello another world
+ ```
+ '''
+ ),
+ self.dedent(
+ expected
+ ),
+ extensions=[
+ markdown.extensions.codehilite.CodeHiliteExtension(
+ pygments_formatter=CustomFormatter,
+ guess_lang=False,
+ ),
+ 'fenced_code'
+ ]
+ )
+
+ def testSvgCustomPygmentsFormatter(self):
+ if has_pygments:
+ expected = '''
+ <?xml version="1.0"?>
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+ <svg xmlns="http://www.w3.org/2000/svg">
+ <g font-family="monospace" font-size="14px">
+ <text x="0" y="14" xml:space="preserve">hello&#160;world</text>
+ <text x="0" y="33" xml:space="preserve">hello&#160;another&#160;world</text>
+ <text x="0" y="52" xml:space="preserve"></text></g></svg>
+ '''
+
+ else:
+ expected = '''
+ <pre class="codehilite"><code>hello world
+ hello another world
+ </code></pre>
+ '''
+
+ self.assertMarkdownRenders(
+ self.dedent(
+ '''
+ ```
+ hello world
+ hello another world
+ ```
+ '''
+ ),
+ self.dedent(
+ expected
+ ),
+ extensions=[
+ markdown.extensions.codehilite.CodeHiliteExtension(
+ pygments_formatter='svg',
+ linenos=False,
+ guess_lang=False,
+ ),
+ 'fenced_code'
+ ]
+ )
+
+ def testInvalidCustomPygmentsFormatter(self):
+ if has_pygments:
+ expected = '''
+ <div class="codehilite"><pre><span></span><code>hello world
+ hello another world
+ </code></pre></div>
+ '''
+
+ else:
+ expected = '''
+ <pre class="codehilite"><code>hello world
+ hello another world
+ </code></pre>
+ '''
+
+ self.assertMarkdownRenders(
+ self.dedent(
+ '''
+ ```
+ hello world
+ hello another world
+ ```
+ '''
+ ),
+ self.dedent(
+ expected
+ ),
+ extensions=[
+ markdown.extensions.codehilite.CodeHiliteExtension(
+ pygments_formatter='invalid',
+ guess_lang=False,
+ ),
+ 'fenced_code'
+ ]
+ )