aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml1
-rw-r--r--README.markdown4
-rwxr-xr-xbin/test9
-rw-r--r--requirements.txt1
-rw-r--r--screenplain/export/html.py15
-rw-r--r--screenplain/parsers/fountain.py5
-rw-r--r--screenplain/richstring.py21
-rw-r--r--test.py24
-rw-r--r--tests/fdx_test.py2
-rw-r--r--tests/fountain_test.py2
-rw-r--r--tests/richstring_test.py3
11 files changed, 72 insertions, 15 deletions
diff --git a/.travis.yml b/.travis.yml
index b90b600..2ece8ef 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,6 @@
language: python
python:
- "2.7"
+ - "3.3"
install: pip install -r requirements.txt
script: bin/test
diff --git a/README.markdown b/README.markdown
index ce453af..445ad1c 100644
--- a/README.markdown
+++ b/README.markdown
@@ -76,3 +76,7 @@ After this, the `screenplain` command will use the working copy of your code.
To run unit tests and style checks, run:
bin/test
+
+For developing for Python 3, instead use:
+
+ mkvirtualenv --no-site-packages --python=$(which python3) screenplain-py3
diff --git a/bin/test b/bin/test
index 2d35baf..025248e 100755
--- a/bin/test
+++ b/bin/test
@@ -1,3 +1,8 @@
#!/bin/bash
-nosetests --nocapture --with-doctest --doctest-tests $* && \
- pep8 --ignore=E402 screenplain tests
+if [[ $(python -V 2>&1) =~ "Python 2" ]]
+then
+ nosetests --nocapture --with-doctest --doctest-tests -I ^test.py $* && \
+ pep8 --ignore=E402 screenplain tests
+else
+ python test.py && pep8 --ignore=E402 screenplain tests
+fi
diff --git a/requirements.txt b/requirements.txt
index 321e747..50f908b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,3 +2,4 @@ reportlab
unittest2
nose
pep8
+six
diff --git a/screenplain/export/html.py b/screenplain/export/html.py
index 1f88e64..81c0f8b 100644
--- a/screenplain/export/html.py
+++ b/screenplain/export/html.py
@@ -18,19 +18,22 @@ class tag(object):
>>> import sys
>>> from __future__ import with_statement
>>> with tag(sys.stdout, 'div'):
- ... sys.stdout.write('hello')
+ ... print('hello')
...
- <div>hello</div>
+ <div>hello
+ </div>
Adding classes to the element is possible:
>>> with tag(sys.stdout, 'div', classes=['action']):
- ... sys.stdout.write('hello')
- <div class="action">hello</div>
+ ... print('hello')
+ <div class="action">hello
+ </div>
>>> with tag(sys.stdout, 'div', classes=['action', 'centered']):
- ... sys.stdout.write('hello')
- <div class="action centered">hello</div>
+ ... print('hello')
+ <div class="action centered">hello
+ </div>
"""
def __init__(self, out, tag, classes=None):
diff --git a/screenplain/parsers/fountain.py b/screenplain/parsers/fountain.py
index 3290dd2..5434c1a 100644
--- a/screenplain/parsers/fountain.py
+++ b/screenplain/parsers/fountain.py
@@ -5,6 +5,7 @@
import itertools
from itertools import takewhile
import re
+from six import next
from screenplain.types import (
Slug, Action, Dialog, DualDialog, Transition, Section, PageBreak,
@@ -269,7 +270,7 @@ def parse_title_page(lines):
it = iter(lines)
try:
- line = it.next()
+ line = next(it)
while True:
key_match = title_page_key_re.match(line)
if not key_match:
@@ -278,7 +279,7 @@ def parse_title_page(lines):
if value:
# Single line key/value
result.setdefault(key, []).append(value)
- line = it.next()
+ line = next(it)
else:
for line in it:
value_match = title_page_value_re.match(line)
diff --git a/screenplain/richstring.py b/screenplain/richstring.py
index e24db1a..ad667d1 100644
--- a/screenplain/richstring.py
+++ b/screenplain/richstring.py
@@ -4,10 +4,21 @@
import re
import cgi
+import six
_magic_re = re.compile(u'[\ue700-\ue705]')
+def _escape(s):
+ """Replaces special HTML characters like <
+ and non-ascii characters with ampersand escapes.
+
+ """
+ encoded = cgi.escape(s).encode('ascii', 'xmlcharrefreplace')
+ # In Py3, encoded is bytes type, so convert it to a string
+ return encoded.decode('ascii')
+
+
class RichString(object):
"""A sequence of segments where each segment can have its own style."""
@@ -20,7 +31,10 @@ class RichString(object):
return ' + '.join(repr(s) for s in self.segments)
def __unicode__(self):
- return ''.join(unicode(s) for s in self.segments)
+ return ''.join(six.text_type(s) for s in self.segments)
+
+ def __str__(self):
+ return self.__unicode__()
def startswith(self, string):
"""Checks if the first segment in this string starts with a
@@ -93,6 +107,9 @@ class Segment(object):
def __unicode__(self):
return self.text
+ def __str__(self):
+ return self.text
+
def __eq__(self, other):
return (
isinstance(other, Segment) and
@@ -116,7 +133,7 @@ class Segment(object):
re.sub(
' +', # at least two spaces
lambda m: '&nbsp;' * (len(m.group(0)) - 1) + ' ',
- cgi.escape(self.text).encode('ascii', 'xmlcharrefreplace'),
+ _escape(self.text),
) +
''.join(style.end_html for style in reversed(ordered_styles))
)
diff --git a/test.py b/test.py
new file mode 100644
index 0000000..c4e5887
--- /dev/null
+++ b/test.py
@@ -0,0 +1,24 @@
+"""Runs nosetest after preparing the test cases.
+
+Removes the leading u from unicode literals so
+Python 3 doctests won't fail.
+
+"""
+
+from screenplain import richstring
+import re
+
+unicode_literal = re.compile(r'u([\'"])')
+
+for n in (
+ 'parse_emphasis',
+ '_unescape',
+ '_demagic_literals',
+):
+ attr = getattr(richstring, n)
+ old_doc = getattr(attr, '__doc__')
+ new_doc = unicode_literal.sub(r'\1', old_doc)
+ setattr(attr, '__doc__', new_doc)
+
+import nose
+nose.main(argv='nosetests --nocapture --with-doctest --doctest-tests'.split())
diff --git a/tests/fdx_test.py b/tests/fdx_test.py
index b6e2bc9..497f908 100644
--- a/tests/fdx_test.py
+++ b/tests/fdx_test.py
@@ -3,7 +3,7 @@
# http://www.opensource.org/licenses/mit-license.php
from testcompat import TestCase
-from StringIO import StringIO
+from six import StringIO
from screenplain.export.fdx import write_text
from screenplain.richstring import plain, bold, italic
diff --git a/tests/fountain_test.py b/tests/fountain_test.py
index fff1e48..c31cc66 100644
--- a/tests/fountain_test.py
+++ b/tests/fountain_test.py
@@ -9,7 +9,7 @@ from screenplain.types import (
Slug, Action, Dialog, DualDialog, Transition, Section, PageBreak
)
from screenplain.richstring import plain, italic, empty_string
-from StringIO import StringIO
+from six import StringIO
def parse(lines):
diff --git a/tests/richstring_test.py b/tests/richstring_test.py
index f2bed5e..ee42087 100644
--- a/tests/richstring_test.py
+++ b/tests/richstring_test.py
@@ -3,6 +3,7 @@
# http://www.opensource.org/licenses/mit-license.php
from testcompat import TestCase
+import six
from screenplain.richstring import (
RichString, Segment,
Bold, Italic,
@@ -38,7 +39,7 @@ class RichStringOperatorTests(TestCase):
s = bold('Hello') + plain(' there ') + bold('folks')
self.assertEquals(
u'Hello there folks',
- unicode(s)
+ six.text_type(s)
)
def test_eq(self):