Hello community,
here is the log from the commit of package python-biplist for openSUSE:Factory checked in at 2017-09-04 12:31:28
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-biplist (Old)
and /work/SRC/openSUSE:Factory/.python-biplist.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-biplist"
Mon Sep 4 12:31:28 2017 rev:10 rq:519841 version:1.0.2
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-biplist/python-biplist.changes 2015-04-27 13:05:35.000000000 +0200
+++ /work/SRC/openSUSE:Factory/.python-biplist.new/python-biplist.changes 2017-09-04 12:31:31.036051169 +0200
@@ -1,0 +2,21 @@
+Thu Aug 31 06:07:29 UTC 2017 - toddrme2178@gmail.com
+
+- Update to version 1.0.2
+ * Sort sets and dictionaries by key when writing.
+- Update to version 1.0.1
+ * Adding back in Python 2.6 support. This will be removed again in a future version.
+- Update to version 1.0.0
+ * This release changes the type of `Uid` from a subclass of `int` to a subclass of `object`.
+ * This change was made to address GitHub issue Ints are being turned into Uids and vice versa when both are present in a plist.
+ * This release also bumps the minimum supported Python versions to 2.7 and 3.4.
+- Update to version 0.9.1
+ * Fixes GitHub issue ERROR: testLargeDates (test_valid.TestValidPlistFile)
+ * Fixes Empty Data object converted as empty string
+ * Creates 1-byte strings when possible
+
+-------------------------------------------------------------------
+Thu Aug 24 13:33:20 UTC 2017 - jmatejek@suse.com
+
+- singlespec auto-conversion
+
+-------------------------------------------------------------------
@@ -38,0 +60 @@
+
Old:
----
biplist-0.9.tar.gz
New:
----
biplist-1.0.2.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-biplist.spec ++++++
--- /var/tmp/diff_new_pack.zuKIGu/_old 2017-09-04 12:31:32.275876850 +0200
+++ /var/tmp/diff_new_pack.zuKIGu/_new 2017-09-04 12:31:32.283875725 +0200
@@ -1,7 +1,7 @@
#
# spec file for package python-biplist
#
-# Copyright (c) 2015 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany.
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -15,26 +15,31 @@
# Please submit bugfixes or comments via http://bugs.opensuse.org/
#
+%ifarch x86_64
+%bcond_without test
+%else
+%bcond_with test
+%endif
+%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-biplist
-Version: 0.9
+Version: 1.0.2
Release: 0
-Url: https://bitbucket.org/wooster/biplist
Summary: A library for reading/writing binary plists
License: BSD-3-Clause
Group: Development/Languages/Python
-Source: http://pypi.python.org/packages/source/b/biplist/biplist-%{version}.tar.gz
-BuildRoot: %{_tmppath}/%{name}-%{version}-build
+Url: https://bitbucket.org/wooster/biplist
+Source: https://files.pythonhosted.org/packages/source/b/biplist/biplist-%{version}.tar.gz
+BuildRequires: %{python_module devel}
+BuildRequires: %{python_module setuptools}
BuildRequires: fdupes
-BuildRequires: python-coverage
-BuildRequires: python-devel >= 2.7
-BuildRequires: python-nose
-BuildRequires: python-setuptools
-%if 0%{?suse_version} && 0%{?suse_version} <= 1110
-%{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
-%else
-BuildArch: noarch
+BuildRequires: python-rpm-macros
+%if %{with test}
+BuildRequires: %{python_module coverage}
+BuildRequires: %{python_module nose}
%endif
+BuildArch: noarch
+%python_subpackages
%description
biplist is a binary plist parser/generator for Python.
@@ -47,16 +52,18 @@
%setup -q -n biplist-%{version}
%build
-python setup.py build
+%python_build
%install
-python setup.py install --prefix=%{_prefix} --root=%{buildroot}
-%fdupes %buildroot/%_prefix
+%python_install
+%python_expand %fdupes %{buildroot}%{$python_sitelib}
+%if %{with test}
%check
-python setup.py -q test
+%python_exec setup.py test
+%endif
-%files
+%files %{python_files}
%defattr(-,root,root,-)
%doc AUTHORS LICENSE README.md
%{python_sitelib}/*
++++++ biplist-0.9.tar.gz -> biplist-1.0.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/biplist-0.9/PKG-INFO new/biplist-1.0.2/PKG-INFO
--- old/biplist-0.9/PKG-INFO 2014-10-26 20:09:20.000000000 +0100
+++ new/biplist-1.0.2/PKG-INFO 2017-05-11 01:02:57.000000000 +0200
@@ -1,19 +1,19 @@
Metadata-Version: 1.1
Name: biplist
-Version: 0.9
+Version: 1.0.2
Summary: biplist is a library for reading/writing binary plists.
Home-page: https://bitbucket.org/wooster/biplist
Author: Andrew Wooster
Author-email: andrew@planetaryscale.com
License: BSD
-Download-URL: https://bitbucket.org/wooster/biplist/downloads/biplist-0.9.tar.gz
+Download-URL: https://bitbucket.org/wooster/biplist/downloads/biplist-1.0.2.tar.gz
Description: `biplist` is a binary plist parser/generator for Python.
Binary Property List (plist) files provide a faster and smaller serialization
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 or Python 3.2 or higher.
+ This module requires Python 2.6 or higher or Python 3.4 or higher.
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/biplist-0.9/biplist/__init__.py new/biplist-1.0.2/biplist/__init__.py
--- old/biplist-0.9/biplist/__init__.py 2014-10-26 20:03:11.000000000 +0100
+++ new/biplist-1.0.2/biplist/__init__.py 2017-05-11 00:53:56.000000000 +0200
@@ -44,13 +44,12 @@
print "Not a plist:", e
"""
-import sys
from collections import namedtuple
import datetime
import io
import math
import plistlib
-from struct import pack, unpack
+from struct import pack, unpack, unpack_from
from struct import error as struct_error
import sys
import time
@@ -79,23 +78,41 @@
# Apple uses Jan 1, 2001 as a base for all plist date/times.
apple_reference_date = datetime.datetime.utcfromtimestamp(978307200)
-class Uid(int):
+class Uid(object):
"""Wrapper around integers for representing UID values. This
is used in keyed archiving."""
+ integer = 0
+ def __init__(self, integer):
+ self.integer = integer
+
def __repr__(self):
- return "Uid(%d)" % self
+ return "Uid(%d)" % self.integer
+
+ def __eq__(self, other):
+ if isinstance(self, Uid) and isinstance(other, Uid):
+ return self.integer == other.integer
+ return False
+
+ def __cmp__(self, other):
+ return self.integer - other.integer
+
+ def __lt__(self, other):
+ return self.integer < other.integer
+
+ def __hash__(self):
+ return self.integer
+
+ def __int__(self):
+ return int(self.integer)
class Data(bytes):
- """Wrapper around str types for representing Data values."""
- pass
+ """Wrapper around bytes to distinguish Data values."""
class InvalidPlistException(Exception):
"""Raised when the plist is incorrectly formatted."""
- pass
class NotBinaryPlistException(Exception):
"""Raised when a binary plist was expected but not encountered."""
- pass
def readPlist(pathOrFile):
"""Raises NotBinaryPlistException, InvalidPlistException"""
@@ -379,7 +396,7 @@
def readAsciiString(self, length):
result = unpack("!%ds" % length, self.contents[self.currentOffset:self.currentOffset+length])[0]
self.currentOffset += length
- return result
+ return str(result.decode('ascii'))
def readUnicode(self, length):
actual_length = length*2
@@ -426,7 +443,9 @@
result = int.from_bytes(data, 'big')
else:
for byte in data:
- result = (result << 8) | unpack('>B', byte)[0]
+ if not isinstance(byte, int): # Python3.0-3.1.x return ints, 2.x return str
+ byte = unpack_from('>B', byte)[0]
+ result = (result << 8) | byte
else:
raise InvalidPlistException("Encountered integer longer than 16 bytes.")
return result
@@ -456,6 +475,51 @@
def __repr__(self):
return "" % self.value
+class StringWrapper(object):
+ __instances = {}
+
+ encodedValue = None
+ encoding = None
+
+ def __new__(cls, value):
+ '''Ensure we only have a only one instance for any string,
+ and that we encode ascii as 1-byte-per character when possible'''
+
+ encodedValue = None
+
+ for encoding in ('ascii', 'utf_16_be'):
+ try:
+ encodedValue = value.encode(encoding)
+ except: pass
+ if encodedValue is not None:
+ if encodedValue not in cls.__instances:
+ cls.__instances[encodedValue] = super(StringWrapper, cls).__new__(cls)
+ cls.__instances[encodedValue].encodedValue = encodedValue
+ cls.__instances[encodedValue].encoding = encoding
+ return cls.__instances[encodedValue]
+
+ raise ValueError('Unable to get ascii or utf_16_be encoding for %s' % repr(value))
+
+ def __len__(self):
+ '''Return roughly the number of characters in this string (half the byte length)'''
+ if self.encoding == 'ascii':
+ return len(self.encodedValue)
+ else:
+ return len(self.encodedValue)//2
+
+ def __lt__(self, other):
+ return self.encodedValue < other.encodedValue
+
+ @property
+ def encodingMarker(self):
+ if self.encoding == 'ascii':
+ return 0b0101
+ else:
+ return 0b0110
+
+ def __repr__(self):
+ return '' % (self.encoding, self.encodedValue)
+
class PlistWriter(object):
header = b'bplist00bybiplist1.0'
file = None
@@ -507,10 +571,9 @@
"""
output = self.header
wrapped_root = self.wrapRoot(root)
- should_reference_root = True#not isinstance(wrapped_root, HashableWrapper)
- self.computeOffsets(wrapped_root, asReference=should_reference_root, isRoot=True)
+ self.computeOffsets(wrapped_root, asReference=True, isRoot=True)
self.trailer = self.trailer._replace(**{'objectRefSize':self.intSize(len(self.computedUniques))})
- (_, output) = self.writeObjectReference(wrapped_root, output)
+ self.writeObjectReference(wrapped_root, output)
output = self.writeObject(wrapped_root, output, setReferencePosition=True)
# output size at this point is an upper bound on how big the
@@ -552,6 +615,10 @@
elif isinstance(root, tuple):
n = tuple([self.wrapRoot(value) for value in root])
return HashableWrapper(n)
+ elif isinstance(root, (str, unicode)) and not isinstance(root, Data):
+ return StringWrapper(root)
+ elif isinstance(root, bytes):
+ return Data(root)
else:
return root
@@ -564,7 +631,7 @@
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, (bytes, unicode)):
+ elif not isinstance(key, StringWrapper):
raise InvalidPlistException('Keys must be strings.')
def proc_size(size):
@@ -584,7 +651,7 @@
elif isinstance(obj, BoolWrapper):
self.incrementByteCount('boolBytes')
elif isinstance(obj, Uid):
- size = self.intSize(obj)
+ size = self.intSize(obj.integer)
self.incrementByteCount('uidBytes', incr=1+size)
elif isinstance(obj, (int, long)):
size = self.intSize(obj)
@@ -597,7 +664,7 @@
elif isinstance(obj, Data):
size = proc_size(len(obj))
self.incrementByteCount('dataBytes', incr=1+size)
- elif isinstance(obj, (unicode, bytes)):
+ elif isinstance(obj, StringWrapper):
size = proc_size(len(obj))
self.incrementByteCount('stringBytes', incr=1+size)
elif isinstance(obj, HashableWrapper):
@@ -621,7 +688,7 @@
self.computeOffsets(key, asReference=True)
self.computeOffsets(value, asReference=True)
else:
- raise InvalidPlistException("Unknown object type.")
+ raise InvalidPlistException("Unknown object type: %s (%s)" % (type(obj).__name__, repr(obj)))
def writeObjectReference(self, obj, output):
"""Tries to write an object reference, adding it to the references
@@ -653,9 +720,10 @@
result += pack('!B', (format << 4) | length)
return result
- if isinstance(obj, (str, unicode)) and obj == unicodeEmpty:
- # The Apple Plist decoder can't decode a zero length Unicode string.
- obj = b''
+ def timedelta_total_seconds(td):
+ # Shim for Python 2.6 compatibility, which doesn't have total_seconds.
+ # Make one argument a float to ensure the right calculation.
+ return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10.0**6) / 10.0**6
if setReferencePosition:
self.referencePositions[obj] = len(output)
@@ -668,9 +736,9 @@
else:
output += pack('!B', 0b00001001)
elif isinstance(obj, Uid):
- size = self.intSize(obj)
+ size = self.intSize(obj.integer)
output += pack('!B', (0b1000 << 4) | size - 1)
- output += self.binaryInt(obj)
+ output += self.binaryInt(obj.integer)
elif isinstance(obj, (int, long)):
byteSize = self.intSize(obj)
root = math.log(byteSize, 2)
@@ -681,16 +749,18 @@
output += pack('!B', (0b0010 << 4) | 3)
output += self.binaryReal(obj)
elif isinstance(obj, datetime.datetime):
- timestamp = (obj - apple_reference_date).total_seconds()
+ try:
+ timestamp = (obj - apple_reference_date).total_seconds()
+ except AttributeError:
+ timestamp = timedelta_total_seconds(obj - apple_reference_date)
output += pack('!B', 0b00110011)
output += pack('!d', float(timestamp))
elif isinstance(obj, Data):
output += proc_variable_length(0b0100, len(obj))
output += obj
- elif isinstance(obj, unicode):
- byteData = obj.encode('utf_16_be')
- output += proc_variable_length(0b0110, len(byteData)//2)
- output += byteData
+ elif isinstance(obj, StringWrapper):
+ output += proc_variable_length(obj.encodingMarker, len(obj))
+ output += obj.encodedValue
elif isinstance(obj, bytes):
output += proc_variable_length(0b0101, len(obj))
output += obj
@@ -703,7 +773,7 @@
output += proc_variable_length(0b1010, len(obj))
objectsToWrite = []
- for objRef in obj:
+ for objRef in sorted(obj) if isinstance(obj, set) else obj:
(isNew, output) = self.writeObjectReference(objRef, output)
if isNew:
objectsToWrite.append(objRef)
@@ -714,7 +784,7 @@
keys = []
values = []
objectsToWrite = []
- for key, value in iteritems(obj):
+ for key, value in sorted(iteritems(obj)):
keys.append(key)
values.append(value)
for key in keys:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/biplist-0.9/biplist.egg-info/PKG-INFO new/biplist-1.0.2/biplist.egg-info/PKG-INFO
--- old/biplist-0.9/biplist.egg-info/PKG-INFO 2014-10-26 20:09:20.000000000 +0100
+++ new/biplist-1.0.2/biplist.egg-info/PKG-INFO 2017-05-11 01:02:57.000000000 +0200
@@ -1,19 +1,19 @@
Metadata-Version: 1.1
Name: biplist
-Version: 0.9
+Version: 1.0.2
Summary: biplist is a library for reading/writing binary plists.
Home-page: https://bitbucket.org/wooster/biplist
Author: Andrew Wooster
Author-email: andrew@planetaryscale.com
License: BSD
-Download-URL: https://bitbucket.org/wooster/biplist/downloads/biplist-0.9.tar.gz
+Download-URL: https://bitbucket.org/wooster/biplist/downloads/biplist-1.0.2.tar.gz
Description: `biplist` is a binary plist parser/generator for Python.
Binary Property List (plist) files provide a faster and smaller serialization
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 or Python 3.2 or higher.
+ This module requires Python 2.6 or higher or Python 3.4 or higher.
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/biplist-0.9/setup.py new/biplist-1.0.2/setup.py
--- old/biplist-0.9/setup.py 2014-10-26 20:04:50.000000000 +0100
+++ new/biplist-1.0.2/setup.py 2017-05-11 00:55:28.000000000 +0200
@@ -12,21 +12,21 @@
major, minor, micro, releaselevel, serial = sys.version_info
-if major <= 1 or (major == 2 and minor < 6) or (major == 3 and minor < 2):
+if major <= 1 or (major == 2 and minor < 6) or (major == 3 and minor < 4):
# N.B.: Haven't tested with older py3k versions.
- print('This module supports Python 2 >= 2.6 and Python 3 >= 3.2.')
+ print('This module supports Python 2 >= 2.6 and Python 3 >= 3.4.')
sys.exit(1)
author = 'Andrew Wooster'
email = 'andrew@planetaryscale.com'
-version = '0.9'
+version = '1.0.2'
desc = 'biplist is a library for reading/writing binary plists.'
setup(
name = 'biplist',
version = version,
url = 'https://bitbucket.org/wooster/biplist',
- download_url = 'https://bitbucket.org/wooster/biplist/downloads/biplist-0.9.tar.gz',
+ download_url = 'https://bitbucket.org/wooster/biplist/downloads/biplist-%s.tar.gz' % version,
license = 'BSD',
description = desc,
long_description =
@@ -36,7 +36,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 or Python 3.2 or higher.""",
+This module requires Python 2.6 or higher or Python 3.4 or higher.""",
author = author,
author_email = email,
packages = find_packages(),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/biplist-0.9/tests/test_valid.py new/biplist-1.0.2/tests/test_valid.py
--- old/biplist-0.9/tests/test_valid.py 2014-10-26 20:03:33.000000000 +0100
+++ new/biplist-1.0.2/tests/test_valid.py 2016-06-18 01:44:56.000000000 +0200
@@ -19,19 +19,19 @@
def validateSimpleBinaryRoot(self, root):
self.assertTrue(type(root) == dict, "Root should be dictionary.")
- self.assertTrue(type(root[b'dateItem']) == datetime.datetime, "date should be datetime")
- us = root[b'dateItem'].microsecond
+ self.assertTrue(type(root['dateItem']) == datetime.datetime, "date should be datetime")
+ us = root['dateItem'].microsecond
if us == 385448:
# Python 3 doesn't round microseconds to the nearest value.
- self.assertEqual(root[b'dateItem'], datetime.datetime(2010, 8, 19, 22, 27, 30, 385448), "dates not equal" )
+ self.assertEqual(root['dateItem'], datetime.datetime(2010, 8, 19, 22, 27, 30, 385448), "dates not equal" )
else:
- self.assertEqual(root[b'dateItem'], datetime.datetime(2010, 8, 19, 22, 27, 30, 385449), "dates not equal" )
- self.assertEqual(root[b'numberItem'], -10000000000000000, "number not of expected value")
- self.assertEqual(root[b'unicodeItem'], toUnicode('abc\u212cdef\u2133'))
- self.assertEqual(root[b'stringItem'], b'Hi there')
- self.assertEqual(root[b'realItem'], 0.47)
- self.assertEqual(root[b'boolItem'], True)
- self.assertEqual(root[b'arrayItem'], [b'item0'])
+ self.assertEqual(root['dateItem'], datetime.datetime(2010, 8, 19, 22, 27, 30, 385449), "dates not equal" )
+ self.assertEqual(root['numberItem'], -10000000000000000, "number not of expected value")
+ self.assertEqual(root['unicodeItem'], toUnicode('abc\u212cdef\u2133'))
+ self.assertEqual(root['stringItem'], 'Hi there')
+ self.assertEqual(root['realItem'], 0.47)
+ self.assertEqual(root['boolItem'], True)
+ self.assertEqual(root['arrayItem'], ['item0'])
def testFileRead(self):
try:
@@ -55,17 +55,17 @@
# 0b0101 (ASCII string), so the value being asserted against
# appears to be what is wrong.
result = readPlist(data_path('unicode_empty.plist'))
- self.assertEqual(result, b'')
+ self.assertEqual(result, '')
def testSmallReal(self):
result = readPlist(data_path('small_real.plist'))
- self.assertEqual(result, {b'4 byte real':0.5})
+ self.assertEqual(result, {'4 byte real':0.5})
def testLargeIntegers(self):
result = readPlist(data_path('large_int_limits.plist'))
- self.assertEqual(result[b'Max 8 Byte Unsigned Integer'], 18446744073709551615)
- self.assertEqual(result[b'Min 8 Byte Signed Integer'], -9223372036854775808)
- self.assertEqual(result[b'Max 8 Byte Signed Integer'], 9223372036854775807)
+ self.assertEqual(result['Max 8 Byte Unsigned Integer'], 18446744073709551615)
+ self.assertEqual(result['Min 8 Byte Signed Integer'], -9223372036854775808)
+ self.assertEqual(result['Max 8 Byte Signed Integer'], 9223372036854775807)
def testLargeDates(self):
result = readPlist(data_path("BFPersistentEventInfo.plist"))
@@ -86,15 +86,31 @@
...
"""
result = readPlist(data_path('nskeyedarchiver_example.plist'))
- self.assertEqual(result, {b'$version': 100000,
- b'$objects':
- [b'$null',
- {b'$class': Uid(3), b'somekey': Uid(2)},
- b'object value as string',
- {b'$classes': [b'Archived', b'NSObject'], b'$classname': b'Archived'}
- ],
- b'$top': {b'root': Uid(1)}, b'$archiver': b'NSKeyedArchiver'})
+ self.assertEqual(result, {
+ '$version': 100000,
+ '$objects':
+ [
+ '$null',
+ {'$class':Uid(3), 'somekey':Uid(2)},
+ 'object value as string',
+ {'$classes':['Archived', 'NSObject'], '$classname':'Archived'}
+ ],
+ '$top': {'root':Uid(1)},
+ '$archiver':'NSKeyedArchiver'
+ })
self.assertEqual("Uid(1)", repr(Uid(1)))
+ def testUidComparisons(self):
+ self.assertTrue(Uid(-2) < Uid(-1))
+ self.assertTrue(Uid(-1) < Uid(0))
+ self.assertTrue(Uid(1) > Uid(0))
+ self.assertTrue(Uid(1) > Uid(-2))
+ self.assertTrue(Uid(-1) == Uid(-1))
+ self.assertTrue(Uid(0) == Uid(0))
+ self.assertTrue(Uid(1) == Uid(1))
+
+ self.assertFalse(1 == Uid(1))
+ self.assertFalse(Uid(0) == 0)
+
if __name__ == '__main__':
unittest.main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/biplist-0.9/tests/test_write.py new/biplist-1.0.2/tests/test_write.py
--- old/biplist-0.9/tests/test_write.py 2014-10-26 20:03:11.000000000 +0100
+++ new/biplist-1.0.2/tests/test_write.py 2016-06-18 01:44:56.000000000 +0200
@@ -1,39 +1,82 @@
+#!/usr/local/env python
+# -*- coding: utf-8 -*-
+
+import datetime, io, os, subprocess, sys, tempfile, unittest
+
from biplist import *
from biplist import PlistWriter
-import datetime
-import io
-import os
-#from cStringIO import StringIO
-import subprocess
-import tempfile
from test_utils import *
-import unittest
try:
unicode
+ unicodeStr = lambda x: x.decode('utf-8')
+ toUnicode = lambda x: x.decode('unicode-escape')
except NameError:
unicode = str
+ unicodeStr = lambda x: x
+ toUnicode = lambda x: x
+try:
+ xrange
+except NameError:
+ xrange = range
class TestWritePlist(unittest.TestCase):
- def setUp(self):
- pass
- 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))
+ def roundTrip(self, case, xml=False, expected=None, reprTest=True):
+ # reprTest may fail randomly if True and the values being encoded include a dictionary with more
+ # than one key.
+
+ # convert to plist string
+ plist = writePlistToString(case, binary=(not xml))
self.assertTrue(len(plist) > 0)
+
+ # confirm that lint is happy with the result
+ self.lintPlist(plist)
+
+ # convert back
readResult = readPlistFromString(plist)
- self.assertEqual(readResult, (expected if expected is not None else root))
- self.lintPlist(plist)
-
- def lintPlist(self, plistString):
- if os.path.exists('/usr/bin/plutil'):
- f = tempfile.NamedTemporaryFile()
- f.write(plistString)
- f.flush()
- name = f.name
- (status, output) = run_command(['/usr/bin/plutil', '-lint', name])
+
+ # test equality
+ if reprTest is True:
+ self.assertEqual(repr(case if expected is None else expected), repr(readResult))
+ else:
+ self.assertEqual((case if expected is None else expected), readResult)
+
+ # write to file
+ plistFile = tempfile.NamedTemporaryFile(mode='wb+', suffix='.plist')
+ writePlist(case, plistFile, binary=(xml is False))
+ plistFile.seek(0)
+
+ # confirm that lint is happy with the result
+ self.lintPlist(plistFile)
+
+ # read back from file
+ fileResult = readPlist(plistFile)
+
+ # test equality
+ if reprTest is True:
+ self.assertEqual(repr(case if expected is None else expected), repr(fileResult))
+ else:
+ self.assertEqual((case if expected is None else expected), fileResult)
+
+ def lintPlist(self, plist):
+ if os.access('/usr/bin/plutil', os.X_OK):
+ plistFile = None
+ plistFilePath = None
+
+ if hasattr(plist, 'name'):
+ plistFilePath = plist.name
+ else:
+ if hasattr(plist, 'read'):
+ plistFile = tempfile.NamedTemporaryFile('w%s' % ('b' if 'b' in plist.mode else ''))
+ plistFile.write(plist.read())
+ else:
+ plistFile = tempfile.NamedTemporaryFile('w%s' % ('b' if isinstance(plist, bytes) else ''))
+ plistFile.write(plist)
+ plistFilePath = plistFile.name
+ plistFile.flush()
+
+ status, output = run_command(['/usr/bin/plutil', '-lint', plistFilePath])
if status != 0:
self.fail("plutil verification failed (status %d): %s" % (status, output))
@@ -58,36 +101,32 @@
self.roundTrip(False)
def testDuplicate(self):
- l = ["foo" for i in range(0, 100)]
+ l = ["foo" for i in xrange(0, 100)]
self.roundTrip(l)
def testListRoot(self):
self.roundTrip([1, 2, 3])
def testDictRoot(self):
- self.roundTrip({'a':1, 'B':'d'})
+ self.roundTrip({'a':1, 'B':'d'}, reprTest=False)
def mixedNumericTypesHelper(self, cases):
result = readPlistFromString(writePlistToString(cases))
- for i in range(0, len(cases)):
+ for i in xrange(0, len(cases)):
self.assertTrue(cases[i] == result[i])
self.assertEqual(type(cases[i]), type(result[i]), "Type mismatch on %d: %s != %s" % (i, repr(cases[i]), repr(result[i])))
- def reprChecker(self, case):
- result = readPlistFromString(writePlistToString(case))
- self.assertEqual(repr(case), repr(result))
-
def testBoolsAndIntegersMixed(self):
self.mixedNumericTypesHelper([0, 1, True, False, None])
self.mixedNumericTypesHelper([False, True, 0, 1, None])
- self.reprChecker({unicode('1'):[True, False, 1, 0], unicode('0'):[1, 2, 0, {unicode('2'):[1, 0, False]}]})
- self.reprChecker([1, 1, 1, 1, 1, True, True, True, True])
+ self.roundTrip({'1':[True, False, 1, 0], '0':[1, 2, 0, {'2':[1, 0, False]}]}, reprTest=False)
+ self.roundTrip([1, 1, 1, 1, 1, True, True, True, True])
def testFloatsAndIntegersMixed(self):
self.mixedNumericTypesHelper([0, 1, 1.0, 0.0, None])
self.mixedNumericTypesHelper([0.0, 1.0, 0, 1, None])
- self.reprChecker({unicode('1'):[1.0, 0.0, 1, 0], unicode('0'):[1, 2, 0, {unicode('2'):[1, 0, 0.0]}]})
- self.reprChecker([1, 1, 1, 1, 1, 1.0, 1.0, 1.0, 1.0])
+ self.roundTrip({'1':[1.0, 0.0, 1, 0], '0':[1, 2, 0, {'2':[1, 0, 0.0]}]}, reprTest=False)
+ self.roundTrip([1, 1, 1, 1, 1, 1.0, 1.0, 1.0, 1.0])
def testSetRoot(self):
self.roundTrip(set((1, 2, 3)))
@@ -112,36 +151,102 @@
self.lintPlist(writePlistToString(root))
self.roundTrip(root)
- def testString(self):
+ def testBytes(self):
self.roundTrip(b'0')
self.roundTrip(b'')
- self.roundTrip({b'a':b''})
+
+ self.roundTrip([b'0'])
+ self.roundTrip([b''])
+
+ self.roundTrip({'a': b'0'})
+ self.roundTrip({'a': b''})
- def testLargeDict(self):
- d = {}
- for i in range(0, 1000):
- d['%d' % i] = '%d' % i
- self.roundTrip(d)
+ def testString(self):
+ self.roundTrip('')
+ self.roundTrip('a')
+ self.roundTrip('1')
+
+ self.roundTrip([''])
+ self.roundTrip(['a'])
+ self.roundTrip(['1'])
+
+ self.roundTrip({'a':''})
+ self.roundTrip({'a':'a'})
+ self.roundTrip({'1':'a'})
+ self.roundTrip({'a':'a'})
+ self.roundTrip({'a':'1'})
+
+ def testUnicode(self):
+ # defaulting to 1 byte strings
+ if str != unicode:
+ self.roundTrip(unicodeStr(r''), expected='')
+ self.roundTrip(unicodeStr(r'a'), expected='a')
+
+ self.roundTrip([unicodeStr(r'a')], expected=['a'])
+
+ self.roundTrip({'a':unicodeStr(r'a')}, expected={'a':'a'})
+ self.roundTrip({unicodeStr(r'a'):'a'}, expected={'a':'a'})
+ self.roundTrip({unicodeStr(r''):unicodeStr(r'')}, expected={'':''})
+
+ # TODO: need a 4-byte unicode character
+ self.roundTrip(unicodeStr(r'ü'))
+ self.roundTrip([unicodeStr(r'ü')])
+ self.roundTrip({'a':unicodeStr(r'ü')})
+ self.roundTrip({unicodeStr(r'ü'):'a'})
+
+ self.roundTrip(toUnicode('\u00b6'))
+ self.roundTrip([toUnicode('\u00b6')])
+ self.roundTrip({toUnicode('\u00b6'):toUnicode('\u00b6')})
+
+ self.roundTrip(toUnicode('\u1D161'))
+ self.roundTrip([toUnicode('\u1D161')])
+ self.roundTrip({toUnicode('\u1D161'):toUnicode('\u1D161')})
+
+ # Smiley face emoji
+ self.roundTrip(toUnicode('\U0001f604'))
+ self.roundTrip([toUnicode('\U0001f604'), toUnicode('\U0001f604')])
+ self.roundTrip({toUnicode('\U0001f604'):toUnicode('\U0001f604')})
+
+ def testNone(self):
+ self.roundTrip(None)
+ self.roundTrip({'1':None})
+ self.roundTrip([None, None, None])
+
def testBools(self):
+ self.roundTrip(True)
+ self.roundTrip(False)
+
self.roundTrip([True, False])
+
+ self.roundTrip({'a':True, 'b':False}, reprTest=False)
def testUniques(self):
root = {'hi':'there', 'halloo':'there'}
- self.roundTrip(root)
+ self.roundTrip(root, reprTest=False)
+
+ def testAllEmpties(self):
+ '''Primarily testint that an empty unicode and bytes are not mixed up'''
+ self.roundTrip([unicodeStr(''), '', b'', [], {}], expected=['', '', b'', [], {}])
+ def testLargeDict(self):
+ d = dict((str(x), str(x)) for x in xrange(0, 1000))
+ self.roundTrip(d, reprTest=False)
+
def testWriteToFile(self):
for is_binary in [True, False]:
- path = '/var/tmp/test.plist'
- writePlist([1, 2, 3], path, binary=is_binary)
- self.assertTrue(os.path.exists(path))
- with open(path, 'rb') as f:
- self.lintPlist(f.read())
-
- def testNone(self):
- self.roundTrip(None)
- self.roundTrip({'1':None})
- self.roundTrip([None, None, None])
+ with tempfile.NamedTemporaryFile(mode='w%s' % ('b' if is_binary else ''), suffix='.plist') as plistFile:
+ # clear out the created file
+ os.unlink(plistFile.name)
+ self.assertFalse(os.path.exists(plistFile.name))
+
+ # write to disk
+ writePlist([1, 2, 3], plistFile.name, binary=is_binary)
+ self.assertTrue(os.path.exists(plistFile.name))
+
+ with open(plistFile.name, 'r%s' % ('b' if is_binary else '')) as f:
+ fileContents = f.read()
+ self.lintPlist(fileContents)
def testBadKeys(self):
try:
@@ -169,7 +274,7 @@
-pow(2, 15), pow(2, 15) - 1,
-pow(2, 31), pow(2, 31) - 1,
-pow(2, 63), pow(2, 64) - 1]
- self.roundTrip(edges)
+ self.roundTrip(edges, reprTest=False)
ioBytes = io.BytesIO()
writer = PlistWriter(ioBytes)
@@ -185,7 +290,7 @@
self.assertEqual(bytelen, got, "Byte size is wrong. Expected %d, got %d" % (bytelen, got))
bytes_lists = [list(x) for x in bytes]
- self.roundTrip(bytes_lists)
+ self.roundTrip(bytes_lists, reprTest=False)
try:
self.roundTrip([0x10000000000000000, pow(2, 64)])
@@ -193,17 +298,23 @@
except InvalidPlistException as e:
pass
- def testWriteData(self):
- self.roundTrip(Data(b"woohoo"))
-
- def testUnicode(self):
- unicodeRoot = unicode("Mirror's Edge\u2122 for iPad")
- writePlist(unicodeRoot, "/tmp/odd.plist")
+ def testUnicode2(self):
+ unicodeRoot = toUnicode("Mirror's Edge\u2122 for iPad")
self.roundTrip(unicodeRoot)
- unicodeStrings = [unicode("Mirror's Edge\u2122 for iPad"), unicode('Weightbot \u2014 Track your Weight in Style')]
+ unicodeStrings = [toUnicode("Mirror's Edge\u2122 for iPad"), toUnicode('Weightbot \u2014 Track your Weight in Style')]
self.roundTrip(unicodeStrings)
- self.roundTrip({unicode(""):unicode("")}, expected={b'':b''})
- self.roundTrip(unicode(""), expected=b'')
+ self.roundTrip({toUnicode(""):toUnicode("")}, expected={'':''})
+ self.roundTrip(toUnicode(""), expected='')
+
+ def testWriteData(self):
+ self.roundTrip(Data(b"woohoo"))
+
+ def testEmptyData(self):
+ data = Data(b'')
+ binplist = writePlistToString(data)
+ plist = readPlistFromString(binplist)
+ self.assertEqual(plist, data)
+ self.assertEqual(type(plist), type(data))
def testUidWrite(self):
self.roundTrip({'$version': 100000,
@@ -213,7 +324,14 @@
'object value as string',
{'$classes': ['Archived', 'NSObject'], '$classname': 'Archived'}
],
- '$top': {'root': Uid(1)}, '$archiver': 'NSKeyedArchiver'})
+ '$top': {'root': Uid(1)}, '$archiver': 'NSKeyedArchiver'}, reprTest=False)
+
+ def testUidRoundTrip(self):
+ # Per https://github.com/wooster/biplist/issues/9
+ self.roundTrip(Uid(1))
+ self.roundTrip([Uid(1), 1])
+ self.roundTrip([1, Uid(1)])
+ self.roundTrip([Uid(1), Uid(1)])
if __name__ == '__main__':
unittest.main()