From faba4f27e201362fc4bb9e499e1007c94187eaec Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Wed, 27 Oct 2021 07:56:40 +0530 Subject: [PATCH 1/7] 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). --- docs/extensions/code_hilite.md | 10 ++++++++++ markdown/extensions/codehilite.py | 12 ++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/docs/extensions/code_hilite.md b/docs/extensions/code_hilite.md index caaf011c0..6fa6190c7 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 e1c221816..273f9e011 100644 --- a/markdown/extensions/codehilite.py +++ b/markdown/extensions/codehilite.py @@ -99,6 +99,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) @@ -139,7 +140,10 @@ 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): + formatter = get_formatter_by_name(self.pygments_formatter, **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 @@ -278,7 +282,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(): From 30cfecaac74f80e56e1ff3c883493d3be3c0447d Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Thu, 28 Oct 2021 11:42:07 +0530 Subject: [PATCH 2/7] Add a new changelog file --- docs/change_log/release-3.4.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 docs/change_log/release-3.4.md diff --git a/docs/change_log/release-3.4.md b/docs/change_log/release-3.4.md new file mode 100644 index 000000000..81135f9d5 --- /dev/null +++ b/docs/change_log/release-3.4.md @@ -0,0 +1,21 @@ +title: Release Notes for v3.4 + +# Python-Markdown 3.4 Release Notes + +Python-Markdown version 3.4 supports Python versions 3.6, 3.7, 3.8, 3.9 and PyPy3. + +## Backwards-incompatible changes + +This release doesn't have any backwards-incompatible changes. + +## New features + +The following new features have been included in the 3.4 release: + +* 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 From 6229f3a0281312362c1a4c37a290014248ab6f44 Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Thu, 28 Oct 2021 11:42:15 +0530 Subject: [PATCH 3/7] Add formatters to spell --- .spell-dict | 1 + 1 file changed, 1 insertion(+) diff --git a/.spell-dict b/.spell-dict index fbe4865a7..6408100fc 100644 --- a/.spell-dict +++ b/.spell-dict @@ -160,3 +160,4 @@ plugin plugins configs pre +formatters From 0138ec86ae1793196bf95eb3fdd4a79a5a659e30 Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Thu, 28 Oct 2021 11:42:24 +0530 Subject: [PATCH 4/7] Add test for custom Pygments formatter --- .../extensions/test_fenced_code.py | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/tests/test_syntax/extensions/test_fenced_code.py b/tests/test_syntax/extensions/test_fenced_code.py index 56473b73d..cdc899576 100644 --- a/tests/test_syntax/extensions/test_fenced_code.py +++ b/tests/test_syntax/extensions/test_fenced_code.py @@ -20,11 +20,12 @@ """ from markdown.test_tools import TestCase -import markdown +import markdown, markdown.extensions.codehilite import os try: import pygments # noqa + import pygments.formatters # noqa has_pygments = True except ImportError: has_pygments = False @@ -781,3 +782,52 @@ def testFencedLanguageAttrNoclasses(self): expected, extensions=['codehilite', '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, '' + for i, t in source: + if i == 1: + t += '
' + yield i, t + yield 0, '
' + + expected = ''' +
hello world +
hello another world +
+ ''' + + else: + CustomFormatter = None + expected = ''' +
hello world
+            hello another world
+            
+ ''' + + 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' + ] + ) From a0dabe0d1c792dda801b26e8102147c7df91b7b3 Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Sat, 30 Oct 2021 08:16:38 +0530 Subject: [PATCH 5/7] Add another test and a negative test --- markdown/extensions/codehilite.py | 6 +- .../extensions/test_fenced_code.py | 79 +++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/markdown/extensions/codehilite.py b/markdown/extensions/codehilite.py index 273f9e011..f4e0a8494 100644 --- a/markdown/extensions/codehilite.py +++ b/markdown/extensions/codehilite.py @@ -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 @@ -141,7 +142,10 @@ def hilite(self, shebang=True): except ValueError: # pragma: no cover lexer = get_lexer_by_name('text', **self.options) if isinstance(self.pygments_formatter, str): - formatter = get_formatter_by_name(self.pygments_formatter, **self.options) + 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) diff --git a/tests/test_syntax/extensions/test_fenced_code.py b/tests/test_syntax/extensions/test_fenced_code.py index cdc899576..30e18bb20 100644 --- a/tests/test_syntax/extensions/test_fenced_code.py +++ b/tests/test_syntax/extensions/test_fenced_code.py @@ -831,3 +831,82 @@ def _wrap_code(self, source): 'fenced_code' ] ) + + def testSvgCustomPygmentsFormatter(self): + if has_pygments: + expected = ''' + + + + + hello world + hello another world + + ''' + + else: + CustomFormatter = None + expected = ''' +
hello world
+            hello another world
+            
+ ''' + + 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 = ''' +
hello world
+            hello another world
+            
+ ''' + + else: + CustomFormatter = None + expected = ''' +
hello world
+            hello another world
+            
+ ''' + + 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' + ] + ) From 684df8269a8e8d0748f7ac072ddfca460cf9ea7d Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Tue, 2 Nov 2021 15:00:49 +0530 Subject: [PATCH 6/7] Fixed lint and spelling fails --- docs/change_log/release-3.4.md | 2 +- tests/test_syntax/extensions/test_fenced_code.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/change_log/release-3.4.md b/docs/change_log/release-3.4.md index 81135f9d5..7965c5654 100644 --- a/docs/change_log/release-3.4.md +++ b/docs/change_log/release-3.4.md @@ -6,7 +6,7 @@ Python-Markdown version 3.4 supports Python versions 3.6, 3.7, 3.8, 3.9 and PyPy ## Backwards-incompatible changes -This release doesn't have any backwards-incompatible changes. +This release does not have any backwards-incompatible changes. ## New features diff --git a/tests/test_syntax/extensions/test_fenced_code.py b/tests/test_syntax/extensions/test_fenced_code.py index 30e18bb20..42803a778 100644 --- a/tests/test_syntax/extensions/test_fenced_code.py +++ b/tests/test_syntax/extensions/test_fenced_code.py @@ -20,7 +20,8 @@ """ from markdown.test_tools import TestCase -import markdown, markdown.extensions.codehilite +import markdown +import markdown.extensions.codehilite import os try: @@ -845,7 +846,6 @@ def testSvgCustomPygmentsFormatter(self): ''' else: - CustomFormatter = None expected = '''
hello world
             hello another world
@@ -883,7 +883,6 @@ def testInvalidCustomPygmentsFormatter(self):
             '''
 
         else:
-            CustomFormatter = None
             expected = '''
             
hello world
             hello another world

From 8a0472a1fa944e4130c71e2f1c0af04564044408 Mon Sep 17 00:00:00 2001
From: Shrikant Sharat Kandula 
Date: Sat, 7 May 2022 13:51:05 +0530
Subject: [PATCH 7/7] Add missing newlines

---
 tests/test_syntax/extensions/test_fenced_code.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/tests/test_syntax/extensions/test_fenced_code.py b/tests/test_syntax/extensions/test_fenced_code.py
index 5488f6a19..f8c3e91d3 100644
--- a/tests/test_syntax/extensions/test_fenced_code.py
+++ b/tests/test_syntax/extensions/test_fenced_code.py
@@ -818,6 +818,7 @@ def testFencedMultipleBlocksSameStyle(self):
             expected = '''
             
# First Code Block
             
+

Normal paragraph

# Second Code Block
             
@@ -829,7 +830,9 @@ def testFencedMultipleBlocksSameStyle(self): ``` { .python } # First Code Block ``` + Normal paragraph + ``` { .python } # Second Code Block ```