From e83ee5308a1d5866297e6ac92be5ae0f0d1e477e Mon Sep 17 00:00:00 2001 From: Adam Griffiths Date: Mon, 22 Mar 2021 19:24:00 +1100 Subject: [PATCH] Fix #3 by re-working lib to use `six` Use six module to support both py2 and py3. Add six as a dependency of cottonmouth. --- cottonmouth/constants.py | 220 ++++++++++++++++++++------------------- cottonmouth/html.py | 37 +++---- cottonmouth/tags.py | 5 +- setup.py | 1 + tests.py | 15 +-- 5 files changed, 142 insertions(+), 136 deletions(-) diff --git a/cottonmouth/constants.py b/cottonmouth/constants.py index 39050bd..4d19cca 100644 --- a/cottonmouth/constants.py +++ b/cottonmouth/constants.py @@ -1,114 +1,116 @@ +import six + HTML_VOID_TAGS = [ - u'area', - u'base', - u'br', - u'col', - u'command', - u'embed', - u'hr', - u'img', - u'input', - u'keygen', - u'link', - u'meta', - u'param', - u'source', - u'track', - u'wbr', + six.u('area'), + six.u('base'), + six.u('br'), + six.u('col'), + six.u('command'), + six.u('embed'), + six.u('hr'), + six.u('img'), + six.u('input'), + six.u('keygen'), + six.u('link'), + six.u('meta'), + six.u('param'), + six.u('source'), + six.u('track'), + six.u('wbr'), ] HTML_TAGS = [ - u'a', - u'address', - u'applet', - u'area', - u'article', - u'aside', - u'b', - u'base', - u'basefont', - u'bgsound', - u'big', - u'blockquote', - u'body', - u'br', - u'button', - u'caption', - u'center', - u'code', - u'col', - u'colgroup', - u'command', - u'dd', - u'details', - u'dir', - u'div', - u'dl', - u'dt', - u'em', - u'embed', - u'fieldset', - u'figure', - u'font', - u'footer', - u'form', - u'frame', - u'frameset', - u'h1', - u'h2', - u'h3', - u'h4', - u'h5', - u'h6', - u'head', - u'header', - u'hr', - u'html', - u'i', - u'iframe', - u'image', - u'img', - u'input', - u'isindex', - u'li', - u'link', - u'listing', - u'marquee', - u'menu', - u'meta', - u'nav', - u'nobr', - u'noembed', - u'noframes', - u'noscript', - u'object', - u'ol', - u'p', - u'param', - u'plaintext', - u'pre', - u's', - u'script', - u'section', - u'select', - u'small', - u'source', - u'strike', - u'strong', - u'style', - u'table', - u'tbody', - u'td', - u'textarea', - u'tfoot', - u'th', - u'thead', - u'title', - u'tr', - u'track', - u'tt', - u'u', - u'ul', - u'wbr', - u'xmp', + six.u('a'), + six.u('address'), + six.u('applet'), + six.u('area'), + six.u('article'), + six.u('aside'), + six.u('b'), + six.u('base'), + six.u('basefont'), + six.u('bgsound'), + six.u('big'), + six.u('blockquote'), + six.u('body'), + six.u('br'), + six.u('button'), + six.u('caption'), + six.u('center'), + six.u('code'), + six.u('col'), + six.u('colgroup'), + six.u('command'), + six.u('dd'), + six.u('details'), + six.u('dir'), + six.u('div'), + six.u('dl'), + six.u('dt'), + six.u('em'), + six.u('embed'), + six.u('fieldset'), + six.u('figure'), + six.u('font'), + six.u('footer'), + six.u('form'), + six.u('frame'), + six.u('frameset'), + six.u('h1'), + six.u('h2'), + six.u('h3'), + six.u('h4'), + six.u('h5'), + six.u('h6'), + six.u('head'), + six.u('header'), + six.u('hr'), + six.u('html'), + six.u('i'), + six.u('iframe'), + six.u('image'), + six.u('img'), + six.u('input'), + six.u('isindex'), + six.u('li'), + six.u('link'), + six.u('listing'), + six.u('marquee'), + six.u('menu'), + six.u('meta'), + six.u('nav'), + six.u('nobr'), + six.u('noembed'), + six.u('noframes'), + six.u('noscript'), + six.u('object'), + six.u('ol'), + six.u('p'), + six.u('param'), + six.u('plaintext'), + six.u('pre'), + six.u('s'), + six.u('script'), + six.u('section'), + six.u('select'), + six.u('small'), + six.u('source'), + six.u('strike'), + six.u('strong'), + six.u('style'), + six.u('table'), + six.u('tbody'), + six.u('td'), + six.u('textarea'), + six.u('tfoot'), + six.u('th'), + six.u('thead'), + six.u('title'), + six.u('tr'), + six.u('track'), + six.u('tt'), + six.u('u'), + six.u('ul'), + six.u('wbr'), + six.u('xmp'), ] diff --git a/cottonmouth/html.py b/cottonmouth/html.py index 7667a6f..0ce743c 100644 --- a/cottonmouth/html.py +++ b/cottonmouth/html.py @@ -1,4 +1,5 @@ -import collections +import six +from six.moves.collections_abc import Iterable import itertools from . import constants @@ -8,7 +9,7 @@ def render(*content, **context): """ Renders a sequence of content as HTML. """ - return u''.join((e for c in content for e in render_content(c, **context))) + return six.u('').join(e for c in content for e in render_content(c, **context)) def render_content(content, **context): @@ -23,17 +24,17 @@ def render_content(content, **context): - Any other value, coerced to unicode """ if content is None: - yield u'' - elif isinstance(content, basestring): + yield six.u('') + elif isinstance(content, six.string_types): yield content elif callable(content): for e in render_content(content(**context), **context): yield e - elif isinstance(content, collections.Iterable): + elif isinstance(content, Iterable): for e in render_iterable(content, **context): yield e else: - yield unicode(content) + yield six.text_type(content) def render_iterable(content, **context): @@ -44,11 +45,11 @@ def render_iterable(content, **context): head = next(tail) # Render tag around the content - if isinstance(head, basestring): + if isinstance(head, six.string_types): for e in render_tag(head, tail, **context): yield e # Render nested lists - elif isinstance(head, collections.Iterable): + elif isinstance(head, Iterable): for e in render_iterable(head, **context): yield e for content in tail: @@ -72,36 +73,36 @@ def render_tag(tag, content, **context): extra, remainder = {}, content # Default to div if no explicit tag is provided - if tag.startswith(u'#'): - tag = u'div{}'.format(tag) - elif tag.startswith(u'.'): - tag = u'div{}'.format(tag) + if tag.startswith(six.u('#')): + tag = six.u('div{}').format(tag) + elif tag.startswith(six.u('.')): + tag = six.u('div{}').format(tag) # Split tag into ["tag#id", "class1", "class2", ...] chunks chunks = tag.split('.') # Parse tag and id out of tag shortcut tag = chunks[0] - if u'#' in chunks[0]: + if six.u('#') in chunks[0]: tag, extra['id'] = chunks[0].split('#') # Parse classes classes = chunks[1:] extra_classes = extra.get('class') - if isinstance(extra_classes, basestring): + if isinstance(extra_classes, six.string_types): classes.extend(extra_classes.split()) elif extra_classes: classes.extend(extra_classes) # Format classes if classes: - extra['class'] = u' '.join(classes) + extra['class'] = six.u(' ').join(classes) # Format attributes - attributes = u''.join([u' {}="{}"'.format(*i) for i in extra.items()]) + attributes = six.u('').join([six.u(' {}="{}"').format(*i) for i in extra.items()]) # Start our tag sandwich - yield u'<{}{}>'.format(tag, attributes) + yield six.u('<{}{}>').format(tag, attributes) # Render the delicious filling or toppings for content in remainder: @@ -110,4 +111,4 @@ def render_tag(tag, content, **context): # CLOSE THE TAG IF WE HAVE TO I GUESS if tag not in constants.HTML_VOID_TAGS: - yield u''.format(tag) + yield six.u('').format(tag) diff --git a/cottonmouth/tags.py b/cottonmouth/tags.py index cda076e..2441e9d 100644 --- a/cottonmouth/tags.py +++ b/cottonmouth/tags.py @@ -1,9 +1,10 @@ +import six from . import constants -class Tag(unicode): +class Tag(six.text_type): def __call__(self, *content, **extra): - tag = [unicode(self), extra] + tag = [six.text_type(self), extra] tag.extend(content) yield tag diff --git a/setup.py b/setup.py index e740779..a7325cb 100644 --- a/setup.py +++ b/setup.py @@ -17,5 +17,6 @@ url='https://github.com/nosamanuel/cottonmouth', license=license, packages=find_packages(exclude=('tests', 'docs')), + install_requires=['six'], test_suite='tests', ) diff --git a/tests.py b/tests.py index 126f637..2c9bb35 100644 --- a/tests.py +++ b/tests.py @@ -1,5 +1,6 @@ import unittest +import six from cottonmouth import constants from cottonmouth import tags from cottonmouth.html import render @@ -87,39 +88,39 @@ def welcome(user=None, **context): [link, dict(rel='stylesheet', type='text/css', href='static/layout.css')]], [body, - h1(u'Welcome to the site!', id='header'), + h1(six.u('Welcome to the site!'), id='header'), ['#map.pretty-map'], ['#main', welcome]]] ) - self.assertTrue(isinstance(render(*content), unicode)) + self.assertTrue(isinstance(render(*content), six.text_type)) def test_unicode_coercion(self): object_ = object() content = ['p', object_] self.assertEqual( render(content), - u'

{}

'.format(unicode(object_)) + six.u('

{}

').format(six.text_type(object_)) ) def test_class_name_are_set_via_attributes(self): content = ['span', {'class': 'foo'}, 'hello'] self.assertEqual( render(content), - u'hello' + six.u('hello') ) def test_class_names_are_extended_via_string_attribute(self): content = ['span.foo', {'class': 'bar'}, 'hello'] self.assertEqual( render(content), - u'hello' + six.u('hello') ) def test_class_names_are_extended_via_list_attribute(self): content = ['span.foo', {'class': ['bar', 'baz']}, 'hello'] self.assertEqual( render(content), - u'hello' + six.u('hello') ) def test_class_names_are_extended_via_iterable_attribute(self): @@ -127,7 +128,7 @@ def test_class_names_are_extended_via_iterable_attribute(self): content = ['span.foo', {'class': classes}, 'hello'] self.assertEqual( render(content), - u'hello' + six.u('hello') )