#!/usr/bin/env python # -*- coding: utf-8 -*- from typing import Any, Optional from math import log2 class ErrorsManager: BASE64:list[str] = [*"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="] def __init__(self, alphabet:Optional[str|list[str]|tuple[str]] = None) -> None: self.__error:int = 0 # self.__re_hexa_error:REPattern|None = None self.__alphabet:list[str] = [] self.set_alphabet(alphabet) @staticmethod def is_string(value:Any|None) -> bool: return isinstance(value, str) @staticmethod def is_array(value:Any|None) -> bool: return isinstance(value, (list, tuple)) @staticmethod def is_integer(value:Any|None) -> bool: return isinstance(value, int) def set_alphabet(self, alphabet:Optional[str] = None) -> int: self.__error = ( 0 if alphabet == None else 1 << 2 if not ErrorsManager.is_array(alphabet) and not ErrorsManager.is_string(alphabet) else 0) << 1 if not self.__error: if alphabet: original_length:int = len(self.__alphabet) final_length:int = 0 character:str i:int alphabet = ( alphabet if isinstance(alphabet, list) else list(alphabet) if isinstance(alphabet, tuple) else [*alphabet]) alphabet = [character for i, character in alphabet if isinstance(character, str) and len(character) == 1 and alphabet.index(character) == i] final_length = len(alphabet) self.__error |= ( 1 << 0 if original_length != final_length else 1 << 1 if final_length < 64 else 0) << 5 self.__alphabet = ( self.__alphabet if self.__alphabet and len(self.__alphabet) else ErrorsManager.BASE64) if self.__error or not alphabet else alphabet # self.__re_hexa_error = RECompile(r'[^' + alphabet[0] + ']') return self.__error def to_array(self, code:str|int|list[int]|tuple[int], length:Optional[int] = 1 ) -> list[int]: array:list[int] = [] if ErrorsManager.is_string(code): hexa:str for hexa in code: array += [self.__alphabet.index(hexa)] elif ErrorsManager.is_integer(code): while code: array += [code & 0x3F] code >>= 6 elif ErrorsManager.is_array(code): hexa:int for hexa in code: array += [hexa] while len(array) < length: array += [0] return array def process(self, error:int|str|list[int]|tuple[int], messages:list[str]|tuple[str] ) -> list[str]: response:list[str] = [] m:int = len(messages) i:int hexa:int for i, hexa in enumerate(self.to_array(error)): j:int for j in range(6): if hexa & (1 << j): k:int = j + i * 6 response += [[k, messages[k] if k < m and messages[k] else "error_message_" + str(k)]] return response def get_alphabet(self) -> str: return "".join(self.__alphabet) def bits(self, code:str|int|list[int]|tuple[int]) -> int|None: if ErrorsManager.is_integer(code): return 1 if not code else int(log2(code) + 1) if ErrorsManager.is_string(code): code = self.to_array(code) if ErrorsManager.is_array(code): code = self.compact(code) l:int = len(code) return 1 if not l or not code[-1] else (l - 1) * 6 + int(log2(code[-1]) + 1) return None def get_error(self) -> int: return self.__error | 0 def to_array_binary(self, code:str|int|list[int]|tuple[int]) -> list[str]: hexa:int return [("000000" + "{0:b}".format(hexa))[-6:] for hexa in self.to_array(code)] def to_integer(self, code:str|int|list[int]|tuple[int]) -> int: if ErrorsManager.is_integer(code): return code if ErrorsManager.is_array(code): hexa:int i:int return sum([hexa << i * 6 for i, hexa in enumerate(code)]) if ErrorsManager.is_string(code): hexa:str i:int return sum([self.__alphabet.index(hexa) << i * 6 for i, hexa in enumerate(code)]) return 0 def to_string(self, code:str|int|list[int]|tuple[int], length:Optional[int] = 1 ) -> str: string:str = "" if ErrorsManager.is_string(code): string += code elif ErrorsManager.is_integer(code): while code: string += self.__alphabet[code & 0x3F] code >>= 6 elif ErrorsManager.is_array(code): hexa:str string = "".join([self.__alphabet[hexa] for hexa in code]) while len(string) < length: string += self.__alphabet[0] return string or self.__alphabet[0] def has(self, code:int|str|tuple[int]|list[int], bits:Optional[int|list[int]|tuple[int]] = None ) -> bool: if not ErrorsManager.is_integer(bits) and not ErrorsManager.is_array(bits): if ErrorsManager.is_string(code): hexa:str for hexa in code: if self.__alphabet.index(hexa): return True return False if ErrorsManager.is_integer(code): return not not code if ErrorsManager.is_array(code): hexa:int for hexa in code: if hexa: return True return False error:list[int] = self.to_array(code) bit:int if ErrorsManager.is_integer(bits): bits = (bits,) for bit in bits[:len(error) - 1]: if error[bit]: return True return False def to_unknown(self, code:Any|None) -> Any|None: return code def compact(self, code:int|str|list[int]|tuple[int]) -> int|str|list[int]: if ErrorsManager.is_string(code): while code and code[-1] == self.__alphabet[0]: code = code[:-1] return code or self.__alphabet[0] if ErrorsManager.is_array(code): code = list(code) while len(code) and not code[-1]: code = code[:-1] return code if len(code) else [0] if ErrorsManager.is_integer(code): return code return 0 @classmethod def type(self, code:int|str|tuple[int]|list[int]): return ( "string" if self.is_string(code) else "integer" if self.is_integer(code) else "array" if self.is_array(code) else "unknown") def bitwise(self, code:int|str|tuple[int]|list[int], bits:int ) -> str|int|list[int]: if not bits or not self.has(code): return code reverse:bool = bits < 0 if reverse: bits *= -1 start:int = int(bits / 6) rest:int = bits % 6 type_method:str = "to_" + ErrorsManager.type(code) code = self.to_array(code) if reverse: code = code[start:] if rest: if code: r:int = 6 - rest block:int = ~-(1 << rest) i:int hexa:int code = [(hexa >> rest) | ((code[i + 1] & block) << r) for i, hexa in enumerate(code[:-1])] + [code[-1] >> rest] else: code = [0] else: i:int if rest: r:int = 6 - rest mask:int = ~-(1 << 6) code = [(code[0] << rest) & mask] + [((hexa << rest) & mask) | (code[i - 1] >> r) for i, hexa in enumerate(code[1:])] + [code[-1] >> r] for i in range(start): code = [0] + code return getattr(self, type_method)(code) def set(self, code:int|str|list[int]|tuple[int], error:int|str|list[int]|tuple[int], bit:Optional[int] = 0, length:Optional[int] = 0 ) -> str: code = self.to_array(code) error = self.to_array(error) if bit: error = self.bitwise(error, bit) i:int = int(bit / 6) if length: start:int = bit % 6 j:int hexa:int end:int = (start + length) % 6 for j, hexa in enumerate([~-(1 << start)] + [0 for j in range(int((length + start) / 6) - 1)] + ([~-(1 << (6 - end)) << end] if end else [])): code[j + i] &= hexa if self.has(error): l:int = len(error) while len(code) < i: code += [0] m:int = len(code) while i < l: if i >= m: code += [0] code[i] = (code[i] or 0) | error[i] i += 1 return self.compact(self.to_string(code)) def join(self, code:int|str|list[int]|tuple[int], error:int|str|list[int]|tuple[int], bit:Optional[int] = 0, length:Optional[int] = 0 ) -> str: return self.set(code, error, bit, length) def set_blocks(self, code:str|int|tuple[int]|list[int], blocks:list[str|int|tuple[int]|list[int]]|tuple[str|int|tuple[int]|list[int]], bit:Optional[int] = 0, length:Optional[int] = 0 ) -> str: block:int i:int if length: code = self.set(code, 0, bit, length) for i, block in blocks: if block: code = self.set(code, block, i + bit) return code or self.__alphabet[0] def slice(self, code:str|int|list[int]|tuple[int], _from:int, _to:Optional[int] = 0 ) -> str|int|list[int]|None: if self.has(code): return code bits:int = self.bits(code) rest:int if _from < 0: _from = bits + _from _to = ( bits if _to > bits else bits - _to if _to < 0 else _to) - _from rest = _to %6 code = self.bitwise(code, -_from) return ( code[:int(_to / 6)] + (self.__alphabet[self.__alphabet.index(code[-1]) & ~-(1 << rest)] if rest else "") if ErrorsManager.is_string(code) else code[:int(_to / 6)] + ([code[-1] & ~-(1 << rest)] if rest else []) if ErrorsManager.is_array(code) else code & ~-(1 << _to) if ErrorsManager.is_integer(code) else None) def has_range(self, code:int|str|list[int]|tuple[int], _from:int, _to:Optional[int] = 0) -> bool: return self.has(self.slice(code, _from, _to))