diff --git a/README.rst b/README.rst index 059a68f..87b394d 100644 --- a/README.rst +++ b/README.rst @@ -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 ``
`` 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 ```` sections.
Useful, if all code on a page is in the same programming language and
diff --git a/markdownify/__init__.py b/markdownify/__init__.py
index 28cdaf6..18281fa 100644
--- a/markdownify/__init__.py
+++ b/markdownify/__init__.py
@@ -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
@@ -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']}
@@ -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 + '"'
diff --git a/markdownify/__init__.pyi b/markdownify/__init__.pyi
index 2494011..4c3dba8 100644
--- a/markdownify/__init__.pyi
+++ b/markdownify/__init__.pyi
@@ -39,6 +39,7 @@ def markdownify(
table_infer_header: bool = ...,
wrap: bool = ...,
wrap_width: Union[int, None] = ...,
+ code_fence: str = ...,
) -> str: ...
@@ -67,6 +68,7 @@ class MarkdownConverter:
table_infer_header: bool = ...,
wrap: bool = ...,
wrap_width: Union[int, None] = ...,
+ code_fence: str = ...,
) -> None:
...
diff --git a/markdownify/main.py b/markdownify/main.py
index ba70671..1145502 100755
--- a/markdownify/main.py
+++ b/markdownify/main.py
@@ -46,6 +46,9 @@ def main(argv=sys.argv[1:]):
choices=(SPACES, BACKSLASH),
help="Defines the style of
conversions: two spaces "
"or backslash at the and of the line thet should break.")
+ parser.add_argument('--code-fence', default='```',
+ help="Fence for '' 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 "
"'' sections.")
diff --git a/tests/test_code_fence.py b/tests/test_code_fence.py
new file mode 100644
index 0000000..4b346ae
--- /dev/null
+++ b/tests/test_code_fence.py
@@ -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('hello\nworld
', code_fence=fence) == (
+ fence + '\nhello\nworld\n' + fence
+ )
+
+
+def test_tilde_fence_with_language():
+ assert markdownify('print(1)
', code_fence='~~~', code_language='python') == (
+ '~~~python\nprint(1)\n~~~'
+ )
+
+
+def test_tilde_fence_with_language_callback():
+ assert markdownify(
+ 'print(1)
',
+ 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(
+ '\n\nhello\n\n
', 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('hello
') == '~~~\nhello\n~~~'
+
+
+def test_tilde_fence_does_not_change_inline_code_or_empty_pre():
+ assert markdownify('hello', 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('print(1)
'))
+ main(['--code-fence', '~~~', '--code-language', 'python'])
+ assert capsys.readouterr().out == '~~~python\nprint(1)\n~~~\n'
diff --git a/tests/types.py b/tests/types.py
index 7424978..54106fb 100644
--- a/tests/types.py
+++ b/tests/types.py
@@ -13,6 +13,7 @@
autolinks=True,
bs4_options='html.parser',
bullets='*+-',
+ code_fence='```',
code_language='',
code_language_callback=None,
convert=None,
@@ -38,6 +39,7 @@
MarkdownConverter(
strip_document=None,
bullets="-",
+ code_fence="~~~",
escape_asterisks=True,
escape_underscores=True,
escape_misc=True,
@@ -68,3 +70,5 @@ def callback(el: BeautifulSoup) -> Union[str, None]:
markdownify('test\n foo\nbar
', code_language_callback=callback)
markdownify('test\n foo\nbar
', code_language_callback=lambda el: None)
+
+markdownify("test
", code_fence="~~~") == "~~~\ntest\n~~~"