diff --git a/src/bits.py b/src/bits.py index 80fd780..a30f51b 100644 --- a/src/bits.py +++ b/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 = "" diff --git a/tests/test_bits.py b/tests/test_bits.py index daefd2e..1ef3486 100644 --- a/tests/test_bits.py +++ b/tests/test_bits.py @@ -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"]) \ + " [==]"):