48 lines
1.3 KiB
Python
48 lines
1.3 KiB
Python
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))
|