Compare commits
No commits in common. "master" and "1.0.1" have entirely different histories.
|
@ -1,7 +1,3 @@
|
|||
__pycache__
|
||||
*.bak
|
||||
*.swp
|
||||
build/
|
||||
dist/
|
||||
*.egg-info
|
||||
*.token
|
||||
|
|
8
Makefile
8
Makefile
|
@ -1,10 +1,10 @@
|
|||
init:
|
||||
pip3 install --user -r requirements.txt
|
||||
pip install -r requirements.txt
|
||||
|
||||
test:
|
||||
python3 -m unittest -v tests/test_bit.py
|
||||
python3 -m unittest -v tests/test_bits.py
|
||||
python3 -m unittest -v tests/test_bytes.py
|
||||
python -m unittest -v tests/test_bit.py
|
||||
python -m unittest -v tests/test_bits.py
|
||||
python -m unittest -v tests/test_bytes.py
|
||||
|
||||
.PHONY: init test
|
||||
|
||||
|
|
24
README.md
24
README.md
|
@ -1,27 +1,3 @@
|
|||
# Bits
|
||||
|
||||
Helps manage your bits!
|
||||
|
||||
Intended to be compatible with Python 3.6+. Written and tested against Python
|
||||
3.8.5 Ubuntu 20.04.1 LTS.
|
||||
|
||||
If something doesn't work in Python 3.6, it's a bug. Please report any such
|
||||
bugs if they are encountered.
|
||||
|
||||
# PyPI
|
||||
|
||||
View on PyPI at [https://pypi.org/project/binary-bits/](https://pypi.org/project/binary-bits/)
|
||||
|
||||
Install from PyPI:
|
||||
python3 -m pip install binary-bits --upgrade
|
||||
|
||||
Use in your project:
|
||||
```python
|
||||
import bits
|
||||
|
||||
my_bytes = bits.Bytes(bytes.fromhex('deadbeef'))
|
||||
```
|
||||
|
||||
# Similar projects
|
||||
## [py-flags](https://pypi.org/project/py-flags/)
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
from .main import Bit, Bits, Bytes
|
|
@ -1,218 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
============================
|
||||
Binary manipulation classes
|
||||
============================
|
||||
|
||||
Provides classes that assist in managing, comparing, transforming and working
|
||||
with binary data in general.
|
||||
|
||||
-----------------
|
||||
Provided Classes
|
||||
-----------------
|
||||
|
||||
Bit
|
||||
===
|
||||
The Bit is the smallest representation of binary data and represents True or
|
||||
False. This class provides methods to perform binary comparisons and
|
||||
conversions between different types that can be used to represent a single
|
||||
binary value.
|
||||
|
||||
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
|
||||
binary math functions and allows getting and setting the state of individual
|
||||
Bits.
|
||||
|
||||
Bytes
|
||||
=====
|
||||
The Bytes class allows an arbitrary amount of binary data to be represented.
|
||||
The Bytes class provides access to each byte using the Byte class, which
|
||||
further allows all the methods to be used on the binary data provided by the
|
||||
Byte and Bit classes. Bytes can be compared and converted between several
|
||||
different data types.
|
||||
|
||||
"""
|
||||
|
||||
class Bit:
|
||||
"""
|
||||
====
|
||||
Bit
|
||||
====
|
||||
|
||||
A single binary bit representation of True or False
|
||||
|
||||
A Bit object can be compared and converted to int, str, bool and other Bit
|
||||
objects. The Bit class is backed by a single bool variable. This backing
|
||||
bool object is converted as necessary to provide conversions and
|
||||
comparisons with other data types. Keep this in mind as converting an
|
||||
object to Bit and then attempting to convert back to the original object
|
||||
may not yield the exact same value.
|
||||
|
||||
In the case of comparisons, the object being compared is cast as a Bit
|
||||
object, and the two Bit objects are then compared with each other and the
|
||||
result of the comparison is returned.
|
||||
|
||||
----------------
|
||||
Type Conversion
|
||||
----------------
|
||||
The objects that can be converted to and from a Bit include *bool*, *int*,
|
||||
*str* and other *Bit* objects.
|
||||
|
||||
Convert *bool* to *Bit*:
|
||||
.. code-block:: python
|
||||
|
||||
/>/>/> a_bit = Bit(True)
|
||||
/>/>/> a_bit
|
||||
Bit(1)
|
||||
/>/>/>
|
||||
|
||||
Convert *Bit* to *bool*:
|
||||
.. code-block:: python
|
||||
|
||||
/>/>/> a_bool = bool(Bit(True))
|
||||
/>/>/> a_bool
|
||||
True
|
||||
/>/>/> type(a_bool)
|
||||
<class 'bool'>
|
||||
/>/>/>
|
||||
|
||||
Convert *int* to *Bit*:
|
||||
.. code-block:: python
|
||||
|
||||
/>/>/> unset_bit = Bit(0)
|
||||
/>/>/> unset_bit
|
||||
Bit(0)
|
||||
/>/>/> set_bit = Bit(1)
|
||||
/>/>/> set_bit
|
||||
Bit(1)
|
||||
/>/>/>
|
||||
|
||||
Convert *Bit* to *int*:
|
||||
.. code-block:: python
|
||||
|
||||
/>/>/> my_int = int(Bit(1))
|
||||
/>/>/> my_int
|
||||
1
|
||||
/>/>/> type(my_int)
|
||||
<class 'int'>
|
||||
/>/>/>
|
||||
|
||||
.. note:: The only values allowed for *int* are **0** and **1**
|
||||
|
||||
Convert *str* to *Bit*:
|
||||
.. code-block:: python
|
||||
|
||||
/>/>/> unset_bit = Bit("0")
|
||||
/>/>/> unset_bit
|
||||
Bit(0)
|
||||
/>/>/> set_bit = Bit("1")
|
||||
/>/>/> set_bit
|
||||
Bit(1)
|
||||
/>/>/>
|
||||
|
||||
Convert *Bit* to *str*:
|
||||
.. code-block: python
|
||||
|
||||
/>/>/> my_str = str(Bit(False))
|
||||
/>/>/> my_str
|
||||
'0'
|
||||
/>/>/> type(my_str)
|
||||
<class 'str'>
|
||||
/>/>/>
|
||||
|
||||
.. note:: The only *str* characters allowed are a single **0** or **1**
|
||||
|
||||
|
||||
------------
|
||||
Comparisons
|
||||
------------
|
||||
A Bit object can be compared to any object which can be cast to a Bit
|
||||
|
||||
.. code-block: python
|
||||
|
||||
/>/>/> 0 == Bit(False) == Bit("0") == Bit(0) == False
|
||||
True
|
||||
/>/>/>
|
||||
|
||||
.. note:: Comparing an object with a value that cannot be cast to Bit will
|
||||
return a ValueError.
|
||||
|
||||
-----------------
|
||||
Rich Comparisons
|
||||
-----------------
|
||||
Bit objects also support "rich comparisons". For example:
|
||||
|
||||
.. code-block: python
|
||||
|
||||
/>/>/> Bit(False) < 1
|
||||
True
|
||||
/>/>/> Bit(True) <= 1
|
||||
True
|
||||
/>/>/> Bit(False) <= "1"
|
||||
True
|
||||
/>/>/> Bit(False) != True
|
||||
True
|
||||
/>/>/> Bit(True) >= False
|
||||
False
|
||||
/>/>/>
|
||||
|
||||
|
||||
--------
|
||||
Methods
|
||||
--------
|
||||
The action method `toggle` is provided for convenience.
|
||||
|
||||
.. code-block: python
|
||||
|
||||
/>/>/> Bit(False).toggle()
|
||||
Bit(1)
|
||||
/>/>/> Bit(True).toggle() == False
|
||||
True
|
||||
/>/>/> int(Bit(False).toggle())
|
||||
1
|
||||
/>/>/>
|
||||
|
||||
The `set` and `unset` methods are also provided to explicitely set the
|
||||
value of Bit.
|
||||
|
||||
.. code-block: python
|
||||
|
||||
/>/>/> a_bit = Bit(0)
|
||||
/>/>/> a_bit
|
||||
Bit(0)
|
||||
/>/>/> a_bit.set()
|
||||
/>/>/> a_bit
|
||||
Bit(1)
|
||||
/>/>/> a_bit.unset()
|
||||
/>/>/> a_bit
|
||||
Bit(0)
|
||||
/>/>/>
|
||||
|
||||
|
||||
-----------
|
||||
Properties
|
||||
-----------
|
||||
The `lie` property returns the opposite value of Bit as a *bool*. Unlike
|
||||
the `toggle` method, `lie` does not change the value of the Bit.
|
||||
|
||||
.. code-block: python
|
||||
|
||||
/>/>/> Bit(1).lie
|
||||
False
|
||||
/>/>/>
|
||||
|
||||
Provide a flexible bit representation
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, var):
|
||||
"""
|
||||
var: Any supported type
|
||||
Supported types: (bool, int, str, Bit)
|
||||
bool: True or False
|
||||
int: 0 = False, 1 = True
|
||||
str: "0" = False, "1" = True
|
||||
int: 0 = False, anything else = True
|
||||
str: "0" = False, anything else = True
|
||||
Bit: Bit = Bit
|
||||
"""
|
||||
if not isinstance(var, (bool, int, str, Bit)):
|
||||
|
@ -230,22 +28,25 @@ class Bit:
|
|||
self.__bit = var
|
||||
|
||||
def __str__(self):
|
||||
"""Return '0' or '1'"""
|
||||
"""
|
||||
Return "0" or "1"
|
||||
"""
|
||||
return str(int(self.__bit))
|
||||
|
||||
def __int__(self):
|
||||
"""Return 0 or 1"""
|
||||
return int(self.__bit)
|
||||
|
||||
def __bool__(self):
|
||||
"""Returns True or False"""
|
||||
return self.__bit
|
||||
|
||||
def __repr__(self):
|
||||
return f'{self.__class__.__name__}({int(self.__bit)})'
|
||||
|
||||
def __eq__(self, compare):
|
||||
"""compare (self) to any supported object"""
|
||||
"""
|
||||
compare (self) to any supported object
|
||||
"""
|
||||
# Massage compare to bool
|
||||
return bool(self) == bool(Bit(compare))
|
||||
|
||||
def __ne__(self, compare):
|
||||
|
@ -267,257 +68,28 @@ class Bit:
|
|||
return self.__bit
|
||||
|
||||
def __hash__(self):
|
||||
"""Not very useful, but provided nonetheless"""
|
||||
"""
|
||||
Not very useful, but provided nonetheless
|
||||
"""
|
||||
return hash(self.__bit)
|
||||
|
||||
def toggle(self):
|
||||
"""Change the state of (self) to Not (self)"""
|
||||
"""
|
||||
(self) = Not (Self)
|
||||
"""
|
||||
self.__bit = not self.__bit
|
||||
|
||||
def set(self):
|
||||
"""Set Bit to True"""
|
||||
self.__bit = True
|
||||
|
||||
def unset(self):
|
||||
"""Set Bit to False"""
|
||||
self.__bit = False
|
||||
|
||||
@property
|
||||
def lie(self):
|
||||
"""Return the opposite of bit, without changing the state of (self)"""
|
||||
"""
|
||||
Return the opposite of bit, without changing the state of (self)
|
||||
"""
|
||||
return not self.__bit
|
||||
|
||||
|
||||
class Bits:
|
||||
"""
|
||||
=====
|
||||
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'
|
||||
/>/>/>
|
||||
|
||||
Provide bit operation helpers.
|
||||
"""
|
||||
|
||||
def __init__(self, var=0, msb_last=False):
|
||||
|
@ -657,14 +229,6 @@ 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
|
||||
|
||||
|
@ -787,18 +351,27 @@ class Bits:
|
|||
set the value of self to int(var)
|
||||
"""
|
||||
self.__value = 0
|
||||
if isinstance(var, (int, bool, Bits, Bytes, Bit)):
|
||||
var_i = int(var)
|
||||
if var_i < 0 or var_i > 255:
|
||||
if isinstance(var, Bits):
|
||||
self.__value = int(var)
|
||||
elif isinstance(var, int):
|
||||
if var < 0 or var > 255:
|
||||
raise ValueError("Integer must be between 0 and 255")
|
||||
self.__value = var_i
|
||||
self.__value = var
|
||||
elif isinstance(var, bytes):
|
||||
if len(var) == 1:
|
||||
self.__setvalue(ord(var))
|
||||
self.__value = ord(var)
|
||||
elif len(var) > 1:
|
||||
raise ValueError("bytes must be single byte with integer"
|
||||
" value between 0 and 255")
|
||||
elif isinstance(var, (list, str)) and (len(var) <=8):
|
||||
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):
|
||||
# handles: "01010101"
|
||||
# ["0", "1", "0", "1", "0", "1", "0", "1"]
|
||||
# [0, 1, 0, 1, 0, 1, 0, 1]
|
||||
|
@ -809,7 +382,7 @@ class Bits:
|
|||
self.__value += (int(bool(int(var[bit]))) *
|
||||
(2 ** (len(var) - 1 - bit)))
|
||||
else:
|
||||
raise TypeError("Expected compatible object")
|
||||
raise TypeError("Expected object with len <= 8")
|
||||
|
||||
def bin(self, pad=True, reverse=False):
|
||||
"""
|
||||
|
@ -817,12 +390,6 @@ 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 = ""
|
||||
|
@ -921,21 +488,13 @@ class Bits:
|
|||
ret.append(Bit(b))
|
||||
return ret
|
||||
|
||||
def hex(self):
|
||||
"""
|
||||
Return the hex-string representation of self
|
||||
"""
|
||||
return bytes(self).hex()
|
||||
|
||||
|
||||
class Bytes:
|
||||
"""
|
||||
A collection of Bits with convenience methods for working with binary data
|
||||
A colletion of Bits with convenient properties for working with binary data
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
def __init__(self, var=None, byteorder="big", bits=None):
|
||||
def __init__(self, var=None, byteorder="big"):
|
||||
"""
|
||||
var: a supported variant (object)
|
||||
byteorder: notimplemented, mostly ignored
|
||||
|
@ -943,17 +502,11 @@ class Bytes:
|
|||
self.__raw = bytearray(b'')
|
||||
self.__small = False
|
||||
self.__iter = None
|
||||
self.__bit_len = None
|
||||
self.__list_len = None
|
||||
if byteorder.lower() in ["small", "little"]:
|
||||
self.__small = True
|
||||
if var is not None:
|
||||
self.__raw = self.__to_bytearray(var)
|
||||
if bits is None:
|
||||
self.__bit_len = len(self.__raw)
|
||||
else:
|
||||
if not isinstance(bits, (int, Bytes, Bit)):
|
||||
raise TypeError(f"bits argument must be int, not {type(bits)}")
|
||||
self.__bit_len = int(bits)
|
||||
|
||||
def __bytes__(self):
|
||||
"""
|
||||
|
@ -1139,28 +692,6 @@ class Bytes:
|
|||
chop += 1
|
||||
return ret + bin(i)[chop:]
|
||||
|
||||
def hex(self, sep=None, bytes_per_sep=1):
|
||||
"""
|
||||
Return the hex-string representation of self
|
||||
"""
|
||||
hexvalue = bytes(self).hex()
|
||||
if sep is None or bytes_per_sep is None or bytes_per_sep == 0:
|
||||
return hexvalue
|
||||
else:
|
||||
if bytes_per_sep > 1:
|
||||
hexvalue = hexvalue[::-1] # reverse the hex string
|
||||
sep = sep[::-1]
|
||||
i = 0
|
||||
retvalue = ""
|
||||
while i < len(hexvalue):
|
||||
if i > 0:
|
||||
retvalue = retvalue + sep
|
||||
retvalue = retvalue + hexvalue[i:(abs(bytes_per_sep * 2) + i)]
|
||||
i += abs(bytes_per_sep * 2)
|
||||
if bytes_per_sep > 1:
|
||||
retvalue = retvalue[::-1]
|
||||
return retvalue
|
||||
|
||||
@property
|
||||
def bytes(self):
|
||||
return bytes(self)
|
15
dev.md
15
dev.md
|
@ -1,15 +0,0 @@
|
|||
Steps to publish to PyPI:
|
||||
|
||||
1) run `make test`
|
||||
2) update version in setup.py
|
||||
3) Tag the current release in git to match the version in setup.py
|
||||
git tag -a 1.2.0 -m 'Release 1.2.0'
|
||||
4) commit any pending changes to git
|
||||
git add .
|
||||
git commit -m 'commit changes'
|
||||
git push origin 1.2.0
|
||||
5) test publish
|
||||
./publish.sh test
|
||||
6) publish to PyPI
|
||||
./publish.sh pypi
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
# This is currently a stub. TODO: documentation
|
||||
#
|
||||
|
84
publish.sh
84
publish.sh
|
@ -1,84 +0,0 @@
|
|||
#!/bin/sh -
|
||||
|
||||
_FALSE=0
|
||||
_TRUE=1
|
||||
__ScriptName="publish.sh"
|
||||
|
||||
username="__token__"
|
||||
test_token=`cat test.token`
|
||||
pypi_token=`cat pypi.token`
|
||||
|
||||
#--------------------------------------------------------------------------------------------------
|
||||
# Handle command line arguments
|
||||
#--------------------------------------------------------------------------------------------------
|
||||
_USERNAME="__token__"
|
||||
_TOKEN=""
|
||||
_TESTREPO=$_FALSE
|
||||
PTYPE="test"
|
||||
|
||||
|
||||
#--- FUNCTION -----------------------------------------------------------------------------------
|
||||
# NAME: __usage
|
||||
# DESCRIPTION: Display usage information.
|
||||
#--------------------------------------------------------------------------------------------------
|
||||
__usage() {
|
||||
cat << EOT
|
||||
Usage : ${__ScriptName} [options] <publish-type>
|
||||
Options:
|
||||
-h Display this help
|
||||
Publish types:
|
||||
- pypi Publish to pypi
|
||||
- test Publish to test.pypi.org
|
||||
|
||||
EOT
|
||||
} # ---------- end of function __usage ----------
|
||||
|
||||
while getopts ':h' opt
|
||||
do
|
||||
case "${opt}" in
|
||||
h ) __usage; exit 0 ;;
|
||||
\?) echo "Invalid option : $OPTARG"
|
||||
__usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND-1))
|
||||
|
||||
|
||||
# Define publish type
|
||||
if [ "$#" -gt 0 ]; then
|
||||
PTYPE=$1
|
||||
shift
|
||||
fi
|
||||
|
||||
# Check publish type
|
||||
if [ "$(echo "$PTYPE" | grep -E '(pypi|test)')" = "" ]; then
|
||||
echo "Publish type \"$PTYPE\" is invalid..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Set the token
|
||||
_TOKEN=$test_token # Default
|
||||
_REPO="testpypi"
|
||||
if [ "$(echo "$PTYPE" | grep -E '(pypi)')" = "pypi" ]; then
|
||||
_TOKEN=$pypi_token
|
||||
_REPO="pypi"
|
||||
echo "Publishing to PYPI official..."
|
||||
fi
|
||||
|
||||
# Upgrade pip packages
|
||||
python3 -m pip install pip --upgrade
|
||||
python3 -m pip install setuptools --upgrade
|
||||
python3 -m pip install wheel --upgrade
|
||||
python3 -m pip install twine --upgrade
|
||||
python3 -m pip install build --upgrade
|
||||
|
||||
# build current version
|
||||
# note: mkae sure to update setup.py first
|
||||
python3 -m build
|
||||
|
||||
# command: python3 -m twine upload --username $_USERNAME --password $_TOKEN --non-interactive --repository $_REPO dist/*
|
||||
python3 -m twine upload --verbose --username $_USERNAME --password $_TOKEN --non-interactive --repository $_REPO dist/*
|
||||
|
||||
|
22
setup.py
22
setup.py
|
@ -4,30 +4,18 @@ with open("README.md", "r") as fh:
|
|||
long_description = fh.read()
|
||||
|
||||
setuptools.setup(
|
||||
name="binary-bits",
|
||||
version="1.1.1",
|
||||
name="bits",
|
||||
version="1.0.1",
|
||||
author="S Groesz",
|
||||
description="Provide additional methods for working with binary data",
|
||||
description="Make your bits easier to handle",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://github.com/wolfpackmars2/bits",
|
||||
packages=setuptools.find_packages(),
|
||||
classifiers=[
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"License :: OSI Approved "" MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
],
|
||||
python_requires='>=3.6',
|
||||
py_modules=['bits'],
|
||||
package_dir={'': 'src'},
|
||||
test_suite='tests',
|
||||
extras_require={
|
||||
'test': ['coverage'],
|
||||
},
|
||||
project_urls={
|
||||
'Source': 'http://git.groesz.org/Groesz.org/bits/',
|
||||
},
|
||||
)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||
'../src')))
|
||||
'..')))
|
||||
|
||||
from bits import Bit
|
||||
from bits import Bits
|
||||
|
|
|
@ -64,24 +64,6 @@ class TestBit(TestCase):
|
|||
var.toggle()
|
||||
self.assertTrue(var, "Bit(False).toggle() == True")
|
||||
|
||||
def test_set(self):
|
||||
"""Test the set() method"""
|
||||
var = Bit(False)
|
||||
var.set()
|
||||
self.assertTrue(var, "Bit(False).set() == True")
|
||||
var = Bit(True)
|
||||
var.set()
|
||||
self.assertTrue(var, "Bit(True).set() == True")
|
||||
|
||||
def test_unset(self):
|
||||
"""Test the unset() method"""
|
||||
var = Bit(True)
|
||||
var.unset()
|
||||
self.assertFalse(var, "Bit(True).unset() == False")
|
||||
var = Bit(False)
|
||||
var.unset()
|
||||
self.assertFalse(var, "Bit(False).unset() == False")
|
||||
|
||||
def test_errors(self):
|
||||
"""
|
||||
Test that errors are raised for invalid values
|
||||
|
|
|
@ -1,87 +1,46 @@
|
|||
from unittest import TestCase
|
||||
|
||||
from .context import Bit, Bits, Bytes
|
||||
from .context import Bits, Bytes
|
||||
|
||||
class TestBits(TestCase):
|
||||
def setUp(self):
|
||||
self.testObjects = [
|
||||
{"bytes": b'\x7f',
|
||||
"str": "01111111",
|
||||
"bits": "01111111",
|
||||
"int": 127,
|
||||
"hex": "7f",
|
||||
"reverse": 254,
|
||||
"bitsObject": Bits(127),
|
||||
"list": [Bit(0),
|
||||
Bit(1),
|
||||
Bit(1),
|
||||
Bit(1),
|
||||
Bit(1),
|
||||
Bit(1),
|
||||
Bit(1),
|
||||
Bit(1)
|
||||
]
|
||||
"bitsObject": Bits(127)
|
||||
},
|
||||
{"bytes": b'\xcf',
|
||||
"str": "11001111",
|
||||
"bits": "11001111",
|
||||
"int": 207,
|
||||
"hex": "cf",
|
||||
"reverse": 243,
|
||||
"bitsObject": Bits(207),
|
||||
"list": [1, 1, 0, 0, 1, 1, 1, 1]
|
||||
"bitsObject": Bits(207)
|
||||
},
|
||||
{"bytes": b'{',
|
||||
"str": "01111011",
|
||||
"bits": "01111011",
|
||||
"int": 123,
|
||||
"hex": "7b",
|
||||
"reverse": 222,
|
||||
"bitsObject": Bits(123),
|
||||
"list": ["0", "1", "1", "1", "1", "0", "1", "1"]
|
||||
|
||||
"bitsObject": Bits(123)
|
||||
},
|
||||
{"bytes": b'<',
|
||||
"str": "00111100",
|
||||
"bits": "00111100",
|
||||
"int": 60,
|
||||
"hex": "3c",
|
||||
"reverse": 60,
|
||||
"bitsObject": Bits(60),
|
||||
"list": [False,
|
||||
False,
|
||||
True,
|
||||
True,
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
False
|
||||
]
|
||||
"bitsObject": Bits(60)
|
||||
},
|
||||
{"bytes": b'>',
|
||||
"str": "00111110",
|
||||
"bits": "00111110",
|
||||
"int": 62,
|
||||
"hex": "3e",
|
||||
"reverse": 124,
|
||||
"bitsObject": Bits(62),
|
||||
"list": [0,
|
||||
False,
|
||||
"1",
|
||||
1,
|
||||
Bit("1"),
|
||||
Bit(True),
|
||||
Bit(1),
|
||||
Bit(False)
|
||||
]
|
||||
},
|
||||
{"bytes": b'=',
|
||||
"str": "00111101",
|
||||
"int": 61,
|
||||
"hex": "3d",
|
||||
"reverse": 188,
|
||||
"bitsObject": Bits(61),
|
||||
"list": [True, 1, 1, True, Bit(0), Bit(1)]
|
||||
"bitsObject": Bits(62)
|
||||
}
|
||||
]
|
||||
|
||||
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"):
|
||||
|
@ -95,59 +54,58 @@ class TestBits(TestCase):
|
|||
self.assertEqual(len(Bits(0)), 8)
|
||||
|
||||
def test_bytes(self):
|
||||
"""Test conversion to bytes objects"""
|
||||
"""
|
||||
Test conversion to bytes object
|
||||
"""
|
||||
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_hex(self):
|
||||
"""Test conversion to hex"""
|
||||
for testcase in self.testObjects:
|
||||
with self.subTest("testcase[\"hex\"]: " + str(testcase["hex"])):
|
||||
self.assertEqual(testcase["bitsObject"].hex(), testcase["hex"])
|
||||
|
||||
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["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"])))
|
||||
self.assertEqual(s, testcase["bits"])
|
||||
|
||||
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["str"])
|
||||
testcase["bits"])
|
||||
with self.subTest("testcase[\"int\"]: " + str(testcase["int"]) \
|
||||
+ " [without leading zeros]"):
|
||||
self.assertEqual(testcase["bitsObject"].bin(pad=False),
|
||||
testcase["str"].lstrip("0"))
|
||||
testcase["bits"].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["str"][::-1])
|
||||
testcase["bits"][::-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")
|
||||
|
@ -187,7 +145,9 @@ 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"]) \
|
||||
+ " [==]"):
|
||||
|
|
|
@ -19,8 +19,6 @@ class TestBytes(TestCase):
|
|||
test_list_of_ints = []
|
||||
test_list_of_str = []
|
||||
for i in range(0, 100):
|
||||
# Generate 100 random test cases, each with max_value between
|
||||
# 9 and 9,999,999,999,999,999,999,999,999 (25 9's)
|
||||
max_value = int("9" * randint(1, 25))
|
||||
test_value = randint(1, max_value)
|
||||
bitesize = ceil(test_value.bit_length() / 8)
|
||||
|
@ -76,8 +74,7 @@ class TestBytes(TestCase):
|
|||
# print(f"\t\t{testcase}")
|
||||
try:
|
||||
self.assertIsInstance(eval(test), eval(compare),
|
||||
"{test} is instance of {compare}"
|
||||
)
|
||||
f"{test} is instance of {compare}")
|
||||
except:
|
||||
import pdb
|
||||
pdb.set_trace()
|
||||
|
@ -94,43 +91,6 @@ class TestBytes(TestCase):
|
|||
with self.assertRaises(TypeError, msg="Bytes(\"1234\")"):
|
||||
Bytes("1234")
|
||||
|
||||
def test_hex(self):
|
||||
"""
|
||||
Test conversion to hex string
|
||||
"""
|
||||
with self.subTest("Test Bytes.hex() using random values"):
|
||||
for testcase in self.testcases["bytes"]:
|
||||
self.assertEqual(Bytes(testcase).hex(), testcase.hex(),
|
||||
f"Bytes({testcase}).hex() == {testcase}.hex()"
|
||||
)
|
||||
with self.subTest("Test hex() function with known values"):
|
||||
self.assertEqual(Bytes(1234).hex(), "04d2",
|
||||
f"Bytes(1234).hex() == '04d2'")
|
||||
self.assertEqual(Bytes(1234).hex(":"), "04:d2",
|
||||
f"Bytes(1234).hex(':') == '04:d2'")
|
||||
self.assertEqual(Bytes(1234).hex(":", 1), "04:d2",
|
||||
f"Bytes(1234).hex(':', 1) == '04:d2'")
|
||||
with self.subTest("Advanced hex() test"):
|
||||
testcase = b'UUDDLRLRAB'
|
||||
self.assertEqual(Bytes(testcase).hex(), testcase.hex(),
|
||||
f"Bytes({testcase}).hex() == {testcase}.hex()")
|
||||
tests = [['55:55:44:44:4c:52:4c:52:41:42', ":", 1],
|
||||
['55:55:44:44:4c:52:4c:52:41:42', ":", -1],
|
||||
['5555:44444c52:4c524142', ":", 4],
|
||||
['55554444:4c524c52:4142', ":", -4],
|
||||
['55 - 55 - 44 - 44 - 4c - 52 - 4c - 52 - 41 - 42', " - ",
|
||||
1],
|
||||
['55, 55, 44, 44, 4c, 52, 4c, 52, 41, 42', ", ", 1],
|
||||
['5555, 44444c52, 4c524142', ", ", 4],
|
||||
['55554444, 4c524c52, 4142', ", ", -4]
|
||||
]
|
||||
for subt in tests:
|
||||
self.assertEqual(Bytes(testcase).hex(sep=subt[1],
|
||||
bytes_per_sep=subt[2]),
|
||||
subt[0],
|
||||
f"Bytes({testcase}).hex(sep='{subt[1]}', " +
|
||||
f"bytes_per_sep={subt[2]}) == {subt[0]}")
|
||||
|
||||
def test_comparison_operators(self):
|
||||
"""
|
||||
Test the comparison operators with Bytes objects
|
||||
|
|
Loading…
Reference in New Issue