tests passing; improve doc

This commit is contained in:
S Groesz 2020-11-23 07:51:45 +00:00
parent 97f7d58dd7
commit 66aa7ee915
2 changed files with 317 additions and 57 deletions

View File

@ -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 = ""

View File

@ -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"]) \
+ " [==]"):