add Bit object
This commit is contained in:
		| @@ -1 +1 @@ | ||||
| from .main import Bits, Bytes | ||||
| from .main import Bit, Bits, Bytes | ||||
|   | ||||
							
								
								
									
										133
									
								
								bits/main.py
									
									
									
									
									
								
							
							
						
						
									
										133
									
								
								bits/main.py
									
									
									
									
									
								
							| @@ -1,8 +1,80 @@ | ||||
|  | ||||
| class Bits(int): | ||||
| class Bit: | ||||
|     """ | ||||
|     Provide a flexible bit representation | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, byte): | ||||
|         self.__setvalue(byte) | ||||
|     def __init__(self, var): | ||||
|         if not isinstance(var, (bool, int, str, Bit)): | ||||
|             raise TypeError("Bit must be one of type (int, bool, str)") | ||||
|         if isinstance(var, (int, Bit)): | ||||
|             if var < 0 or var > 1: | ||||
|                 raise ValueError("Bit must be 0 or 1") | ||||
|             self.__bit = bool(var) | ||||
|         elif isinstance(var, str): | ||||
|             if var == "0" or var == "1": | ||||
|                 self.__bit = bool(int(var)) | ||||
|             else: | ||||
|                 raise ValueError('Bit must be "0" or "1"') | ||||
|         elif isinstance(var, bool): | ||||
|             self.__bit = var | ||||
|  | ||||
|     def __str__(self): | ||||
|         return str(int(self.__bit)) | ||||
|  | ||||
|     def __int__(self): | ||||
|         return int(self.__bit) | ||||
|  | ||||
|     def __bool__(self): | ||||
|         return self.__bit | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return f'{self.__class__.__name__}({self.__bit!r})' | ||||
|  | ||||
|     def __eq__(self, compare): | ||||
|         # Massage compare to bool | ||||
|         return bool(self) == bool(Bit(compare)) | ||||
|  | ||||
|     def __ne__(self, compare): | ||||
|         return bool(self) != bool(Bit(compare)) | ||||
|  | ||||
|     def __lt__(self, compare): | ||||
|         return bool(self) < bool(Bit(compare)) | ||||
|  | ||||
|     def __le__(self, compare): | ||||
|         return bool(self) <= bool(Bit(compare)) | ||||
|  | ||||
|     def __gt__(self, compare): | ||||
|         return bool(self) > bool(Bit(compare)) | ||||
|  | ||||
|     def __ge__(self, compare): | ||||
|         return bool(self) >= bool(Bit(compare)) | ||||
|  | ||||
|     def __get__(self, instance, owner): | ||||
|         return self.__bit | ||||
|  | ||||
|     def __hash__(self): | ||||
|         return hash(self.__bit) | ||||
|  | ||||
|     def toggle(self): | ||||
|         self.__bit = not self.__bit | ||||
|  | ||||
|     @property | ||||
|     def lie(self): | ||||
|         """ | ||||
|         Return the opposite of bit, without changing the bit state | ||||
|         """ | ||||
|         return not self.__bit | ||||
|  | ||||
|  | ||||
| class Bits: | ||||
|     """ | ||||
|     Provide bit operation helpers. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, var=0): | ||||
|         self.__value = 0 | ||||
|         self.__setvalue(var) | ||||
|  | ||||
|     def __bytes__(self): | ||||
|         return bytes([self.__value]) | ||||
| @@ -21,6 +93,9 @@ class Bits(int): | ||||
|     def __str__(self): | ||||
|         return self.bin() | ||||
|  | ||||
|     def __ord__(self): | ||||
|         return int(self) | ||||
|  | ||||
|     def __index__(self): | ||||
|         return int(self) | ||||
|  | ||||
| @@ -46,49 +121,46 @@ class Bits(int): | ||||
|         return int(self) >= int(Bits(compare)) | ||||
|  | ||||
|     def __getitem__(self, index): | ||||
|         return bool(int(self.bin()[index])) #self.bool(key) | ||||
|         return self.list()[index] | ||||
|  | ||||
|     def __setitem__(self, index, value): | ||||
|         # we'll take the easy way for now | ||||
|         s = list(self.bin()) | ||||
|         l = self.list() | ||||
|         # str->int->bool->int : accept bool, str, int, return either "0" or "1" | ||||
|         s[index] = str(int(bool(int(value)))) | ||||
|         self.__setvalue("".join(s)) | ||||
|         l[index] = bool(int(value)) | ||||
|         self.__setvalue(l) | ||||
|  | ||||
|     def __get__(self, instance, owner): | ||||
|         return self.bin() | ||||
|  | ||||
|     def __hash__(self): | ||||
|         return None | ||||
|  | ||||
|     def __setvalue(self, value): | ||||
|     def __setvalue(self, var): | ||||
|         self.__value = 0 | ||||
|         if isinstance(value, int): | ||||
|             if value < 0 or value > 255: | ||||
|         if isinstance(var, int): | ||||
|             if var < 0 or var > 255: | ||||
|                 raise ValueError("Integer must be between 0 and 255") | ||||
|             self.__value = value | ||||
|         elif isinstance(value, bytes): | ||||
|             val = 0 | ||||
|             for _byte in value: | ||||
|                 val += _byte | ||||
|                 if val > 255 or val < 0: | ||||
|             self.__value = var | ||||
|         elif isinstance(var, bytes): | ||||
|             for _byte in var: | ||||
|                 self.__value += _byte | ||||
|                 if ret > 255 or ret < 0: | ||||
|                     raise ValueError("Sum value of bytes must be between 0" | ||||
|                                      " and 255") | ||||
|             self.__value = val | ||||
|         elif (len(value) > 0) and (len(value) <=8): | ||||
|             # TODO: apparently, can't use a list when inheriting from 'int' | ||||
|             for bit in range(0, len(value)): | ||||
|                 self.__value += (int(bool(int(value[bit]))) * | ||||
|                                  (2 ** (len(value) - 1 - bit))) | ||||
|         elif (len(var) > 0) and (len(var) <=8): | ||||
|             for bit in range(0, len(var)): | ||||
|                 self.__value += (int(bool(int(var[bit]))) * | ||||
|                                  (2 ** (len(var) - 1 - bit))) | ||||
|         else: | ||||
|             raise TypeError("Expected object with len <= 8") | ||||
|  | ||||
|     def bin(self, pad=True): | ||||
|     def bin(self, pad=True, reverse=False): | ||||
|         bitcount = self.__value.bit_length() | ||||
|         ret = "" | ||||
|         if pad and ((bitcount % 8) > 0): | ||||
|             ret = "0" * (8 - (bitcount % 8)) | ||||
|         return ret + bin(self.__value)[2:] | ||||
|         ret += bin(self.__value)[2:] | ||||
|         if reverse: | ||||
|             ret = ret[::-1] | ||||
|         return ret | ||||
|  | ||||
|     @property | ||||
|     def chr(self): | ||||
| @@ -113,6 +185,13 @@ class Bits(int): | ||||
|     def nibble(self): | ||||
|         return [self.bin()[:4], self.bin()[4:]] | ||||
|  | ||||
|     def list(self, pad=True, reverse=False): | ||||
|         ret = [] | ||||
|         bits = self.bin(pad=pad, reverse=reverse) | ||||
|         for bit in bits: | ||||
|             ret.append(bool(int(bit))) | ||||
|         return ret | ||||
|  | ||||
|  | ||||
| class Bytes(bytearray): | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import sys | ||||
| sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), | ||||
|                                                 '..'))) | ||||
|  | ||||
| from bits import Bit | ||||
| from bits import Bits | ||||
| from bits import Bytes | ||||
|  | ||||
|   | ||||
							
								
								
									
										82
									
								
								tests/test_bit.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								tests/test_bit.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| from unittest import TestCase | ||||
|  | ||||
| from .context import Bit | ||||
|  | ||||
| class TestBit(TestCase): | ||||
|     def setUp(self): | ||||
|         self.true = ["1", 1, True] | ||||
|         self.false = ["0", 0, False] | ||||
|         pass | ||||
|  | ||||
|     def test_init(self): | ||||
|         """ | ||||
|         Test creation of bit object | ||||
|         """ | ||||
|         for t in self.true: | ||||
|             self.assertTrue(Bit(t), f"Bit({t}) == True") | ||||
|         for f in self.false: | ||||
|             self.assertFalse(Bit(f), f"Bit({f}) == False") | ||||
|  | ||||
|     def test_conversion(self): | ||||
|         """ | ||||
|         Test the conversion methods | ||||
|         """ | ||||
|         self.assertEqual(str(Bit(True)), "1", f'str(Bit(True)) == "1"') | ||||
|         self.assertEqual(str(Bit(False)), "0", f'str(Bit(False)) == "0"') | ||||
|         self.assertEqual(int(Bit(True)), 1, f'int(Bit(True)) == 1') | ||||
|         self.assertEqual(int(Bit(False)), 0, f'int(Bit(False)) == 0') | ||||
|         self.assertEqual(bool(Bit(True)), True, f'bool(Bit(True)) == True') | ||||
|         self.assertEqual(bool(Bit(False)), False, f'bool(Bit(False)) == False') | ||||
|  | ||||
|     def test_comparison(self): | ||||
|         """ | ||||
|         Test the comparison operators | ||||
|         """ | ||||
|         self.assertNotEqual(Bit(False), Bit(True), 'Bit(False) != Bit(True)') | ||||
|         self.assertNotEqual(Bit(True), Bit(False), 'Bit(True) != Bit(False)') | ||||
|         self.assertLess(Bit(False), Bit(True), 'Bit(False) < Bit(True)') | ||||
|         self.assertGreater(Bit(True), Bit(False), 'Bit(True) > Bit(False)') | ||||
|         self.assertLessEqual(Bit(False), Bit(True), 'Bit(False) <= Bit(True)') | ||||
|         self.assertLessEqual(Bit(True), Bit(True), 'Bit(True) <= Bit(True)') | ||||
|         self.assertLessEqual(Bit(False), Bit(False), | ||||
|                              'Bit(False) <= Bit(False)') | ||||
|         self.assertGreaterEqual(Bit(True), Bit(False), | ||||
|                                 'Bit(True) >= Bit(False)') | ||||
|         self.assertGreaterEqual(Bit(True), Bit(True), | ||||
|                                 'Bit(True) >= Bit(True)') | ||||
|         self.assertGreaterEqual(Bit(False), Bit(False), | ||||
|                                 'Bit(False) >= Bit(False)') | ||||
|  | ||||
|     def test_lie(self): | ||||
|         """ | ||||
|         Test the lie function/property | ||||
|         """ | ||||
|         self.assertFalse(Bit(True).lie, "Bit(True).lie == False") | ||||
|         self.assertTrue(Bit(False).lie, "Bit(False).lie == True") | ||||
|  | ||||
|     def test_toggle(self): | ||||
|         """ | ||||
|         Test the toggle function | ||||
|         """ | ||||
|         var = Bit(True) | ||||
|         var.toggle() | ||||
|         self.assertFalse(var, "Bit(True).toggle() == False") | ||||
|         var.toggle() | ||||
|         self.assertTrue(var, "Bit(False).toggle() == True") | ||||
|  | ||||
|     def test_errors(self): | ||||
|         """ | ||||
|         Test that errors are raised for invalid values | ||||
|         """ | ||||
|         with self.subTest("Raise TypeError"): | ||||
|             self.assertRaises(TypeError, Bit, 12.7) | ||||
|             self.assertRaises(TypeError, Bit, b'xyz') | ||||
|             self.assertRaises(TypeError, Bit, [True, False]) | ||||
|             self.assertRaises(TypeError, Bit, [True]) | ||||
|  | ||||
|         with self.subTest("Raise ValueError"): | ||||
|             self.assertRaises(ValueError, Bit, "2") | ||||
|             self.assertRaises(ValueError, Bit, 2) | ||||
|             self.assertRaises(ValueError, Bit, -1) | ||||
|             self.assertRaises(ValueError, Bit, "-2") | ||||
|  | ||||
		Reference in New Issue
	
	Block a user