Skip to content
Open
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
6 changes: 6 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ newline_style
newline). While the latter convention is non-standard, it is commonly
preferred and supported by a lot of interpreters.

code_fence
Defines the opening and closing fence for ``<pre>`` sections. Use a string
of at least three backticks (the default) or at least three tildes.
For example, ``code_fence="~~~"`` produces tilde-fenced code blocks.
The same option is available as ``--code-fence`` on the command line.

code_language
Defines the language that should be assumed for all ``<pre>`` sections.
Useful, if all code on a page is in the same programming language and
Expand Down
8 changes: 7 additions & 1 deletion markdownify/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ class DefaultOptions:
autolinks = True
bs4_options = 'html.parser'
bullets = '*+-' # An iterable of bullet types.
code_fence = '```'
code_language = ''
code_language_callback = None
convert = None
Expand Down Expand Up @@ -211,6 +212,10 @@ def __init__(self, **options):
raise ValueError('You may specify either tags to strip or tags to'
' convert, but not both.')

code_fence = self.options['code_fence']
if not isinstance(code_fence, six.string_types) or not re.match(r'(?:`{3,}|~{3,})\Z', code_fence):
raise ValueError('Invalid value for code_fence: %s' % code_fence)

# If a string or list is passed to bs4_options, assume it is a 'features' specification
if not isinstance(self.options['bs4_options'], dict):
self.options['bs4_options'] = {'features': self.options['bs4_options']}
Expand Down Expand Up @@ -702,7 +707,8 @@ def convert_pre(self, el, text, parent_tags):
else:
raise ValueError('Invalid value for strip_pre: %s' % self.options['strip_pre'])

return '\n\n```%s\n%s\n```\n\n' % (code_language, text)
code_fence = self.options['code_fence']
return '\n\n%s%s\n%s\n%s\n\n' % (code_fence, code_language, text, code_fence)

def convert_q(self, el, text, parent_tags):
return '"' + text + '"'
Expand Down
2 changes: 2 additions & 0 deletions markdownify/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def markdownify(
table_infer_header: bool = ...,
wrap: bool = ...,
wrap_width: Union[int, None] = ...,
code_fence: str = ...,
) -> str: ...


Expand Down Expand Up @@ -67,6 +68,7 @@ class MarkdownConverter:
table_infer_header: bool = ...,
wrap: bool = ...,
wrap_width: Union[int, None] = ...,
code_fence: str = ...,
) -> None:
...

Expand Down
3 changes: 3 additions & 0 deletions markdownify/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ def main(argv=sys.argv[1:]):
choices=(SPACES, BACKSLASH),
help="Defines the style of <br> conversions: two spaces "
"or backslash at the and of the line thet should break.")
parser.add_argument('--code-fence', default='```',
help="Fence for '<pre>' sections: at least three backticks "
"or at least three tildes. Defaults to three backticks.")
parser.add_argument('--code-language', default='',
help="Defines the language that should be assumed for all "
"'<pre>' sections.")
Expand Down
62 changes: 62 additions & 0 deletions tests/test_code_fence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import pytest

from markdownify import MarkdownConverter, STRIP, STRIP_ONE, markdownify


@pytest.mark.parametrize('fence', ['```', '~~~', '````', '~~~~'])
def test_code_fence(fence):
assert markdownify('<pre><code>hello\nworld</code></pre>', code_fence=fence) == (
fence + '\nhello\nworld\n' + fence
)


def test_tilde_fence_with_language():
assert markdownify('<pre>print(1)</pre>', code_fence='~~~', code_language='python') == (
'~~~python\nprint(1)\n~~~'
)


def test_tilde_fence_with_language_callback():
assert markdownify(
'<pre class="python">print(1)</pre>',
code_fence='~~~',
code_language_callback=lambda el: el['class'][0],
) == '~~~python\nprint(1)\n~~~'


@pytest.mark.parametrize('strip_pre, content', [
(None, '\n\nhello\n\n'),
(STRIP, 'hello'),
(STRIP_ONE, '\nhello'),
])
def test_tilde_fence_preserves_strip_pre(strip_pre, content):
assert markdownify(
'<pre>\n\nhello\n\n</pre>', code_fence='~~~', strip_pre=strip_pre,
) == '~~~\n' + content + '\n~~~'


def test_code_fence_subclass_option():
class TildeConverter(MarkdownConverter):
class Options:
code_fence = '~~~'

assert TildeConverter().convert('<pre>hello</pre>') == '~~~\nhello\n~~~'


def test_tilde_fence_does_not_change_inline_code_or_empty_pre():
assert markdownify('<code>hello</code><pre></pre>', code_fence='~~~') == '`hello`'


@pytest.mark.parametrize('fence', ['', '``', '~~', '```~', '~~~\n', 'abc', None, 3])
def test_invalid_code_fence(fence):
with pytest.raises(ValueError, match='Invalid value for code_fence'):
MarkdownConverter(code_fence=fence)


def test_code_fence_cli(monkeypatch, capsys):
from io import StringIO
from markdownify.main import main

monkeypatch.setattr('sys.stdin', StringIO('<pre>print(1)</pre>'))
main(['--code-fence', '~~~', '--code-language', 'python'])
assert capsys.readouterr().out == '~~~python\nprint(1)\n~~~\n'
4 changes: 4 additions & 0 deletions tests/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
autolinks=True,
bs4_options='html.parser',
bullets='*+-',
code_fence='```',
code_language='',
code_language_callback=None,
convert=None,
Expand All @@ -38,6 +39,7 @@
MarkdownConverter(
strip_document=None,
bullets="-",
code_fence="~~~",
escape_asterisks=True,
escape_underscores=True,
escape_misc=True,
Expand Down Expand Up @@ -68,3 +70,5 @@ def callback(el: BeautifulSoup) -> Union[str, None]:

markdownify('<pre class="python">test\n foo\nbar</pre>', code_language_callback=callback)
markdownify('<pre class="python">test\n foo\nbar</pre>', code_language_callback=lambda el: None)

markdownify("<pre>test</pre>", code_fence="~~~") == "~~~\ntest\n~~~"