Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'master' of git://github.com/kelleyk/biplist into kelley…

…k-master

Conflicts:
	biplist/__init__.py
  • Loading branch information...
commit 4bf73ec538040a4d5bcc4850d9a23666b407d975 2 parents d8319c0 + 31419a5
@wooster authored
View
3  .gitignore
@@ -11,5 +11,8 @@ Icon*
# Emacs noise
*~
+# tox noise
+*.egg
+
# other noise
.svn
View
2  AUTHORS
@@ -1 +1,3 @@
Andrew Wooster (andrew@planetaryscale.com)
+
+Ported to Python 3 by Kevin Kelley (kelleyk@kelleyk.net)
View
85 biplist/__init__.py
@@ -44,8 +44,8 @@
print "Not a plist:", e
"""
+import sys
from collections import namedtuple
-from cStringIO import StringIO
import calendar
import datetime
import math
@@ -54,6 +54,8 @@
import sys
import time
+import six
+
__all__ = [
'Uid', 'Data', 'readPlist', 'writePlist', 'readPlistFromString',
'writePlistToString', 'InvalidPlistException', 'NotBinaryPlistException'
@@ -67,7 +69,7 @@ class Uid(int):
def __repr__(self):
return "Uid(%d)" % self
-class Data(str):
+class Data(six.binary_type):
"""Wrapper around str types for representing Data values."""
pass
@@ -83,18 +85,18 @@ def readPlist(pathOrFile):
"""Raises NotBinaryPlistException, InvalidPlistException"""
didOpen = False
result = None
- if isinstance(pathOrFile, (str, unicode)):
+ if isinstance(pathOrFile, (six.binary_type, six.text_type)):
pathOrFile = open(pathOrFile, 'rb')
didOpen = True
try:
reader = PlistReader(pathOrFile)
result = reader.parse()
- except NotBinaryPlistException, e:
+ except NotBinaryPlistException as e:
try:
pathOrFile.seek(0)
result = plistlib.readPlist(pathOrFile)
result = wrapDataObject(result, for_binary=True)
- except Exception, e:
+ except Exception as e:
raise InvalidPlistException(e)
if didOpen:
pathOrFile.close()
@@ -109,7 +111,7 @@ def wrapDataObject(o, for_binary=False):
o = wrapDataObject(list(o), for_binary)
o = tuple(o)
elif isinstance(o, list):
- for i in xrange(len(o)):
+ for i in range(len(o)):
o[i] = wrapDataObject(o[i], for_binary)
elif isinstance(o, dict):
for k in o:
@@ -122,7 +124,7 @@ def writePlist(rootObject, pathOrFile, binary=True):
return plistlib.writePlist(rootObject, pathOrFile)
else:
didOpen = False
- if isinstance(pathOrFile, (str, unicode)):
+ if isinstance(pathOrFile, (six.binary_type, six.text_type)):
pathOrFile = open(pathOrFile, 'wb')
didOpen = True
writer = PlistWriter(pathOrFile)
@@ -132,14 +134,17 @@ def writePlist(rootObject, pathOrFile, binary=True):
return result
def readPlistFromString(data):
- return readPlist(StringIO(data))
+ return readPlist(six.BytesIO(data))
def writePlistToString(rootObject, binary=True):
if not binary:
rootObject = wrapDataObject(rootObject, binary)
- return plistlib.writePlistToString(rootObject)
+ if six.PY3:
+ return plistlib.writePlistToBytes(rootObject)
+ else:
+ return plistlib.writePlistToString(rootObject)
else:
- io = StringIO()
+ io = six.BytesIO()
writer = PlistWriter(io)
writer.writeRoot(rootObject)
return io.getvalue()
@@ -147,7 +152,7 @@ def writePlistToString(rootObject, binary=True):
def is_stream_binary_plist(stream):
stream.seek(0)
header = stream.read(7)
- if header == 'bplist0':
+ if header == six.b('bplist0'):
return True
else:
return False
@@ -201,7 +206,7 @@ def readRoot(self):
offset_i += 1
self.setCurrentOffsetToObjectNumber(self.trailer.topLevelObjectNumber)
result = self.readObject()
- except TypeError, e:
+ except TypeError as e:
raise InvalidPlistException(e)
return result
@@ -387,7 +392,7 @@ def __repr__(self):
return "<BoolWrapper: %s>" % self.value
class PlistWriter(object):
- header = 'bplist00bybiplist1.0'
+ header = six.b('bplist00bybiplist1.0')
file = None
byteCounts = None
trailer = None
@@ -455,7 +460,7 @@ def writeRoot(self, root):
output = self.writeOffsetTable(output)
output += pack('!xxxxxxBBQQQ', *self.trailer)
self.file.write(output)
-
+
def wrapRoot(self, root):
if isinstance(root, bool):
if root is True:
@@ -469,7 +474,7 @@ def wrapRoot(self, root):
return HashableWrapper(n)
elif isinstance(root, dict):
n = {}
- for key, value in root.iteritems():
+ for key, value in six.iteritems(root):
n[self.wrapRoot(key)] = self.wrapRoot(value)
return HashableWrapper(n)
elif isinstance(root, list):
@@ -492,7 +497,7 @@ def check_key(key):
raise InvalidPlistException('Dictionary keys cannot be null in plists.')
elif isinstance(key, Data):
raise InvalidPlistException('Data cannot be dictionary keys in plists.')
- elif not isinstance(key, (str, unicode)):
+ elif not isinstance(key, (six.binary_type, six.text_type)):
raise InvalidPlistException('Keys must be strings.')
def proc_size(size):
@@ -514,7 +519,7 @@ def proc_size(size):
elif isinstance(obj, Uid):
size = self.intSize(obj)
self.incrementByteCount('uidBytes', incr=1+size)
- elif isinstance(obj, (int, long)):
+ elif isinstance(obj, six.integer_types):
size = self.intSize(obj)
self.incrementByteCount('intBytes', incr=1+size)
elif isinstance(obj, (float)):
@@ -525,7 +530,7 @@ def proc_size(size):
elif isinstance(obj, Data):
size = proc_size(len(obj))
self.incrementByteCount('dataBytes', incr=1+size)
- elif isinstance(obj, (str, unicode)):
+ elif isinstance(obj, (six.text_type, six.binary_type)):
size = proc_size(len(obj))
self.incrementByteCount('stringBytes', incr=1+size)
elif isinstance(obj, HashableWrapper):
@@ -544,7 +549,7 @@ def proc_size(size):
elif isinstance(obj, dict):
size = proc_size(len(obj))
self.incrementByteCount('dictBytes', incr=1+size)
- for key, value in obj.iteritems():
+ for key, value in six.iteritems(obj):
check_key(key)
self.computeOffsets(key, asReference=True)
self.computeOffsets(value, asReference=True)
@@ -573,7 +578,7 @@ def writeObject(self, obj, output, setReferencePosition=False):
object was written.
"""
def proc_variable_length(format, length):
- result = ''
+ result = six.b('')
if length > 0b1110:
result += pack('!B', (format << 4) | 0b1111)
result = self.writeObject(length, result)
@@ -581,10 +586,10 @@ def proc_variable_length(format, length):
result += pack('!B', (format << 4) | length)
return result
- if isinstance(obj, unicode) and obj == u'':
+ if isinstance(obj, six.text_type) and obj == six.u(''):
# The Apple Plist decoder can't decode a zero length Unicode string.
- obj = ''
-
+ obj = six.b('')
+
if setReferencePosition:
self.referencePositions[obj] = len(output)
@@ -599,7 +604,7 @@ def proc_variable_length(format, length):
size = self.intSize(obj)
output += pack('!B', (0b1000 << 4) | size - 1)
output += self.binaryInt(obj)
- elif isinstance(obj, (int, long)):
+ elif isinstance(obj, six.integer_types):
bytes = self.intSize(obj)
root = math.log(bytes, 2)
output += pack('!B', (0b0001 << 4) | int(root))
@@ -616,15 +621,14 @@ def proc_variable_length(format, length):
elif isinstance(obj, Data):
output += proc_variable_length(0b0100, len(obj))
output += obj
- elif isinstance(obj, (str, unicode)):
- if isinstance(obj, unicode):
- bytes = obj.encode('utf_16_be')
- output += proc_variable_length(0b0110, len(bytes)/2)
- output += bytes
- else:
- bytes = obj
- output += proc_variable_length(0b0101, len(bytes))
- output += bytes
+ elif isinstance(obj, six.text_type):
+ bytes = obj.encode('utf_16_be')
+ output += proc_variable_length(0b0110, len(bytes)//2)
+ output += bytes
+ elif isinstance(obj, six.binary_type):
+ bytes = obj
+ output += proc_variable_length(0b0101, len(bytes))
+ output += bytes
elif isinstance(obj, HashableWrapper):
obj = obj.value
if isinstance(obj, (set, list, tuple)):
@@ -645,7 +649,7 @@ def proc_variable_length(format, length):
keys = []
values = []
objectsToWrite = []
- for key, value in obj.iteritems():
+ for key, value in six.iteritems(obj):
keys.append(key)
values.append(value)
for key in keys:
@@ -663,9 +667,16 @@ def proc_variable_length(format, length):
def writeOffsetTable(self, output):
"""Writes all of the object reference offsets."""
all_positions = []
- writtenReferences = self.writtenReferences.items()
- writtenReferences.sort(lambda x,y: cmp(x[1], y[1]))
+ writtenReferences = list(self.writtenReferences.items())
+ writtenReferences.sort(key=lambda x: x[1])
for obj,order in writtenReferences:
+ # Porting note: Elsewhere we deliberately replace empty unicdoe strings
+ # with empty binary strings, but the empty unicode string
+ # goes into writtenReferences. This isn't an issue in Py2
+ # because u'' and b'' have the same hash; but it is in
+ # Py3, where they don't.
+ if six.PY3 and obj == six.u(''):
+ obj = six.b('')
position = self.referencePositions.get(obj)
if position is None:
raise InvalidPlistException("Error while writing offsets table. Object not found. %s" % obj)
@@ -679,7 +690,7 @@ def binaryReal(self, obj):
return result
def binaryInt(self, obj, bytes=None):
- result = ''
+ result = six.b('')
if bytes is None:
bytes = self.intSize(obj)
if bytes == 1:
View
19 setup.py
@@ -12,23 +12,20 @@
major, minor, micro, releaselevel, serial = sys.version_info
-if major == 2 and minor < 6:
- print("Python >= 2.6 is required to use this module.")
- sys.exit(1)
-elif major >= 3:
- print("There is no support for Python 3 yet in this module.")
+if major <= 1 or (major == 2 and minor < 6) or (major == 3 and minor < 2):
+ # N.B.: Haven't tested with older py3k versions.
+ print('This module supports Python 2 >= 2.6 and Python 3 >= 3.2.')
sys.exit(1)
author = 'Andrew Wooster'
email = 'andrew@planetaryscale.com'
-version = '0.4'
+version = '0.5'
desc = 'biplist is a library for reading/writing binary plists.'
setup(
name = 'biplist',
version = version,
- url = 'https://github.com/wooster/biplist',
- download_url = 'https://github.com/wooster/biplist/downloads/biplist-0.4.tar.gz',
+ url = 'https://github.com/kelleyk/py3k-biplist',
license = 'BSD',
description = desc,
long_description =
@@ -38,7 +35,7 @@
format for property lists on OS X. This is a library for generating binary
plists which can be read by OS X, iOS, or other clients.
-This module requires Python 2.6 or higher.""",
+This module requires Python 2.6 or higher or Python 3.2 or higher.""",
author = author,
author_email = email,
packages = find_packages(),
@@ -55,6 +52,6 @@
],
setup_requires = ['nose', 'coverage'],
test_suite = 'nose.collector',
- install_requires = [
- ],
+ install_requires = ['six'],
+ requires = ['six'],
)
View
4 tests/test_invalid.py
@@ -17,14 +17,14 @@ def testEmptyFile(self):
def testTooShort(self):
try:
- readPlistFromString("bplist0")
+ readPlistFromString(six.b("bplist0"))
self.fail("Should not successfully read plist which is too short.")
except InvalidPlistException as e:
pass
def testInvalid(self):
try:
- readPlistFromString("bplist0-------------------------------------")
+ readPlistFromString(six.b("bplist0-------------------------------------"))
self.fail("Should not successfully read invalid plist.")
except InvalidPlistException as e:
pass
View
3  tests/test_utils.py
@@ -1,6 +1,7 @@
import os
import subprocess
import sys
+import six
def data_path(path):
return os.path.join(os.path.dirname(globals()["__file__"]), 'data', path)
@@ -12,7 +13,7 @@ def run_command(args, verbose = False):
p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
stdin, stdout = (p.stdin, p.stdout)
output = stdout.read()
- output = output.strip("\n")
+ output = output.strip(six.b("\n"))
status = stdin.close()
p.wait()
return p.returncode, output
View
44 tests/test_valid.py
@@ -3,6 +3,7 @@
import os
from test_utils import *
import unittest
+import six
class TestValidPlistFile(unittest.TestCase):
def setUp(self):
@@ -10,14 +11,14 @@ def setUp(self):
def validateSimpleBinaryRoot(self, root):
self.assertTrue(type(root) == dict, "Root should be dictionary.")
- self.assertTrue(type(root['dateItem']) == datetime.datetime, "date should be datetime")
- self.assertEquals(root['dateItem'], datetime.datetime(2010, 8, 19, 22, 27, 30, 385449), "dates not equal" )
- self.assertEquals(root['numberItem'], -10000000000000000L, "number not of expected value")
- self.assertEquals(root['unicodeItem'], u'abc\u212cdef\u2133')
- self.assertEquals(root['stringItem'], 'Hi there')
- self.assertEquals(root['realItem'], 0.47)
- self.assertEquals(root['boolItem'], True)
- self.assertEquals(root['arrayItem'], ['item0'])
+ self.assertTrue(type(root[six.b('dateItem')]) == datetime.datetime, "date should be datetime")
+ self.assertEquals(root[six.b('dateItem')], datetime.datetime(2010, 8, 19, 22, 27, 30, 385449), "dates not equal" )
+ self.assertEquals(root[six.b('numberItem')], -10000000000000000, "number not of expected value")
+ self.assertEquals(root[six.b('unicodeItem')], six.u('abc\u212cdef\u2133'))
+ self.assertEquals(root[six.b('stringItem')], six.b('Hi there'))
+ self.assertEquals(root[six.b('realItem')], 0.47)
+ self.assertEquals(root[six.b('boolItem')], True)
+ self.assertEquals(root[six.b('arrayItem')], [six.b('item0')])
def testFileRead(self):
try:
@@ -30,15 +31,22 @@ def testFileRead(self):
def testUnicodeRoot(self):
result = readPlist(data_path('unicode_root.plist'))
- self.assertEquals(result, u"Mirror's Edge\u2122 for iPad")
+ self.assertEquals(result, six.u("Mirror's Edge\u2122 for iPad"))
def testEmptyUnicodeRoot(self):
+ # Porting note: this test was tricky; it was only passing in
+ # Python 2 because the empty byte-string returned by
+ # readPlist() is considered equal to the empty unicode-string
+ # in the assertion. Confusingly enough, given the name of the
+ # test, the value in unicode_empty.plist has the format byte
+ # 0b0101 (ASCII string), so the value being asserted against
+ # appears to be what is wrong.
result = readPlist(data_path('unicode_empty.plist'))
- self.assertEquals(result, u"")
+ self.assertEquals(result, six.b(""))
def testSmallReal(self):
result = readPlist(data_path('small_real.plist'))
- self.assertEquals(result, {'4 byte real':0.5})
+ self.assertEquals(result, {six.b('4 byte real'):0.5})
def testKeyedArchiverPlist(self):
"""
@@ -55,14 +63,14 @@ def testKeyedArchiverPlist(self):
...
"""
result = readPlist(data_path('nskeyedarchiver_example.plist'))
- self.assertEquals(result, {'$version': 100000,
- '$objects':
- ['$null',
- {'$class': Uid(3), 'somekey': Uid(2)},
- 'object value as string',
- {'$classes': ['Archived', 'NSObject'], '$classname': 'Archived'}
+ self.assertEquals(result, {six.b('$version'): 100000,
+ six.b('$objects'):
+ [six.b('$null'),
+ {six.b('$class'): Uid(3), six.b('somekey'): Uid(2)},
+ six.b('object value as string'),
+ {six.b('$classes'): [six.b('Archived'), six.b('NSObject')], six.b('$classname'): six.b('Archived')}
],
- '$top': {'root': Uid(1)}, '$archiver': 'NSKeyedArchiver'})
+ six.b('$top'): {six.b('root'): Uid(1)}, six.b('$archiver'): six.b('NSKeyedArchiver')})
self.assertEquals("Uid(1)", repr(Uid(1)))
if __name__ == '__main__':
View
39 tests/test_write.py
@@ -2,21 +2,24 @@
from biplist import PlistWriter
import datetime
import os
-from cStringIO import StringIO
+#from cStringIO import StringIO
import subprocess
import tempfile
from test_utils import *
import unittest
+import six
class TestWritePlist(unittest.TestCase):
def setUp(self):
pass
- def roundTrip(self, root, xml=False):
+ def roundTrip(self, root, xml=False, expected=None):
+ # 'expected' is more fallout from the
+ # don't-write-empty-unicode-strings issue.
plist = writePlistToString(root, binary=(not xml))
self.assertTrue(len(plist) > 0)
readResult = readPlistFromString(plist)
- self.assertEquals(readResult, root)
+ self.assertEquals(readResult, (expected if expected is not None else root))
self.lintPlist(plist)
def lintPlist(self, plistString):
@@ -34,13 +37,13 @@ def testXMLPlist(self):
def testXMLPlistWithData(self):
for binmode in (True, False):
- binplist = writePlistToString({'data': Data('\x01\xac\xf0\xff')}, binary=binmode)
+ binplist = writePlistToString({'data': Data(six.b('\x01\xac\xf0\xff'))}, binary=binmode)
plist = readPlistFromString(binplist)
self.assertTrue(isinstance(plist['data'], Data), \
"unable to encode then decode Data into %s plist" % ("binary" if binmode else "XML"))
def testConvertToXMLPlistWithData(self):
- binplist = writePlistToString({'data': Data('\x01\xac\xf0\xff')})
+ binplist = writePlistToString({'data': Data(six.b('\x01\xac\xf0\xff'))})
plist = readPlistFromString(binplist)
xmlplist = writePlistToString(plist, binary=False)
self.assertTrue(len(xmlplist) > 0, "unable to convert plist with Data from binary to XML")
@@ -50,7 +53,7 @@ def testBoolRoot(self):
self.roundTrip(False)
def testDuplicate(self):
- l = ["foo" for i in xrange(0, 100)]
+ l = ["foo" for i in range(0, 100)]
self.roundTrip(l)
def testListRoot(self):
@@ -99,13 +102,13 @@ def testComplicated(self):
self.roundTrip(root)
def testString(self):
- self.roundTrip('0')
- self.roundTrip('')
- self.roundTrip({'a':''})
+ self.roundTrip(six.b('0'))
+ self.roundTrip(six.b(''))
+ self.roundTrip({six.b('a'):six.b('')})
def testLargeDict(self):
d = {}
- for i in xrange(0, 1000):
+ for i in range(0, 1000):
d['%d' % i] = '%d' % i
self.roundTrip(d)
@@ -121,7 +124,7 @@ def testWriteToFile(self):
path = '/var/tmp/test.plist'
writePlist([1, 2, 3], path, binary=is_binary)
self.assertTrue(os.path.exists(path))
- self.lintPlist(open(path).read())
+ self.lintPlist(open(path, 'rb').read())
def testNone(self):
self.roundTrip(None)
@@ -135,7 +138,7 @@ def testBadKeys(self):
except InvalidPlistException as e:
pass
try:
- self.roundTrip({Data("hello world"):1})
+ self.roundTrip({Data(six.b("hello world")):1})
self.fail("Data is not a valid key in Cocoa.")
except InvalidPlistException as e:
pass
@@ -153,7 +156,7 @@ def testIntBoundaries(self):
edges = [-pow(2, 7), pow(2, 7) - 1, -pow(2, 15), pow(2, 15) - 1, -pow(2, 31), pow(2, 31) - 1]
self.roundTrip(edges)
- io = StringIO()
+ io = six.BytesIO()
writer = PlistWriter(io)
bytes = [(1, [pow(2, 7) - 1]),
(2, [pow(2, 15) - 1]),
@@ -175,16 +178,16 @@ def testIntBoundaries(self):
pass
def testWriteData(self):
- self.roundTrip(Data("woohoo"))
+ self.roundTrip(Data(six.b("woohoo")))
def testUnicode(self):
- unicodeRoot = u"Mirror's Edge\u2122 for iPad"
+ unicodeRoot = six.u("Mirror's Edge\u2122 for iPad")
writePlist(unicodeRoot, "/tmp/odd.plist")
self.roundTrip(unicodeRoot)
- unicodeStrings = [u"Mirror's Edge\u2122 for iPad", u'Weightbot \u2014 Track your Weight in Style']
+ unicodeStrings = [six.u("Mirror's Edge\u2122 for iPad"), six.u('Weightbot \u2014 Track your Weight in Style')]
self.roundTrip(unicodeStrings)
- self.roundTrip({u"":u""})
- self.roundTrip(u"")
+ self.roundTrip({six.u(""):six.u("")}, expected={six.b(''):six.b('')})
+ self.roundTrip(six.u(""), expected=six.b(''))
def testUidWrite(self):
self.roundTrip({'$version': 100000,
View
5 tox.ini
@@ -0,0 +1,5 @@
+[tox]
+envlist = py26, py27, py32
+[testenv]
+deps = nose
+commands = nosetests
Please sign in to comment.
Something went wrong with that request. Please try again.