Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .spell-dict
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,4 @@ plugin
plugins
configs
pre
formatters
8 changes: 7 additions & 1 deletion docs/change_log/release-3.4.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
10 changes: 10 additions & 0 deletions docs/extensions/code_hilite.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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/
16 changes: 14 additions & 2 deletions markdown/extensions/codehilite.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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
Expand Down Expand Up @@ -99,6 +100,7 @@ def __init__(self, src, **options):
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)
Expand Down Expand Up @@ -139,7 +141,13 @@ def hilite(self, shebang=True):
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
Expand Down Expand Up @@ -279,7 +287,11 @@ def __init__(self, **kwargs):
'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():
Expand Down
130 changes: 129 additions & 1 deletion tests/test_syntax/extensions/test_fenced_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@

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
Expand Down Expand Up @@ -800,7 +802,7 @@ def testFencedLanguageAttrNoclasses(self):
extensions=['codehilite', 'fenced_code']
)

def testFencedMultpleBlocksSameStyle(self):
def testFencedMultipleBlocksSameStyle(self):
if has_pygments:
# See also: https://github.com/Python-Markdown/markdown/issues/1240
expected = (
Expand Down Expand Up @@ -844,3 +846,129 @@ def testFencedMultpleBlocksSameStyle(self):
'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'
]
)