from __future__ import annotations from typing import TYPE_CHECKING if TYPE_CHECKING: from typing_extensions import Self # 3.11+ # Map an integer in the inclusive range 0-255 to its string byte representation _printable = {i: chr(i) if 32 <= i < 128 else "." for i in range(256)} def hexdump(data: bytes, linesize: int) -> list[str]: """ data is a bytes object. The returned result is a string. """ prettylines = [] if len(data) < 65536: fmt = "%%04X %%-%ds %%s" else: fmt = "%%08X %%-%ds %%s" fmt = fmt % (3 * linesize - 1,) for i in range(0, len(data), linesize): line = data[i : i + linesize] hextext = line.hex(" ") rawtext = "".join(_printable[b] for b in line) prettylines.append(fmt % (i, hextext, rawtext)) return prettylines class HexString(bytes): """ Represents bytes that will be hex-dumped to a string when its string representation is requested. """ def __init__(self, data: bytes, linesize: int = 16) -> None: self.linesize = linesize def __new__(cls, data: bytes, *args: object, **kwargs: object) -> Self: return bytes.__new__(cls, data) def __str__(self) -> str: if not self: return "''" sep = "\n" return sep + sep.join( hexdump(self, self.linesize))