tests passing; improve doc
This commit is contained in:
parent
97f7d58dd7
commit
66aa7ee915
268
src/bits.py
268
src/bits.py
|
@ -18,7 +18,7 @@ False. This class provides methods to perform binary comparisons and
|
|||
conversions between different types that can be used to represent a single
|
||||
binary value.
|
||||
|
||||
Byte
|
||||
Bits
|
||||
====
|
||||
A Byte is the representation of 8 Bits. The Byte class provides methods to
|
||||
compare a single binary Byte to other data types. The Class also provides
|
||||
|
@ -290,7 +290,234 @@ class Bit:
|
|||
|
||||
class Bits:
|
||||
"""
|
||||
Provide bit operation helpers.
|
||||
=====
|
||||
Bits
|
||||
=====
|
||||
|
||||
A single byte of data with helper methods and properties.
|
||||
|
||||
A Bits object provides many helpful methods for working with a byte of
|
||||
information. The backing object is a single int, from which all operations
|
||||
and conversions are performed. A Bits object can be converted and compared
|
||||
to *bytes*, *int*, *str*, *list(of Bit)*, *bool* and other *Bits* objects.
|
||||
The custom object *Bytes* can also be cast to a Bits object, as long as the
|
||||
*Bytes* object has an *int* value >= **0** and <= **255**.
|
||||
|
||||
When comparing or operating on *Bits* objects with objects of a different
|
||||
type, the *operand* object will first be cast to a Bits object. In this
|
||||
way, any object which can be used to create a *Bits* object can also be
|
||||
used to perform operations with *Bits*.
|
||||
|
||||
----------------
|
||||
Type Conversion
|
||||
----------------
|
||||
The objects that can be converted to and from *Bits* include *bytes*,
|
||||
*int*, *str*, *list*, *bool* and other *Bits* objects. When using a list,
|
||||
the length of the list must be 8 or less, each list item must be castable
|
||||
to *int*, and each item will be evaluated as either *True* or *False*.
|
||||
|
||||
Convert *bool* to *Bits*:
|
||||
.. code-block: python
|
||||
|
||||
/>/>/> my_bits = Bits(True)
|
||||
/>/>/> my_bits
|
||||
Bits(1)
|
||||
/>/>/>
|
||||
|
||||
Convert *bytes* to *Bits*:
|
||||
.. code-block: python
|
||||
|
||||
/>/>/> my_bits = Bits(b'\\xab')
|
||||
/>/>/> my_bits
|
||||
Bits(171)
|
||||
/>/>/> my_bits = Bits(bytes([210]))
|
||||
/>/>/> my_bits
|
||||
Bits(210)
|
||||
/>/>/>
|
||||
|
||||
Convert *int* to *Bits*:
|
||||
.. code-block: python
|
||||
|
||||
/>/>/> my_bits = Bits(234)
|
||||
/>/>/> my_bits
|
||||
Bits(234)
|
||||
/>/>/>
|
||||
|
||||
Convert *str* to *Bits*:
|
||||
.. code-block: python
|
||||
|
||||
/>/>/> my_bits = Bits("01010101")
|
||||
/>/>/> my_bits
|
||||
Bits(85)
|
||||
/>/>/> my_bits = Bits("11")
|
||||
/>/>/> my_bits
|
||||
Bits(3)
|
||||
/>/>/>
|
||||
|
||||
Convert *list* to *Bits*:
|
||||
.. code-block: python
|
||||
|
||||
/>/>/> my_bits = Bits(["0", "1", "0", "1", "0", "1", "0", "1"])
|
||||
/>/>/> my_bits
|
||||
Bits(85)
|
||||
/>/>/> my_bits = Bits([0, 1, 0, 1, 0, 1, 0, 1])
|
||||
/>/>/> my_bits
|
||||
Bits(85)
|
||||
/>/>/> my_bits = Bits([False, True, False, True, False, True])
|
||||
/>/>/> my_bits
|
||||
Bits(21)
|
||||
/>/>/> my_bits = Bits([True, True])
|
||||
/>/>/> my_bits
|
||||
Bits(3)
|
||||
/>/>/> my_bits = Bits([0, "1", False, True, "0", Bit(1), Bit(0), 127])
|
||||
/>/>/> my_bits
|
||||
Bits(85)
|
||||
|
||||
.. note:: Each item in a list will be evaluated to either True or False.
|
||||
|
||||
|
||||
-------------
|
||||
Type Casting
|
||||
-------------
|
||||
Bits objects support casting to multiple different types.
|
||||
.. code-block: python
|
||||
|
||||
/>/>/> my_bits = Bits(16)
|
||||
/>/>/> str(my_bits)
|
||||
'00010000'
|
||||
/>/>/> int(my_bits)
|
||||
16
|
||||
/>/>/> bin(my_bits)
|
||||
'0b10000'
|
||||
/>/>/> bool(my_bits)
|
||||
True
|
||||
/>/>/> list(my_bits)
|
||||
[Bit(0), Bit(0), Bit(0), Bit(1), Bit(0), Bit(0), Bit(0), Bit(0)]
|
||||
/>/>/> bytes(my_bits)
|
||||
b'\x10'
|
||||
/>/>/> bytearray([my_bits])
|
||||
bytearray(b'\x10')
|
||||
/>/>/>
|
||||
|
||||
|
||||
-------------
|
||||
Subscripting
|
||||
-------------
|
||||
Subscripting can be used to query or modify each Bit.
|
||||
.. code-block: python
|
||||
|
||||
/>/>/> my_bits = Bits('01101110')
|
||||
/>/>/> my_bits[3]
|
||||
Bit(0)
|
||||
/>/>/> my_bits[3] = True
|
||||
/>/>/> my_bits[3]
|
||||
Bit(1)
|
||||
/>/>/> str(my_bits)
|
||||
'01111110')
|
||||
/>/>/> list(my_bits)
|
||||
[Bit(0), Bit(1), Bit(1), Bit(1), Bit(1), Bit(1), Bit(1), Bit(0)]
|
||||
/>/>/>
|
||||
|
||||
|
||||
------------
|
||||
Comparisons
|
||||
------------
|
||||
Bits objects can be compared to any object which can be cast to a Bits
|
||||
object. This goes both ways.
|
||||
.. code-block: python
|
||||
|
||||
/>/>/> my_bits = Bits('01101110')
|
||||
/>/>/> my_bits
|
||||
Bits(110)
|
||||
/>/>/> 110 == my_bits
|
||||
True
|
||||
/>/>/> 111 == my_bits
|
||||
False
|
||||
/>/>/> 111 > my_bits
|
||||
True
|
||||
/>/>/> my_bits > 112
|
||||
False
|
||||
/>/>/> b'n' == my_bits
|
||||
True
|
||||
/>/>/> my_bits >= b'#'
|
||||
True
|
||||
/>/>/> my_bits >= '10000000'
|
||||
False
|
||||
/>/>/> '10000000' >= my_bits
|
||||
True
|
||||
/>/>/>
|
||||
|
||||
|
||||
--------------------
|
||||
Bit-Masks and Flags
|
||||
--------------------
|
||||
Bits objects support bitmask and flag operations.
|
||||
.. code-block: python
|
||||
|
||||
/>/>/> my_bits = Bits('01101110')
|
||||
/>/>/> my_bits
|
||||
Bits(110)
|
||||
/>/>/> 110 in my_bits
|
||||
True
|
||||
/>/>/> Bits('00000010') in my_bits
|
||||
True
|
||||
/>/>/> pow_2 = [0, 1, 2, 4, 8, 16, 32, 64, 128]
|
||||
/>/>/> for p2 in pow_2:
|
||||
... print(str(Bits(p2)))
|
||||
...
|
||||
00000000
|
||||
00000001
|
||||
00000010
|
||||
00000100
|
||||
00001000
|
||||
00010000
|
||||
00100000
|
||||
01000000
|
||||
10000000
|
||||
/>/>/> for p2 in pow_2:
|
||||
... print(str(p2) + ' in Bits(110): ' + p2 in my_bits)
|
||||
...
|
||||
0 in Bits(110): True
|
||||
1 in Bits(110): False
|
||||
2 in Bits(110): True
|
||||
4 in Bits(110): True
|
||||
8 in Bits(110): True
|
||||
16 in Bits(110): False
|
||||
32 in Bits(110): True
|
||||
64 in Bits(110): True
|
||||
128 in Bits(110): False
|
||||
/>/>/> str(Bits(14))
|
||||
'00001110'
|
||||
/>/>/> 14 in my_bits
|
||||
True
|
||||
/>/>/> (0 + 2 + 4 + 8) in my_bits
|
||||
True
|
||||
/>/>/>
|
||||
|
||||
|
||||
-------------------
|
||||
Bitwise operations
|
||||
-------------------
|
||||
Binary bitwise operations are supported.
|
||||
.. code-block: python
|
||||
|
||||
/>/>/> my_bits = Bits('01101110')
|
||||
/>/>/> my_bits
|
||||
Bits(110)
|
||||
/>/>/> str(my_bits & '0011')
|
||||
'00000010'
|
||||
/>/>/> str(my_bits >> 3)
|
||||
'00001101'
|
||||
/>/>/> str(my_bits << 1)
|
||||
'11011100'
|
||||
/>/>/> str(my_bits|'10111110')
|
||||
'11111110'
|
||||
/>/>/> str(my_bits^'10101010')
|
||||
'11000100'
|
||||
/>/>/> str(~my_bits)
|
||||
'10010001'
|
||||
/>/>/>
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, var=0, msb_last=False):
|
||||
|
@ -430,6 +657,14 @@ class Bits:
|
|||
def __or__(self, other):
|
||||
return Bits(int(self) | int(Bits(other)))
|
||||
|
||||
def __invert__(self):
|
||||
# There's probably a better way to do this...
|
||||
inv = str(self).replace('0', 'x').replace('1', '0').replace('x', '1')
|
||||
return Bits(inv)
|
||||
|
||||
def __neg__(self):
|
||||
return ~ self
|
||||
|
||||
def __radd__(self, other):
|
||||
return Bits(other) + self
|
||||
|
||||
|
@ -552,27 +787,18 @@ class Bits:
|
|||
set the value of self to int(var)
|
||||
"""
|
||||
self.__value = 0
|
||||
if isinstance(var, Bits):
|
||||
self.__value = int(var)
|
||||
elif isinstance(var, int):
|
||||
if var < 0 or var > 255:
|
||||
if isinstance(var, (int, bool, Bits, Bytes, Bit)):
|
||||
var_i = int(var)
|
||||
if var_i < 0 or var_i > 255:
|
||||
raise ValueError("Integer must be between 0 and 255")
|
||||
self.__value = var
|
||||
self.__value = var_i
|
||||
elif isinstance(var, bytes):
|
||||
if len(var) == 1:
|
||||
self.__value = ord(var)
|
||||
self.__setvalue(ord(var))
|
||||
elif len(var) > 1:
|
||||
raise ValueError("bytes must be single byte with integer"
|
||||
" value between 0 and 255")
|
||||
else:
|
||||
self.__value = 0
|
||||
elif isinstance(var, Bytes):
|
||||
if int(var) < 256:
|
||||
self.__value = int(var)
|
||||
else:
|
||||
raise ValueError("Bytes object must be < 256")
|
||||
return list(var)
|
||||
elif (len(var) > 0) and (len(var) <=8):
|
||||
elif isinstance(var, (list, str)) and (len(var) <=8):
|
||||
# handles: "01010101"
|
||||
# ["0", "1", "0", "1", "0", "1", "0", "1"]
|
||||
# [0, 1, 0, 1, 0, 1, 0, 1]
|
||||
|
@ -583,7 +809,7 @@ class Bits:
|
|||
self.__value += (int(bool(int(var[bit]))) *
|
||||
(2 ** (len(var) - 1 - bit)))
|
||||
else:
|
||||
raise TypeError("Expected object with len <= 8")
|
||||
raise TypeError("Expected compatible object")
|
||||
|
||||
def bin(self, pad=True, reverse=False):
|
||||
"""
|
||||
|
@ -591,6 +817,12 @@ class Bits:
|
|||
pad: True to include leading zeros, False to strip leading zeroes
|
||||
reverse: True to return binary string representation of self as stiB.
|
||||
"""
|
||||
if self.__value == 0:
|
||||
if pad:
|
||||
return "0" * 8
|
||||
else:
|
||||
return "0"
|
||||
|
||||
from math import ceil
|
||||
bitcount = self.__value.bit_length()
|
||||
ret = ""
|
||||
|
|
|
@ -1,46 +1,81 @@
|
|||
from unittest import TestCase
|
||||
|
||||
from .context import Bits, Bytes
|
||||
from .context import Bit, Bits, Bytes
|
||||
|
||||
class TestBits(TestCase):
|
||||
def setUp(self):
|
||||
self.testObjects = [
|
||||
{"bytes": b'\x7f',
|
||||
"bits": "01111111",
|
||||
"str": "01111111",
|
||||
"int": 127,
|
||||
"reverse": 254,
|
||||
"bitsObject": Bits(127)
|
||||
"bitsObject": Bits(127),
|
||||
"list": [Bit(0),
|
||||
Bit(1),
|
||||
Bit(1),
|
||||
Bit(1),
|
||||
Bit(1),
|
||||
Bit(1),
|
||||
Bit(1),
|
||||
Bit(1)
|
||||
]
|
||||
},
|
||||
{"bytes": b'\xcf',
|
||||
"bits": "11001111",
|
||||
"str": "11001111",
|
||||
"int": 207,
|
||||
"reverse": 243,
|
||||
"bitsObject": Bits(207)
|
||||
"bitsObject": Bits(207),
|
||||
"list": [1, 1, 0, 0, 1, 1, 1, 1]
|
||||
},
|
||||
{"bytes": b'{',
|
||||
"bits": "01111011",
|
||||
"str": "01111011",
|
||||
"int": 123,
|
||||
"reverse": 222,
|
||||
"bitsObject": Bits(123)
|
||||
"bitsObject": Bits(123),
|
||||
"list": ["0", "1", "1", "1", "1", "0", "1", "1"]
|
||||
|
||||
},
|
||||
{"bytes": b'<',
|
||||
"bits": "00111100",
|
||||
"str": "00111100",
|
||||
"int": 60,
|
||||
"reverse": 60,
|
||||
"bitsObject": Bits(60)
|
||||
"bitsObject": Bits(60),
|
||||
"list": [False,
|
||||
False,
|
||||
True,
|
||||
True,
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
False
|
||||
]
|
||||
},
|
||||
{"bytes": b'>',
|
||||
"bits": "00111110",
|
||||
"str": "00111110",
|
||||
"int": 62,
|
||||
"reverse": 124,
|
||||
"bitsObject": Bits(62)
|
||||
"bitsObject": Bits(62),
|
||||
"list": [0,
|
||||
False,
|
||||
"1",
|
||||
1,
|
||||
Bit("1"),
|
||||
Bit(True),
|
||||
Bit(1),
|
||||
Bit(False)
|
||||
]
|
||||
},
|
||||
{"bytes": b'=',
|
||||
"str": "00111101",
|
||||
"int": 61,
|
||||
"reverse": 188,
|
||||
"bitsObject": Bits(61),
|
||||
"list": [True, 1, 1, True, Bit(0), Bit(1)]
|
||||
}
|
||||
]
|
||||
|
||||
def test_class(self):
|
||||
"""
|
||||
Test various class features
|
||||
"""
|
||||
"""Test various class features"""
|
||||
with self.subTest("Reject values > 255"):
|
||||
self.assertRaises(ValueError, Bits, 256)
|
||||
with self.subTest("Reject values < 0"):
|
||||
|
@ -54,58 +89,53 @@ class TestBits(TestCase):
|
|||
self.assertEqual(len(Bits(0)), 8)
|
||||
|
||||
def test_bytes(self):
|
||||
"""
|
||||
Test conversion to bytes object
|
||||
"""
|
||||
"""Test conversion to bytes objects"""
|
||||
for testcase in self.testObjects:
|
||||
with self.subTest("testcase[\"int\"]: " + str(testcase["int"])):
|
||||
self.assertEqual(bytes(testcase["bitsObject"]),
|
||||
testcase["bytes"])
|
||||
|
||||
def test_int(self):
|
||||
"""
|
||||
Test integer conversion
|
||||
"""
|
||||
"""Test integer conversion"""
|
||||
for testcase in self.testObjects:
|
||||
with self.subTest("testcase[\"int\"]: " + str(testcase["int"])):
|
||||
self.assertEqual(int(testcase["bitsObject"]), testcase["int"])
|
||||
|
||||
def test_str(self):
|
||||
"""
|
||||
Test string representation
|
||||
"""
|
||||
"""Test string representation"""
|
||||
for testcase in self.testObjects:
|
||||
with self.subTest("testcase[\"int\"]: " + str(testcase["int"])):
|
||||
s = str(testcase["bitsObject"])
|
||||
self.assertEqual(s, testcase["bits"])
|
||||
self.assertEqual(s, testcase["str"])
|
||||
|
||||
def test_list(self):
|
||||
"""Test list conversion"""
|
||||
for testcase in self.testObjects:
|
||||
with self.subTest("testcase[\"list\"]: " + str(testcase["list"])):
|
||||
self.assertEqual(testcase["bytes"],
|
||||
bytes(Bits(testcase["list"])))
|
||||
|
||||
def test_bits(self):
|
||||
"""
|
||||
Test bit representation
|
||||
"""
|
||||
"""Test bit representation"""
|
||||
for testcase in self.testObjects:
|
||||
with self.subTest("testcase[\"int\"]: " + str(testcase["int"])):
|
||||
self.assertEqual(testcase["bitsObject"].bin(),
|
||||
testcase["bits"])
|
||||
testcase["str"])
|
||||
with self.subTest("testcase[\"int\"]: " + str(testcase["int"]) \
|
||||
+ " [without leading zeros]"):
|
||||
self.assertEqual(testcase["bitsObject"].bin(pad=False),
|
||||
testcase["bits"].lstrip("0"))
|
||||
testcase["str"].lstrip("0"))
|
||||
|
||||
def test_reverse(self):
|
||||
"""
|
||||
Test the reverse function changes the object bitorder and value
|
||||
"""
|
||||
"""Test the reverse function changes the object bitorder and value"""
|
||||
for testcase in self.testObjects:
|
||||
with self.subTest("testcase[\"int\"]: " + str(testcase["int"])):
|
||||
testcase["bitsObject"].reverse()
|
||||
self.assertEqual(testcase["bitsObject"].bin(),
|
||||
testcase["bits"][::-1])
|
||||
testcase["str"][::-1])
|
||||
|
||||
def test_membership_operators(self):
|
||||
"""
|
||||
Test the membership operator (x in y)
|
||||
"""
|
||||
"""Test the membership operator (x in y)"""
|
||||
with self.subTest("should all be True"):
|
||||
for i in range(1, 256):
|
||||
self.assertTrue(i in Bits(255), f"Bits({i}) in Bits(255) fail")
|
||||
|
@ -145,9 +175,7 @@ class TestBits(TestCase):
|
|||
"Bits(80) and Bits(64) == Bits(64)")
|
||||
|
||||
def test_comparisons(self):
|
||||
"""
|
||||
Test the comparison operators
|
||||
"""
|
||||
"""Test the comparison operators"""
|
||||
for testcase in self.testObjects:
|
||||
with self.subTest("testcase[\"int\"]: " + str(testcase["int"]) \
|
||||
+ " [==]"):
|
||||
|
|
Loading…
Reference in New Issue