[Python-modules-commits] [bitstruct] 01/04: New upstream version 3.4.0

Brian May bam at debian.org
Wed Jul 12 21:34:46 UTC 2017


This is an automated email from the git hooks/post-receive script.

bam pushed a commit to branch debian/master
in repository bitstruct.

commit 955cf4f43d160d057960252c8356ed97b82f6d01
Author: Brian May <bam at debian.org>
Date:   Thu Jul 13 07:30:34 2017 +1000

    New upstream version 3.4.0
---
 PKG-INFO                    |  16 ++--
 README.rst                  |  14 ++--
 bitstruct.egg-info/PKG-INFO |  16 ++--
 bitstruct.py                | 175 +++++++++++++++++++++++++++++++++-----------
 tests/test_bitstruct.py     | 163 +++++++++++++++++++++++++++++++++++++++--
 5 files changed, 312 insertions(+), 72 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index ce3a6ca..a89dfb5 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: bitstruct
-Version: 3.1.0
+Version: 3.4.0
 Summary: This module performs conversions between Python values and C bit field structs represented as Python byte strings.
 Home-page: https://github.com/eerimoq/bitstruct
 Author: Erik Moqvist, Ilya Petukhov
@@ -59,17 +59,17 @@ Description: |buildstatus|_
             3
         
         An example of packing/unpacking a unsinged integer, a signed integer,
-        a float, a boolean and a byte string:
+        a float, a boolean, a byte string and a string:
         
         .. code-block:: python
         
             >>> from bitstruct import *
-            >>> pack('u5s5f32b1r13', 1, -1, 3.75, True, b'\xff\xff')
-            b'\x0f\xd0\x1c\x00\x00?\xff'
-            >>> unpack('u5s5f32b1r13', b'\x0f\xd0\x1c\x00\x00?\xff')
-            (1, -1, 3.75, True, b'\xff\xf8')
-            >>> calcsize('u5s5f32b1r13')
-            56
+            >>> pack('u5s5f32b1r13t40', 1, -1, 3.75, True, b'\xff\xff', u'hello')
+            b'\x0f\xd0\x1c\x00\x00?\xffhello'
+            >>> unpack('u5s5f32b1r13t40', b'\x0f\xd0\x1c\x00\x00?\xffhello')
+            (1, -1, 3.75, True, b'\xff\xf8', u'hello')
+            >>> calcsize('u5s5f32b1r13t24')
+            80
         
         The same format and values as in the previous example, but using LSB
         (Least Significant Bit) first instead of the default MSB (Most
diff --git a/README.rst b/README.rst
index 79c2353..55d18a6 100644
--- a/README.rst
+++ b/README.rst
@@ -51,17 +51,17 @@ wrapping the result in a named tuple:
     3
 
 An example of packing/unpacking a unsinged integer, a signed integer,
-a float, a boolean and a byte string:
+a float, a boolean, a byte string and a string:
 
 .. code-block:: python
 
     >>> from bitstruct import *
-    >>> pack('u5s5f32b1r13', 1, -1, 3.75, True, b'\xff\xff')
-    b'\x0f\xd0\x1c\x00\x00?\xff'
-    >>> unpack('u5s5f32b1r13', b'\x0f\xd0\x1c\x00\x00?\xff')
-    (1, -1, 3.75, True, b'\xff\xf8')
-    >>> calcsize('u5s5f32b1r13')
-    56
+    >>> pack('u5s5f32b1r13t40', 1, -1, 3.75, True, b'\xff\xff', u'hello')
+    b'\x0f\xd0\x1c\x00\x00?\xffhello'
+    >>> unpack('u5s5f32b1r13t40', b'\x0f\xd0\x1c\x00\x00?\xffhello')
+    (1, -1, 3.75, True, b'\xff\xf8', u'hello')
+    >>> calcsize('u5s5f32b1r13t24')
+    80
 
 The same format and values as in the previous example, but using LSB
 (Least Significant Bit) first instead of the default MSB (Most
diff --git a/bitstruct.egg-info/PKG-INFO b/bitstruct.egg-info/PKG-INFO
index ce3a6ca..a89dfb5 100644
--- a/bitstruct.egg-info/PKG-INFO
+++ b/bitstruct.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: bitstruct
-Version: 3.1.0
+Version: 3.4.0
 Summary: This module performs conversions between Python values and C bit field structs represented as Python byte strings.
 Home-page: https://github.com/eerimoq/bitstruct
 Author: Erik Moqvist, Ilya Petukhov
@@ -59,17 +59,17 @@ Description: |buildstatus|_
             3
         
         An example of packing/unpacking a unsinged integer, a signed integer,
-        a float, a boolean and a byte string:
+        a float, a boolean, a byte string and a string:
         
         .. code-block:: python
         
             >>> from bitstruct import *
-            >>> pack('u5s5f32b1r13', 1, -1, 3.75, True, b'\xff\xff')
-            b'\x0f\xd0\x1c\x00\x00?\xff'
-            >>> unpack('u5s5f32b1r13', b'\x0f\xd0\x1c\x00\x00?\xff')
-            (1, -1, 3.75, True, b'\xff\xf8')
-            >>> calcsize('u5s5f32b1r13')
-            56
+            >>> pack('u5s5f32b1r13t40', 1, -1, 3.75, True, b'\xff\xff', u'hello')
+            b'\x0f\xd0\x1c\x00\x00?\xffhello'
+            >>> unpack('u5s5f32b1r13t40', b'\x0f\xd0\x1c\x00\x00?\xffhello')
+            (1, -1, 3.75, True, b'\xff\xf8', u'hello')
+            >>> calcsize('u5s5f32b1r13t24')
+            80
         
         The same format and values as in the previous example, but using LSB
         (Least Significant Bit) first instead of the default MSB (Most
diff --git a/bitstruct.py b/bitstruct.py
index c02d778..5f1c8b6 100644
--- a/bitstruct.py
+++ b/bitstruct.py
@@ -1,57 +1,82 @@
+from __future__ import print_function
+
 import re
 import struct
 
-__version__ = "3.1.0"
+__version__ = "3.4.0"
 
 
 def _parse_format(fmt):
+    if fmt[-1] in [">", "<"]:
+        byte_order = fmt[-1]
+        fmt = fmt[:-1]
+    else:
+        byte_order = ">"
+
     parsed_infos = re.findall(r'([<>]?)([a-zA-Z])(\d+)', fmt)
 
     # Use big endian as default and use the endianness of the previous
     # value if none is given for the current value.
     infos = []
     endianness = ">"
+
     for info in parsed_infos:
         if info[0] != "":
             endianness = info[0]
+
         infos.append((info[1], int(info[2]), endianness))
-    
-    return infos
+
+    return infos, byte_order
 
 
 def _pack_integer(size, arg):
-    if arg < 0:
-        arg = ((1 << size) + arg)
+    value = int(arg)
+
+    if value < 0:
+        value = ((1 << size) + value)
 
-    return '{{:0{}b}}'.format(size).format(arg)
+    return '{{:0{}b}}'.format(size).format(value)
 
 
 def _pack_boolean(size, arg):
-    return _pack_integer(size, int(arg))
+    value = bool(arg)
+
+    return _pack_integer(size, int(value))
 
 
 def _pack_float(size, arg):
+    value = float(arg)
+
     if size == 32:
-        value = struct.pack('>f', arg)
+        value = struct.pack('>f', value)
     elif size == 64:
-        value = struct.pack('>d', arg)
+        value = struct.pack('>d', value)
     else:
-        raise ValueError('Bad float size {}. Must be 32 or 64 bits.'.format(size))
+        raise ValueError('expected float size of 32 of 64 bits (got {})'.format(
+            size))
+
     return ''.join('{:08b}'.format(b)
                    for b in bytearray(value))
 
 
 def _pack_bytearray(size, arg):
+    value = bytearray(arg)
     bits = ''.join('{:08b}'.format(b)
-                   for b in arg)
+                   for b in value)
 
     return bits[0:size]
 
 
-def _unpack_integer(_type, bits):
+def _pack_text(size, arg):
+    value = arg.encode('utf-8')
+
+    return _pack_bytearray(size, bytearray(value))
+
+
+def _unpack_integer(type_, bits):
     value = int(bits, 2)
 
-    if _type == 's':
+    if type_ == 's':
         if bits[0] == '1':
             value -= (1 << len(bits))
 
@@ -72,7 +97,8 @@ def _unpack_float(size, bits):
     elif size == 64:
         value = struct.unpack('>d', packed)[0]
     else:
-        raise ValueError('Bad float size {}. Must be 32 or 64 bits.'.format(size))
+        raise ValueError('expected float size of 32 of 64 bits (got {})'.format(
+            size))
 
     return value
 
@@ -87,6 +113,10 @@ def _unpack_bytearray(size, bits):
     return value
 
 
+def _unpack_text(size, bits):
+    return _unpack_bytearray(size, bits).decode('utf-8')
+
+
 def pack(fmt, *args):
     """Return a byte string containing the values v1, v2, ... packed
     according to the given format. If the total number of bits are not
@@ -97,53 +127,90 @@ def pack(fmt, *args):
     :param args: Variable argument list of values to pack.
     :returns: A byte string of the packed values.
 
-    `fmt` is a string of bitorder-type-length groups. Bitorder may be
-    omitted.
+    `fmt` is a string of bitorder-type-length groups, and optionally a
+    byteorder identifier afer the groups. Bitorder and byteorder may
+    be omitted.
 
     Bitorder is either ">" or "<", where ">" means MSB first and "<"
     means LSB first. If bitorder is omitted, the previous values'
     bitorder is used for the current value. For example, in the format
     string "u1<u2u3" u1 is MSB first and both u2 and u3 are LSB first.
 
-    There are six types; 'u', 's', 'f', 'b', 'r' and 'p'.
+    Byteorder is either ">" or "<", where ">" means most significant
+    byte first and "<" means least significant byte first. If
+    byteorder is omitted, most significant byte first is used.
+
+    There are seven types; 'u', 's', 'f', 'b', 't', 'r' and 'p'.
 
     - 'u' -- unsigned integer
     - 's' -- signed integer
     - 'f' -- floating point number of 32 or 64 bits
     - 'b' -- boolean
+    - 't' -- text (ascii or utf-8)
     - 'r' -- raw, bytes
     - 'p' -- padding, ignore
 
     Length is the number of bits to pack the value into.
-    
-    Example format string: 'u1u3p7s16'
+
+    Example format string with default bit and byte ordering: 'u1u3p7s16'
+
+    Same format string, but with least significant byte first:
+    'u1u3p7s16<'
+
+    Same format string, but with LSB first ('<' prefix) and least
+    significant byte first ('<' suffix): '<u1u3p7s16<'
 
     """
 
     bits = ''
-    infos = _parse_format(fmt)
+    infos, byte_order = _parse_format(fmt)
     i = 0
 
-    for _type, size, endianness in infos:
-        if _type == 'p':
+    # Sanity check of the number of arguments.
+    number_of_arguments = 0
+
+    for info in infos:
+        if info[0] != 'p':
+            number_of_arguments += 1
+
+    if number_of_arguments > len(args):
+        raise ValueError("pack expected {} item(s) for packing "
+                         "(got {})".format(number_of_arguments, len(args)))
+
+    for type_, size, endianness in infos:
+        if type_ == 'p':
             bits += size * '0'
         else:
-            if _type in 'us':
+            if type_ in 'us':
                 value_bits = _pack_integer(size, args[i])
-            elif _type == 'f':
+            elif type_ == 'f':
                 value_bits = _pack_float(size, args[i])
-            elif _type == 'b':
+            elif type_ == 'b':
                 value_bits = _pack_boolean(size, args[i])
-            elif _type == 'r':
+            elif type_ == 't':
+                value_bits = _pack_text(size, args[i])
+            elif type_ == 'r':
                 value_bits = _pack_bytearray(size, bytearray(args[i]))
             else:
-                raise ValueError("bad type '{}' in format".format(_type))
+                raise ValueError("bad type '{}' in format".format(type_))
 
             # reverse the bit order in little endian values
             if endianness == "<":
                 value_bits = value_bits[::-1]
 
-            bits += value_bits
+            # reverse bytes order for least significant byte first
+            if byte_order == ">":
+                bits += value_bits
+            else:
+                aligned_offset = len(value_bits) - (8 - (len(bits) % 8))
+
+                while aligned_offset > 0:
+                    bits += value_bits[aligned_offset:]
+                    value_bits = value_bits[:aligned_offset]
+                    aligned_offset -= 8
+
+                bits += value_bits
+
             i += 1
 
     # padding of last byte
@@ -167,32 +234,58 @@ def unpack(fmt, data):
     """
 
     bits = ''.join(['{:08b}'.format(b) for b in bytearray(data)])
-    infos = _parse_format(fmt)
+    infos, byte_order = _parse_format(fmt)
+
+    # Sanity check.
+    number_of_bits_to_unpack = sum([size for _, size, _ in infos])
+
+    if number_of_bits_to_unpack > len(bits):
+        raise ValueError("unpack requires at least {} bits to unpack "
+                         "(got {})".format(number_of_bits_to_unpack,
+                                           len(bits)))
+
     res = []
-    i = 0
+    offset = 0
 
-    for _type, size, endianness in infos:
-        if _type == 'p':
+    for type_, size, endianness in infos:
+        if type_ == 'p':
             pass
         else:
-            value_bits = bits[i:i+size]
+            # reverse bytes order for least significant byte first
+            if byte_order == ">":
+                value_bits = bits[offset:offset+size]
+            else:
+                value_bits_tmp = bits[offset:offset+size]
+                aligned_offset = (size - ((offset + size) % 8))
+                value_bits = ''
+
+                while aligned_offset > 0:
+                    value_bits += value_bits_tmp[aligned_offset:aligned_offset+8]
+                    value_bits_tmp = value_bits_tmp[:aligned_offset]
+                    aligned_offset -= 8
+
+                value_bits += value_bits_tmp
 
             # reverse the bit order in little endian values
             if endianness == "<":
                 value_bits = value_bits[::-1]
 
-            if _type in 'us':
-                value = _unpack_integer(_type, value_bits)
-            elif _type == 'f':
+            if type_ in 'us':
+                value = _unpack_integer(type_, value_bits)
+            elif type_ == 'f':
                 value = _unpack_float(size, value_bits)
-            elif _type == 'b':
+            elif type_ == 'b':
                 value = _unpack_boolean(value_bits)
-            elif _type == 'r':
+            elif type_ == 't':
+                value = _unpack_text(size, value_bits)
+            elif type_ == 'r':
                 value = bytes(_unpack_bytearray(size, value_bits))
             else:
-                raise ValueError("bad type '{}' in format".format(_type))
+                raise ValueError("bad type '{}' in format".format(type_))
+
             res.append(value)
-        i += size
+
+        offset += size
 
     return tuple(res)
 
@@ -205,7 +298,7 @@ def calcsize(fmt):
 
     """
 
-    return sum([size for _, size, _ in _parse_format(fmt)])
+    return sum([size for _, size, _ in _parse_format(fmt)[0]])
 
 
 def byteswap(fmt, data, offset = 0):
diff --git a/tests/test_bitstruct.py b/tests/test_bitstruct.py
index 5011f96..27404cf 100644
--- a/tests/test_bitstruct.py
+++ b/tests/test_bitstruct.py
@@ -1,4 +1,6 @@
 import unittest
+import timeit
+import sys
 from bitstruct import *
 
 
@@ -37,6 +39,62 @@ class BitStructTest(unittest.TestCase):
         packed = pack('u5b2u1', -1, False, 1)
         self.assertEqual(packed, b'\xf9')
 
+        packed = pack('b1t24', False, u"Hi!")
+        self.assertEqual(packed, b'$4\x90\x80')
+
+        packed = pack('b1t24', False, "Hi!")
+        self.assertEqual(packed, b'$4\x90\x80')
+
+        # Too many values to pack.
+        try:
+            pack('b1t24', False)
+            self.fail()
+        except ValueError as e:
+            self.assertEqual(
+                str(e),
+                'pack expected 2 item(s) for packing (got 1)')
+
+        # Cannot convert argument to integer.
+        try:
+            pack('u1', "foo")
+            self.fail()
+        except ValueError as e:
+            self.assertEqual(
+                str(e),
+                "invalid literal for int() with base 10: 'foo'")
+
+        # Cannot convert argument to float.
+        try:
+            pack('f32', "foo")
+            self.fail()
+        except ValueError as e:
+            if sys.version_info[0] < 3:
+                self.assertEqual(
+                    str(e),
+                    'could not convert string to float: foo')
+            else:
+                self.assertEqual(
+                    str(e),
+                    "could not convert string to float: 'foo'")
+
+        # Cannot convert argument to bytearray.
+        try:
+            pack('r5', 1.0)
+            self.fail()
+        except TypeError as e:
+            self.assertEqual(
+                str(e),
+                "'float' object is not iterable")
+
+        # Cannot encode argument as utf-8.
+        try:
+            pack('t8', 1.0)
+            self.fail()
+        except AttributeError as e:
+            self.assertEqual(
+                str(e),
+                "'float' object has no attribute 'encode'")
+
     def test_unpack(self):
         """Unpack values.
 
@@ -80,12 +138,27 @@ class BitStructTest(unittest.TestCase):
         unpacked = unpack('u5b2u1', packed)
         self.assertEqual(unpacked, (0, True, 0))
 
-        # bad float size
+        packed = b'$4\x90\x80'
+        unpacked = unpack('b1t24', packed)
+        self.assertEqual(unpacked, (False, u"Hi!"))
+
+        # Bad float size.
         try:
             unpack('f33', b'\x00\x00\x00\x00\x00')
             self.fail()
-        except ValueError:
-            pass
+        except ValueError as e:
+            self.assertEqual(
+                str(e),
+                'expected float size of 32 of 64 bits (got 33)')
+
+        # Too many bits to unpack.
+        try:
+            unpack('u9', b'\x00')
+            self.fail()
+        except ValueError as e:
+            self.assertEqual(
+                str(e),
+                'unpack requires at least 9 bits to unpack (got 8)')
 
         # gcc packed struct with bitfields
         #
@@ -105,8 +178,6 @@ class BitStructTest(unittest.TestCase):
                                    b'\x01\x00\x00\x00\x01\xe7\xa2\x91\x00'))
         self.assertEqual(unpacked, (1, 1, 0x12345, 0x67))
 
-
-
     def test_pack_unpack(self):
         """Pack and unpack values.
 
@@ -137,8 +208,8 @@ class BitStructTest(unittest.TestCase):
         size = calcsize('u1s6u7u9')
         self.assertEqual(size, 23)
 
-        size = calcsize('b1s6u7u9')
-        self.assertEqual(size, 23)
+        size = calcsize('b1s6u7u9p1t8')
+        self.assertEqual(size, 32)
 
     def test_byteswap(self):
         """Byte swap.
@@ -164,7 +235,7 @@ class BitStructTest(unittest.TestCase):
         self.assertEqual(packed, ref)
         unpacked = unpack('>u19s3f32', packed)
         self.assertEqual(unpacked, (0x1234, -2, -1.0))
-        
+
         # little endian
         ref = b'\x2c\x48\x0c\x00\x00\x07\xf4'
         packed = pack('<u19s3f32', 0x1234, -2, -1.0)
@@ -193,6 +264,82 @@ class BitStructTest(unittest.TestCase):
         unpacked = unpack('<u2', packed)
         self.assertEqual(unpacked, (2, ))
 
+    def test_byte_order(self):
+        """Test pack/unpack with byte order information in the format string.
+
+        """
+
+        # most significant byte first (default)
+        ref = b'\x02\x46\x9a\xfe\x00\x00\x00'
+        packed = pack('u19s3f32>', 0x1234, -2, -1.0)
+        self.assertEqual(packed, ref)
+        unpacked = unpack('u19s3f32>', packed)
+        self.assertEqual(unpacked, (0x1234, -2, -1.0))
+
+        # least significant byte first
+        ref = b'\x34\x12\x18\x00\x00\xe0\xbc'
+        packed = pack('u19s3f32<', 0x1234, -2, -1.0)
+        self.assertEqual(packed, ref)
+        unpacked = unpack('u19s3f32<', packed)
+        self.assertEqual(unpacked, (0x1234, -2, -1.0))
+
+        # least significant byte first
+        ref = b'\x34\x12'
+        packed = pack('u8s8<', 0x34, 0x12)
+        self.assertEqual(packed, ref)
+        unpacked = unpack('u8s8<', packed)
+        self.assertEqual(unpacked, (0x34, 0x12))
+
+        # least significant byte first
+        ref = b'\x34\x22'
+        packed = pack('u3u12<', 1, 0x234)
+        self.assertEqual(packed, ref)
+        unpacked = unpack('u3s12<', packed)
+        self.assertEqual(unpacked, (1, 0x234))
+
+        # least significant byte first
+        ref = b'\x34\x11\x00'
+        packed = pack('u3u17<', 1, 0x234)
+        self.assertEqual(packed, ref)
+        unpacked = unpack('u3s17<', packed)
+        self.assertEqual(unpacked, (1, 0x234))
+
+        # least significant byte first
+        ref = b'\x80'
+        packed = pack('u1<', 1)
+        self.assertEqual(packed, ref)
+        unpacked = unpack('u1<', packed)
+        self.assertEqual(unpacked, (1, ))
+
+        # least significant byte first
+        ref = b'\x45\x23\x25\x82'
+        packed = pack('u19u5u1u7<', 0x12345, 5, 1, 2)
+        self.assertEqual(packed, ref)
+        unpacked = unpack('u19u5u1u7<', packed)
+        self.assertEqual(unpacked, (0x12345, 5, 1, 2))
+
+    def test_performance(self):
+        """Test pack/unpack performance.
+
+        """
+
+        time = timeit.timeit("pack('s6u7r40b1t152', "
+                             "-2, 22, b'\x01\x01\x03\x04\x05', "
+                             "True, u'foo fie bar gom gum')",
+                             setup="from bitstruct import pack",
+                             number=50000)
+        print("pack time: {} s ({} s/pack)".format(time, time / 50000))
+
+        time = timeit.timeit("unpack('s6u7r40b1t152', "
+                             "b'\\xf8\\xb0\\x08\\x08\\x18 "
+                             "-\\x99\\xbd\\xbc\\x81\\x99"
+                             "\\xa5\\x94\\x81\\x89\\x85"
+                             "\\xc8\\x81\\x9d\\xbd\\xb4"
+                             "\\x81\\x9d\\xd5\\xb4')",
+                             setup="from bitstruct import unpack",
+                             number=50000)
+        print("unpack time: {} s ({} s/unpack)".format(time, time / 50000))
+
 
 if __name__ == '__main__':
     unittest.main()

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/bitstruct.git



More information about the Python-modules-commits mailing list