abschlussarbeit/_trash/lib/python3.11/site-packages/pyftdi/misc.py

349 lines
12 KiB
Python

# Copyright (c) 2010-2024 Emmanuel Blot <emmanuel.blot@free.fr>
# Copyright (c) 2008-2016, Neotion
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Miscellaneous helpers"""
# pylint: disable=invalid-name
# pylint: disable=import-outside-toplevel
from array import array
from copy import deepcopy
from re import match
from typing import Any, Iterable, Optional, Sequence, Union
# String values evaluated as true boolean values
TRUE_BOOLEANS = ['on', 'true', 'enable', 'enabled', 'yes', 'high', '1']
# String values evaluated as false boolean values
FALSE_BOOLEANS = ['off', 'false', 'disable', 'disabled', 'no', 'low', '0']
# ASCII or '.' filter
ASCIIFILTER = ''.join([((len(repr(chr(_x))) == 3) or (_x == 0x5c)) and chr(_x)
or '.' for _x in range(128)]) + '.' * 128
ASCIIFILTER = bytearray(ASCIIFILTER.encode('ascii'))
def hexdump(data: Union[bytes, bytearray, Iterable[int]],
full: bool = False, abbreviate: bool = False) -> str:
"""Convert a binary buffer into a hexadecimal representation.
Return a multi-line strings with hexadecimal values and ASCII
representation of the buffer data.
:param data: binary buffer to dump
:param full: use `hexdump -Cv` format
:param abbreviate: replace identical lines with '*'
:return: the generated string
"""
try:
if isinstance(data, (bytes, array)):
src = bytearray(data)
elif not isinstance(data, bytearray):
# data may be a list/tuple
src = bytearray(b''.join(data))
else:
src = data
except Exception as exc:
raise TypeError(f"Unsupported data type '{type(data)}'") from exc
length = 16
result = []
last = b''
abv = False
for i in range(0, len(src), length):
s = src[i:i+length]
if abbreviate:
if s == last:
if not abv:
result.append('*\n')
abv = True
continue
abv = False
hexa = ' '.join((f'{x:02x}' for x in s))
printable = s.translate(ASCIIFILTER).decode('ascii')
if full:
hx1, hx2 = hexa[:3*8], hexa[3*8:]
hl = length//2
result.append(f'{i:08x} {hx1:<{hl*3}} {hx2:<{hl*3}} '
f'|{printable}|\n')
else:
result.append(f'{i:06x} {hexa:<{length*3}} {printable}\n')
last = s
return ''.join(result)
def hexline(data: Union[bytes, bytearray, Iterable[int]],
sep: str = ' ') -> str:
"""Convert a binary buffer into a hexadecimal representation.
Return a string with hexadecimal values and ASCII representation
of the buffer data.
:param data: binary buffer to dump
:param sep: the separator string/char
:return: the formatted string
"""
try:
if isinstance(data, (bytes, array)):
src = bytearray(data)
elif not isinstance(data, bytearray):
# data may be a list/tuple
src = bytearray(b''.join(data))
else:
src = data
except Exception as exc:
raise TypeError(f"Unsupported data type '{type(data)}'") from exc
hexa = sep.join((f'{x:02x}' for x in src))
printable = src.translate(ASCIIFILTER).decode('ascii')
return f'({len(data)}) {hexa} : {printable}'
def to_int(value: Union[int, str]) -> int:
"""Parse a value and convert it into an integer value if possible.
Input value may be:
- a string with an integer coded as a decimal value
- a string with an integer coded as a hexadecimal value
- a integral value
- a integral value with a unit specifier (kilo or mega)
:param value: input value to convert to an integer
:return: the value as an integer
:rtype: int
:raise ValueError: if the input value cannot be converted into an int
"""
if not value:
return 0
if isinstance(value, int):
return value
mo = match(r'^\s*(\d+)\s*(?:([KMkm]i?)?B?)?\s*$', value)
if mo:
mult = {'K': (1000),
'KI': (1 << 10),
'M': (1000 * 1000),
'MI': (1 << 20)}
value = int(mo.group(1))
if mo.group(2):
value *= mult[mo.group(2).upper()]
return value
return int(value.strip(), value.startswith('0x') and 16 or 10)
def to_bool(value: Union[int, bool, str], permissive: bool = True,
allow_int: bool = False) -> bool:
"""Parse a string and convert it into a boolean value if possible.
Input value may be:
- a string with an integer value, if `allow_int` is enabled
- a boolean value
- a string with a common boolean definition
:param value: the value to parse and convert
:param permissive: default to the False value if parsing fails
:param allow_int: allow an integral type as the input value
:raise ValueError: if the input value cannot be converted into an bool
"""
if value is None:
return False
if isinstance(value, bool):
return value
if isinstance(value, int):
if allow_int:
return bool(value)
if permissive:
return False
raise ValueError(f"Invalid boolean value: '{value}'")
if value.lower() in TRUE_BOOLEANS:
return True
if permissive or (value.lower() in FALSE_BOOLEANS):
return False
raise ValueError(f"Invalid boolean value: '{value}'")
def to_bps(value: str) -> int:
"""Parse a string and convert it into a baudrate value.
The function accepts common multipliers as K, M and G
:param value: the value to parse and convert
:type value: str or int or float
:rtype: float
:raise ValueError: if the input value cannot be converted into a float
"""
if isinstance(value, float):
return int(value)
if isinstance(value, int):
return value
mo = match(r'^(?P<value>[-+]?[0-9]*\.?[0-9]+(?:[Ee][-+]?[0-9]+)?)'
r'(?P<unit>[KkMmGg])?$', value)
if not mo:
raise ValueError('Invalid frequency')
frequency = float(mo.group(1))
if mo.group(2):
mult = {'K': 1E3, 'M': 1E6, 'G': 1E9}
frequency *= mult[mo.group(2).upper()]
return int(frequency)
def xor(_a_: bool, _b_: bool) -> bool:
"""XOR logical operation.
:param _a_: first argument
:param _b_: second argument
:return: xor-ed value
"""
# pylint: disable=superfluous-parens
return bool((not (_a_) and _b_) or (_a_ and not (_b_)))
def is_iterable(obj: Any) -> bool:
"""Tells whether an instance is iterable or not.
:param obj: the instance to test
:type obj: object
:return: True if the object is iterable
:rtype: bool
"""
try:
iter(obj)
return True
except TypeError:
return False
def pretty_size(size, sep: str = ' ',
lim_k: int = 1 << 10, lim_m: int = 10 << 20,
plural: bool = True, floor: bool = True) -> str:
"""Convert a size into a more readable unit-indexed size (KiB, MiB)
:param size: integral value to convert
:param sep: the separator character between the integral value and
the unit specifier
:param lim_k: any value above this limit is a candidate for KiB
conversion.
:param lim_m: any value above this limit is a candidate for MiB
conversion.
:param plural: whether to append a final 's' to byte(s)
:param floor: how to behave when exact conversion cannot be
achieved: take the closest, smaller value or fallback to the next
unit that allows the exact representation of the input value
:return: the prettyfied size
"""
size = int(size)
if size > lim_m:
ssize = size >> 20
if floor or (ssize << 20) == size:
return f'{ssize}{sep}MiB'
if size > lim_k:
ssize = size >> 10
if floor or (ssize << 10) == size:
return f'{ssize}{sep}KiB'
return f'{size}{sep}byte{plural and "s" or ""}'
def add_custom_devices(ftdicls=None,
vpstr: Optional[Sequence[str]] = None,
force_hex: bool = False) -> None:
"""Helper function to add custom VID/PID to FTDI device identifer map.
The string to parse should match the following format:
[vendor_name=]<vendor_id>:[product_name=]<product_id>
* vendor_name and product_name are optional strings, they may be omitted
as they only serve as human-readable aliases for vendor and product
names.
* vendor_id and product_id are mandatory strings that should resolve
as 16-bit integer (USB VID and PID values). They may be expressed as
decimal or hexadecimal syntax.
ex:
* ``0x403:0x9999``, vid:pid short syntax, with no alias names
* ``mycompany=0x666:myproduct=0xcafe``, vid:pid complete syntax with
aliases
:param vpstr: typically, a option switch string describing the device
to add
:param ftdicls: the Ftdi class that should support the new device.
:param force_hex: if set, consider that the pid/vid string are
hexadecimal encoded values.
"""
from inspect import isclass
if not isclass(ftdicls):
raise ValueError('Expect Ftdi class, not instance')
for vidpid in vpstr or []:
vidpids = {vid: set() for vid in ftdicls.PRODUCT_IDS}
vname = ''
pname = ''
try:
vid, pid = vidpid.split(':')
if '=' in vid:
vname, vid = vid.split('=', 1)
if '=' in pid:
pname, pid = pid.split('=', 1)
if force_hex:
vid, pid = [int(v, 16) for v in (vid, pid)]
else:
vid, pid = [to_int(v) for v in (vid, pid)]
except ValueError as exc:
raise ValueError('Invalid VID:PID value') from exc
if vid not in vidpids:
ftdicls.add_custom_vendor(vid, vname)
vidpids[vid] = set()
if pid not in vidpids[vid]:
ftdicls.add_custom_product(vid, pid, pname)
vidpids[vid].add(pid)
def show_call_stack():
"""Print the current call stack, only useful for debugging purpose."""
from sys import _current_frames
from threading import current_thread
from traceback import print_stack
print_stack(_current_frames()[current_thread().ident])
class classproperty(property):
"""Getter property decorator for a class"""
# pylint: disable=invalid-name
def __get__(self, obj: Any, objtype=None) -> Any:
return super().__get__(objtype)
class EasyDict(dict):
"""Dictionary whose members can be accessed as instance members
"""
def __init__(self, dictionary=None, **kwargs):
super().__init__(self)
if dictionary is not None:
self.update(dictionary)
self.update(kwargs)
def __getattr__(self, name):
try:
return self.__getitem__(name)
except KeyError as exc:
raise AttributeError(f"'{self.__class__.__name__}' object has no "
f"attribute '{name}'") from exc
def __setattr__(self, name, value):
self.__setitem__(name, value)
@classmethod
def copy(cls, dictionary):
def _deep_copy(obj):
if isinstance(obj, list):
return [_deep_copy(v) for v in obj]
if isinstance(obj, dict):
return EasyDict({k: _deep_copy(obj[k]) for k in obj})
return deepcopy(obj)
return cls(_deep_copy(dictionary))
def mirror(self) -> 'EasyDict':
"""Instanciate a mirror EasyDict."""
return EasyDict({v: k for k, v in self.items()})