From ee53d85c4188772909b1216467a0e792c2bdec61 Mon Sep 17 00:00:00 2001 From: dmpayton Date: Tue, 23 Feb 2016 15:15:29 -0800 Subject: [PATCH 01/12] Fixes to get tests passing in Python 3. --- markdownify/__init__.py | 3 ++- setup.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/markdownify/__init__.py b/markdownify/__init__.py index 9b44d47..442e5e2 100644 --- a/markdownify/__init__.py +++ b/markdownify/__init__.py @@ -1,5 +1,6 @@ from bs4 import BeautifulSoup, NavigableString import re +import six convert_heading_re = re.compile(r'convert_h(\d+)') @@ -61,7 +62,7 @@ class MarkdownConverter(object): # Convert the children first for el in node.children: if isinstance(el, NavigableString): - text += self.process_text(unicode(el)) + text += self.process_text(six.text_type(el)) else: text += self.process_tag(el) diff --git a/setup.py b/setup.py index 7a3414c..0baa1f6 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ read = lambda filepath: codecs.open(filepath, 'r', 'utf-8').read() pkgmeta = {} -execfile(os.path.join(os.path.dirname(__file__), 'markdownify', 'pkgmeta.py'), +exec(open(os.path.join(os.path.dirname(__file__), 'markdownify', 'pkgmeta.py')).read(), pkgmeta) @@ -75,7 +75,7 @@ setup( 'pytest', ], install_requires=[ - 'beautifulsoup4', + 'beautifulsoup4', 'six' ], classifiers=[ 'Environment :: Web Environment', From 8696e2bde18f626e380feac723af15648169a71d Mon Sep 17 00:00:00 2001 From: Chris Hallberg Date: Mon, 12 Jun 2017 16:03:04 -0400 Subject: [PATCH 02/12] Suppress BeautifulSoup warning by explicitly passing in the default parser as recommended by the error message: ``` /home/challberg/.local/lib/python2.7/site-packages/bs4/__init__.py:181: UserWarning: No parser was explicitly specified, so I'm using the best available HTML parser for this system ("html.parser"). This usually isn't a problem, but if you run this code on another system, or in a different virtual environment, it may use a different parser and behave differently. The code that caused this warning is on line 35 of the file unroll.py. To get rid of this warning, change code that looks like this: BeautifulSoup(YOUR_MARKUP}) to this: BeautifulSoup(YOUR_MARKUP, "html.parser") markup_type=markup_type)) ``` --- markdownify/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/markdownify/__init__.py b/markdownify/__init__.py index 9b44d47..b265cab 100644 --- a/markdownify/__init__.py +++ b/markdownify/__init__.py @@ -52,7 +52,7 @@ class MarkdownConverter(object): # want a full document. Therefore, we'll mark our fragment with an id, # create the document, and extract the element with the id. html = wrapped % html - soup = BeautifulSoup(html) + soup = BeautifulSoup(html, 'html.parser') return self.process_tag(soup.find(id=FRAGMENT_ID), children_only=True) def process_tag(self, node, children_only=False): From b132a6f5b329eb3e03a7ce96ff25b0c58f030b45 Mon Sep 17 00:00:00 2001 From: Steven Skoczen Date: Tue, 28 Nov 2017 12:07:31 +1300 Subject: [PATCH 03/12] Updates to 0.4.1, pkgmeta included directly in setup. --- markdownify/pkgmeta.py | 8 -------- setup.py | 9 +++++---- 2 files changed, 5 insertions(+), 12 deletions(-) delete mode 100644 markdownify/pkgmeta.py diff --git a/markdownify/pkgmeta.py b/markdownify/pkgmeta.py deleted file mode 100644 index d6327f2..0000000 --- a/markdownify/pkgmeta.py +++ /dev/null @@ -1,8 +0,0 @@ -pkgmeta = dict( - __title__='markdownify', - __author__='Matthew Tretter', - __version__='0.4.0', -) - -globals().update(pkgmeta) -__all__ = pkgmeta.keys() diff --git a/setup.py b/setup.py index 0baa1f6..aef2ba4 100644 --- a/setup.py +++ b/setup.py @@ -7,10 +7,11 @@ from setuptools.command.test import test as TestCommand, Command read = lambda filepath: codecs.open(filepath, 'r', 'utf-8').read() - -pkgmeta = {} -exec(open(os.path.join(os.path.dirname(__file__), 'markdownify', 'pkgmeta.py')).read(), - pkgmeta) +pkgmeta = { + '__title__': 'markdownify', + '__author__': 'Matthew Tretter', + '__version__': '0.4.1', +} class PyTest(TestCommand): From 78afcc173e922280ee533f97f8ffa634e6d3edf5 Mon Sep 17 00:00:00 2001 From: Thomas Lange Date: Thu, 27 Sep 2018 11:26:49 +0200 Subject: [PATCH 04/12] Adding MIT license file --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b899f0c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright 2012-2018 Matthew Tretter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 5ac08522bede35e217d7fac649073879ef178fc4 Mon Sep 17 00:00:00 2001 From: Jonathan Vanasco Date: Thu, 13 Jun 2019 18:46:02 -0400 Subject: [PATCH 05/12] updating classifer to mit license issue #9 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index aef2ba4..ea57c27 100644 --- a/setup.py +++ b/setup.py @@ -82,7 +82,7 @@ setup( 'Environment :: Web Environment', 'Framework :: Django', 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', + 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2.5', 'Programming Language :: Python :: 2.6', From d0f688d2e4e225b65da1d6745687f00b0ce839f6 Mon Sep 17 00:00:00 2001 From: AlexVonB Date: Thu, 4 Jul 2019 16:26:09 +0200 Subject: [PATCH 06/12] Add newline before and after a markdown list Fixes matthewwithanm#5 as well as an issue where `

foo

  • bar
` gets converted to `foo * bar` which is not correct --- markdownify/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/markdownify/__init__.py b/markdownify/__init__.py index 12be28e..25608bf 100644 --- a/markdownify/__init__.py +++ b/markdownify/__init__.py @@ -153,7 +153,7 @@ class MarkdownConverter(object): el = el.parent if nested: text = '\n' + self.indent(text, 1) - return text + return '\n' + text + '\n' convert_ul = convert_list convert_ol = convert_list From 5f9243d91d1484c0d0d6777cc408150fda6e2f94 Mon Sep 17 00:00:00 2001 From: AlexVonB Date: Thu, 4 Jul 2019 16:32:21 +0200 Subject: [PATCH 07/12] added tests for matthewwithanm#11 --- tests/test_conversions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_conversions.py b/tests/test_conversions.py index af4b54f..dfc8d3c 100644 --- a/tests/test_conversions.py +++ b/tests/test_conversions.py @@ -103,6 +103,9 @@ def test_strong(): def test_ul(): assert md('
  • a
  • b
') == '* a\n* b\n' + +def test_inline_ul(): + assert md('

foo

  • a
  • b

bar

') == 'foo \n* a\n* b\n\nbar' def test_nested_uls(): From 28e447d9ae26d3c1ee2bc4a268823bdaf67e2312 Mon Sep 17 00:00:00 2001 From: AlexVonB Date: Thu, 11 Jul 2019 23:26:45 +0200 Subject: [PATCH 08/12] remove prefixed and suffixed spaces from inline tags fixes matthewwithanm#13 --- markdownify/__init__.py | 26 +++++++++++++++++++++++--- tests/test_conversions.py | 26 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/markdownify/__init__.py b/markdownify/__init__.py index 12be28e..43c32ca 100644 --- a/markdownify/__init__.py +++ b/markdownify/__init__.py @@ -22,6 +22,17 @@ def escape(text): return '' return text.replace('_', r'\_') +def chomp(text): + """ + If the text in an inline tag like b, a, or em contains a leading or trailing + space, strip the string and return a space as suffix of prefix, if needed. + This function is used to prevent conversions like + foo => ** foo** + """ + prefix = ' ' if text and text[0] == ' ' else '' + suffix = ' ' if text and text[-1] == ' ' else '' + text = text.strip() + return (prefix, suffix, text) def _todict(obj): return dict((k, getattr(obj, k)) for k in dir(obj) if not k.startswith('_')) @@ -110,13 +121,16 @@ class MarkdownConverter(object): return '%s\n%s\n\n' % (text, pad_char * len(text)) if text else '' def convert_a(self, el, text): + prefix, suffix, text = chomp(text) + if not text: + return '' href = el.get('href') title = el.get('title') if self.options['autolinks'] and text == href and not title: # Shortcut syntax return '<%s>' % href title_part = ' "%s"' % title.replace('"', r'\"') if title else '' - return '[%s](%s%s)' % (text or '', href, title_part) if href else text or '' + return '%s[%s](%s%s)%s' % (prefix, text or '', href, title_part, suffix) if href else text or '' def convert_b(self, el, text): return self.convert_strong(el, text) @@ -128,7 +142,10 @@ class MarkdownConverter(object): return ' \n' def convert_em(self, el, text): - return '*%s*' % text if text else '' + prefix, suffix, text = chomp(text) + if not text: + return '' + return '%s*%s*%s' % (prefix, text if text else '', suffix) def convert_hn(self, n, el, text): style = self.options['heading_style'] @@ -176,7 +193,10 @@ class MarkdownConverter(object): return '%s\n\n' % text if text else '' def convert_strong(self, el, text): - return '**%s**' % text if text else '' + prefix, suffix, text = chomp(text) + if not text: + return '' + return '%s**%s**%s' % (prefix, text if text else '', suffix) def convert_img(self, el, text): alt = el.attrs.get('alt', None) or '' diff --git a/tests/test_conversions.py b/tests/test_conversions.py index af4b54f..1d577ec 100644 --- a/tests/test_conversions.py +++ b/tests/test_conversions.py @@ -21,10 +21,24 @@ nested_uls = re.sub('\s+', '', """
  • 3
  • """) +def test_chomp(): + assert md(' ') == ' ' + assert md(' ') == ' ' + assert md(' ') == ' ' + assert md(' ') == ' ' + assert md(' s ') == ' **s** ' + assert md(' s ') == ' **s** ' + assert md(' s ') == ' **s** ' + assert md(' s ') == ' **s** ' def test_a(): assert md('Google') == '[Google](http://google.com)' +def test_a_spaces(): + assert md('foo Google bar') == 'foo [Google](http://google.com) bar' + assert md('foo Google bar') == 'foo [Google](http://google.com) bar' + assert md('foo Google bar') == 'foo [Google](http://google.com) bar' + assert md('foo bar') == 'foo bar' def test_a_with_title(): text = md('Google') @@ -44,6 +58,12 @@ def test_a_no_autolinks(): def test_b(): assert md('Hello') == '**Hello**' +def test_b_spaces(): + assert md('foo Hello bar') == 'foo **Hello** bar' + assert md('foo Hello bar') == 'foo **Hello** bar' + assert md('foo Hello bar') == 'foo **Hello** bar' + assert md('foo bar') == 'foo bar' + def test_blockquote(): assert md('
    Hello
    ').strip() == '> Hello' @@ -61,6 +81,12 @@ def test_br(): def test_em(): assert md('Hello') == '*Hello*' +def test_em_spaces(): + assert md('foo Hello bar') == 'foo *Hello* bar' + assert md('foo Hello bar') == 'foo *Hello* bar' + assert md('foo Hello bar') == 'foo *Hello* bar' + assert md('foo bar') == 'foo bar' + def test_h1(): assert md('

    Hello

    ') == 'Hello\n=====\n\n' From 5563161c865ee75c853b19242279dc8fd0c471ff Mon Sep 17 00:00:00 2001 From: AlexVonB Date: Fri, 12 Jul 2019 10:23:17 +0200 Subject: [PATCH 09/12] remove needless checks for emtpy text --- markdownify/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/markdownify/__init__.py b/markdownify/__init__.py index 43c32ca..e02f4e7 100644 --- a/markdownify/__init__.py +++ b/markdownify/__init__.py @@ -130,7 +130,7 @@ class MarkdownConverter(object): # Shortcut syntax return '<%s>' % href title_part = ' "%s"' % title.replace('"', r'\"') if title else '' - return '%s[%s](%s%s)%s' % (prefix, text or '', href, title_part, suffix) if href else text or '' + return '%s[%s](%s%s)%s' % (prefix, text, href, title_part, suffix) if href else text def convert_b(self, el, text): return self.convert_strong(el, text) @@ -145,7 +145,7 @@ class MarkdownConverter(object): prefix, suffix, text = chomp(text) if not text: return '' - return '%s*%s*%s' % (prefix, text if text else '', suffix) + return '%s*%s*%s' % (prefix, text, suffix) def convert_hn(self, n, el, text): style = self.options['heading_style'] @@ -196,7 +196,7 @@ class MarkdownConverter(object): prefix, suffix, text = chomp(text) if not text: return '' - return '%s**%s**%s' % (prefix, text if text else '', suffix) + return '%s**%s**%s' % (prefix, text, suffix) def convert_img(self, el, text): alt = el.attrs.get('alt', None) or '' From b747378b52163c7e0e369c74abb0a486ddfb1e81 Mon Sep 17 00:00:00 2001 From: AlexVonB Date: Sun, 9 Aug 2020 21:11:16 +0200 Subject: [PATCH 10/12] fixed nested lists and wrote correct tests nested lists did not work: after a nested list was over, a new line was inserted. this leads to a large gap before the rest of the parent list. lists are prefixed and suffixed with a single newline, this is now represented in the tests. --- markdownify/__init__.py | 3 ++- tests/test_conversions.py | 14 +++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/markdownify/__init__.py b/markdownify/__init__.py index d126cae..4a44737 100644 --- a/markdownify/__init__.py +++ b/markdownify/__init__.py @@ -169,7 +169,8 @@ class MarkdownConverter(object): break el = el.parent if nested: - text = '\n' + self.indent(text, 1) + # remove trailing newline if nested + return '\n' + self.indent(text, 1).rstrip() return '\n' + text + '\n' convert_ul = convert_list diff --git a/tests/test_conversions.py b/tests/test_conversions.py index 2526af3..853ed66 100644 --- a/tests/test_conversions.py +++ b/tests/test_conversions.py @@ -2,7 +2,7 @@ from markdownify import markdownify as md, ATX, ATX_CLOSED import re -nested_uls = re.sub('\s+', '', """ +nested_uls = re.sub(r'\s+', '', """
    • 1
        @@ -116,7 +116,7 @@ def test_i(): def test_ol(): - assert md('
        1. a
        2. b
        ') == '1. a\n2. b\n' + assert md('
        1. a
        2. b
        ') == '\n1. a\n2. b\n\n' def test_p(): @@ -128,10 +128,10 @@ def test_strong(): def test_ul(): - assert md('
        • a
        • b
        ') == '* a\n* b\n' - + assert md('
        • a
        • b
        ') == '\n* a\n* b\n\n' + def test_inline_ul(): - assert md('

        foo

        • a
        • b

        bar

        ') == 'foo \n* a\n* b\n\nbar' + assert md('

        foo

        • a
        • b

        bar

        ') == 'foo\n\n\n* a\n* b\n\nbar\n\n' def test_nested_uls(): @@ -139,11 +139,11 @@ def test_nested_uls(): Nested ULs should alternate bullet characters. """ - assert md(nested_uls) == '* 1\n\t+ a\n\t\t- I\n\t\t- II\n\t\t- III\n\t\t\n\t+ b\n\t+ c\n\t\n* 2\n* 3\n' + assert md(nested_uls) == '\n* 1\n\t+ a\n\t\t- I\n\t\t- II\n\t\t- III\n\t+ b\n\t+ c\n* 2\n* 3\n\n' def test_bullets(): - assert md(nested_uls, bullets='-') == '- 1\n\t- a\n\t\t- I\n\t\t- II\n\t\t- III\n\t\t\n\t- b\n\t- c\n\t\n- 2\n- 3\n' + assert md(nested_uls, bullets='-') == '\n- 1\n\t- a\n\t\t- I\n\t\t- II\n\t\t- III\n\t- b\n\t- c\n- 2\n- 3\n\n' def test_img(): From 3b049cdb9ca597c7e1458538b74ade3630e4a12f Mon Sep 17 00:00:00 2001 From: AlexVonB Date: Sun, 9 Aug 2020 21:13:33 +0200 Subject: [PATCH 11/12] added egg dirs to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index ae9fdc5..8350118 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ *.pyc *.egg +.eggs/ +*.egg-info/ .DS_Store /.env /dist From aceced68eb4cafba2aaf77636200764d0553317c Mon Sep 17 00:00:00 2001 From: AlexVonB Date: Sun, 9 Aug 2020 21:17:39 +0200 Subject: [PATCH 12/12] cleaning up changes with help of linter --- markdownify/__init__.py | 2 ++ tests/test_conversions.py | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/markdownify/__init__.py b/markdownify/__init__.py index 4a44737..56854e8 100644 --- a/markdownify/__init__.py +++ b/markdownify/__init__.py @@ -22,6 +22,7 @@ def escape(text): return '' return text.replace('_', r'\_') + def chomp(text): """ If the text in an inline tag like b, a, or em contains a leading or trailing @@ -34,6 +35,7 @@ def chomp(text): text = text.strip() return (prefix, suffix, text) + def _todict(obj): return dict((k, getattr(obj, k)) for k in dir(obj) if not k.startswith('_')) diff --git a/tests/test_conversions.py b/tests/test_conversions.py index 853ed66..b8487e5 100644 --- a/tests/test_conversions.py +++ b/tests/test_conversions.py @@ -21,6 +21,7 @@ nested_uls = re.sub(r'\s+', '', """
      • 3
      """) + def test_chomp(): assert md(' ') == ' ' assert md(' ') == ' ' @@ -31,15 +32,18 @@ def test_chomp(): assert md(' s ') == ' **s** ' assert md(' s ') == ' **s** ' + def test_a(): assert md('Google') == '[Google](http://google.com)' + def test_a_spaces(): assert md('foo Google bar') == 'foo [Google](http://google.com) bar' assert md('foo Google bar') == 'foo [Google](http://google.com) bar' assert md('foo Google bar') == 'foo [Google](http://google.com) bar' assert md('foo bar') == 'foo bar' + def test_a_with_title(): text = md('Google') assert text == r'[Google](http://google.com "The \"Goog\"")' @@ -58,6 +62,7 @@ def test_a_no_autolinks(): def test_b(): assert md('Hello') == '**Hello**' + def test_b_spaces(): assert md('foo Hello bar') == 'foo **Hello** bar' assert md('foo Hello bar') == 'foo **Hello** bar' @@ -81,6 +86,7 @@ def test_br(): def test_em(): assert md('Hello') == '*Hello*' + def test_em_spaces(): assert md('foo Hello bar') == 'foo *Hello* bar' assert md('foo Hello bar') == 'foo *Hello* bar' @@ -130,6 +136,7 @@ def test_strong(): def test_ul(): assert md('
      • a
      • b
      ') == '\n* a\n* b\n\n' + def test_inline_ul(): assert md('

      foo

      • a
      • b

      bar

      ') == 'foo\n\n\n* a\n* b\n\nbar\n\n'