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 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. diff --git a/markdownify/__init__.py b/markdownify/__init__.py index 9b44d47..56854e8 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+)') @@ -22,6 +23,19 @@ def escape(text): 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('_')) @@ -52,7 +66,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): @@ -61,7 +75,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) @@ -109,13 +123,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, href, title_part, suffix) if href else text def convert_b(self, el, text): return self.convert_strong(el, text) @@ -127,7 +144,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, suffix) def convert_hn(self, n, el, text): style = self.options['heading_style'] @@ -151,8 +171,9 @@ class MarkdownConverter(object): break el = el.parent if nested: - text = '\n' + self.indent(text, 1) - return text + # remove trailing newline if nested + return '\n' + self.indent(text, 1).rstrip() + return '\n' + text + '\n' convert_ul = convert_list convert_ol = convert_list @@ -175,7 +196,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, suffix) def convert_img(self, el, text): alt = el.attrs.get('alt', None) or '' 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 7a3414c..ea57c27 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 = {} -execfile(os.path.join(os.path.dirname(__file__), 'markdownify', 'pkgmeta.py'), - pkgmeta) +pkgmeta = { + '__title__': 'markdownify', + '__author__': 'Matthew Tretter', + '__version__': '0.4.1', +} class PyTest(TestCommand): @@ -75,13 +76,13 @@ setup( 'pytest', ], install_requires=[ - 'beautifulsoup4', + 'beautifulsoup4', 'six' ], classifiers=[ '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', diff --git a/tests/test_conversions.py b/tests/test_conversions.py index af4b54f..b8487e5 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+', '', """