Source code for memobj.utils
import operator
import struct
import time
from enum import Enum
from typing import Generic, TypeVar
from collections.abc import Callable
# TODO: remove when dropping 3.11 support
T = TypeVar("T")
def wait_for_value(
callback: Callable[[], T],
value: T,
*,
timeout: float | None = None,
sleep_time: float = 0.5,
inverse: bool = False,
) -> tuple[T, float]:
"""Wait for callback to return a value
Args:
callback (Callable[[], T]): the callable to wait for changes from
value (T): the value to wait for
timeout (float | None, optional): optional timeout. Defaults to None.
sleep_time (float, optional): how long to wait between calls. Defaults to 0.5.
inverse (bool, optional): wait for the result to NOT be value instead
Raises:
TimeoutError: if we passed the timeout
Returns:
returns the value and how long we waited for it
"""
elapsed: float = 0.0
current = callback()
if inverse:
comparison = operator.eq
else:
comparison = operator.ne
while comparison(current, value):
time.sleep(sleep_time)
elapsed += sleep_time
if timeout and elapsed > timeout:
raise TimeoutError(f"ran out of time waiting for value {value}")
current = callback()
return current, elapsed
def yield_changes(
callback: Callable[[], T],
*,
amount: int | None = None,
timeout: float | None = None,
sleep_time: float = 0.5,
):
"""Yield values from the callback as they change
Args:
callback (Callable[[], T]): the callable to yield changes from
amount (int | None, optional): the amount of values to yield. Defaults to None.
timeout (float | None, optional): optional timeout. Defaults to None.
sleep_time (float, optional): how long to wait between calls. Defaults to 0.5.
Yields:
T: the values as they change
"""
value = callback()
yield value
results = 1
elapsed: float = 0.0
while True:
if amount and results >= amount:
break
value, per_elapsed = wait_for_value(
callback,
value,
timeout=(timeout - elapsed) if timeout else None,
sleep_time=sleep_time,
inverse=True,
)
yield value
elapsed += per_elapsed
results += 1
[docs]
class ProcessEndianness(Enum):
native = 0
little = 1
big = 2
[docs]
class Type(Enum):
"""
Byte sized based types
"""
# struct calls this char but they're trolling
byte = "c"
bool = "?"
signed1 = "b"
unsigned1 = "B"
signed2 = "h"
unsigned2 = "H"
signed4 = "i"
unsigned4 = "I"
signed8 = "q"
unsigned8 = "Q"
signed_size = "n"
unsigned_size = "N"
float = "f"
double = "d"
[docs]
def get_type_size(type_: Type) -> int:
return struct.calcsize(type_.value)
def align_up(value: int, align: int) -> int:
return align_down(value + (align - 1), align)
def align_down(value: int, align: int) -> int:
return value & -align
def is_aligned(value: int, align: int) -> bool:
return (value & (align - 1)) == 0
def get_alignment(size: int) -> int:
return size & -size
def get_padding(offset: int, align: int) -> int:
return -offset & (align - 1)
def pad_format(format_string: str) -> str:
stripped_endian = ""
if format_string.startswith(("@", "=", "<", ">", "!")):
stripped_endian = format_string[0]
format_string = format_string[1:]
largest = max(struct.calcsize(c) for c in format_string)
aligned_format = ""
offset = 0
for format_char in format_string:
align = struct.calcsize(format_char)
padding = get_padding(offset, align)
offset += align + padding
aligned_format += ("x" * padding) + format_char
aligned_format += "x" * get_padding(offset, largest)
return stripped_endian + aligned_format