Add setuptools
This commit is contained in:
parent
74e0cfb40d
commit
3cb48b0345
3 changed files with 282 additions and 264 deletions
|
@ -1,266 +1,4 @@
|
||||||
#!/usr/bin/env python3
|
from rebindiff.main import main
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import os
|
|
||||||
import os.path
|
|
||||||
import random
|
|
||||||
import sys
|
|
||||||
from typing import Sequence, Generator, Tuple, BinaryIO, Optional, Mapping
|
|
||||||
|
|
||||||
from rebindiff.utils import color
|
|
||||||
from rebindiff.utils.strings import ellipsize
|
|
||||||
from rebindiff.utils.values import str_base, unicode_ascii_repr
|
|
||||||
|
|
||||||
BLOCK_NUM_HEADER = "blk"
|
|
||||||
BLOCK_NUM_PADDING_RIGHT = 2
|
|
||||||
BLOCK_NUM_BASE = 16
|
|
||||||
|
|
||||||
BLOCK_BYTES = 4
|
|
||||||
|
|
||||||
LEFT_PADDING = 1
|
|
||||||
BYTE_SPACING = 1
|
|
||||||
HEX_ASCII_SPACING = 2
|
|
||||||
FILE_SPACING = 4
|
|
||||||
RIGHT_PADDING = 0
|
|
||||||
|
|
||||||
NOT_ASCII_CHAR = "‧"
|
|
||||||
HORZ_ELLIPSIS = "…"
|
|
||||||
VERT_ELLIPSIS = "⋮"
|
|
||||||
|
|
||||||
BLOCK_TEMPLATE = " " * LEFT_PADDING + "{hexr}" + " " * HEX_ASCII_SPACING + "|{asciir}|" + " " * RIGHT_PADDING
|
|
||||||
|
|
||||||
HEX_REPR_LENGTH = (2 + BYTE_SPACING) * BLOCK_BYTES - BYTE_SPACING
|
|
||||||
ASCII_REPR_LENGTH = BLOCK_BYTES + 2
|
|
||||||
|
|
||||||
NAME_MAX_LENGTH = HEX_REPR_LENGTH + HEX_ASCII_SPACING + ASCII_REPR_LENGTH
|
|
||||||
TOTAL_BLOCK_LENGTH = NAME_MAX_LENGTH + LEFT_PADDING + RIGHT_PADDING
|
|
||||||
|
|
||||||
if BLOCK_NUM_BASE > ord('Z') - ord('A') + 10 or BLOCK_NUM_BASE < 2:
|
|
||||||
raise AssertionError("Invalid block number base")
|
|
||||||
|
|
||||||
global_colormap = {}
|
|
||||||
|
|
||||||
|
|
||||||
def print_names(block_col_width: int, *names: str) -> None:
|
|
||||||
print(end=color.BOLD + color.UNDERLINE)
|
|
||||||
print(BLOCK_NUM_HEADER.center(block_col_width), end=" " * BLOCK_NUM_PADDING_RIGHT)
|
|
||||||
|
|
||||||
print(
|
|
||||||
" " * LEFT_PADDING +
|
|
||||||
(" " * (FILE_SPACING + LEFT_PADDING + RIGHT_PADDING)).join(
|
|
||||||
[ellipsize(name, NAME_MAX_LENGTH).center(NAME_MAX_LENGTH) for name in names]
|
|
||||||
) + " " * RIGHT_PADDING
|
|
||||||
)
|
|
||||||
|
|
||||||
print(end=color.RESET)
|
|
||||||
|
|
||||||
|
|
||||||
def colorize(string: str, byte: int, colormap: Mapping[int, str]) -> str:
|
|
||||||
if byte in colormap:
|
|
||||||
return color.colors[colormap[byte]] + string + color.RESET
|
|
||||||
return string
|
|
||||||
|
|
||||||
|
|
||||||
def get_other_halfbyte(half: int, byte: int) -> int:
|
|
||||||
if byte == 0:
|
|
||||||
return 0
|
|
||||||
if half & 0x0F == 0:
|
|
||||||
return byte & 0x0F
|
|
||||||
return byte & 0x1F0
|
|
||||||
|
|
||||||
|
|
||||||
def get_color_map(intgroup: Sequence[Optional[int]]) -> Mapping[int, str]:
|
|
||||||
intset = set(intgroup)
|
|
||||||
if len(intset) == 1:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
from .utils import color
|
|
||||||
# ncolors = list(sorted(color.colors.keys(), key=lambda c: c.replace("BRIGHT_", "")))
|
|
||||||
ncolors = list(color.colors.keys()).copy()
|
|
||||||
random.shuffle(ncolors)
|
|
||||||
ncolors *= 3
|
|
||||||
colormap = {}
|
|
||||||
halfbytes = {}
|
|
||||||
|
|
||||||
for byte in intgroup:
|
|
||||||
lo = byte & 0x0F
|
|
||||||
hi = byte & 0x1F0
|
|
||||||
|
|
||||||
if lo not in halfbytes:
|
|
||||||
halfbytes[lo] = []
|
|
||||||
if hi not in halfbytes:
|
|
||||||
halfbytes[hi] = []
|
|
||||||
|
|
||||||
halfbytes[lo].append(byte)
|
|
||||||
halfbytes[hi].append(byte)
|
|
||||||
|
|
||||||
for halfbyte in halfbytes: # sorted(halfbytes, key=lambda half: int_distance_sort_key(half, halfbytes.keys())):
|
|
||||||
if len(halfbytes[halfbyte]) == len(intgroup):
|
|
||||||
# Halfbyte does not change
|
|
||||||
continue
|
|
||||||
colormap[halfbyte] = ncolors.pop(0)
|
|
||||||
|
|
||||||
for byte in intset:
|
|
||||||
lo = byte & 0x0F
|
|
||||||
hi = byte & 0x1F0
|
|
||||||
|
|
||||||
# The byte gets the color of the half with the least occurrences
|
|
||||||
try:
|
|
||||||
least_common = sorted([hi, lo], key=lambda half: len(halfbytes[half]))[0]
|
|
||||||
colormap[byte] = colormap[least_common]
|
|
||||||
except:
|
|
||||||
print(byte, colormap)
|
|
||||||
|
|
||||||
return colormap
|
|
||||||
|
|
||||||
|
|
||||||
def print_line(block_col_width, block_num: int, line: Sequence[Sequence[Optional[bytes]]]) -> None:
|
|
||||||
global global_colormap
|
|
||||||
print(
|
|
||||||
color.BOLD + str_base(block_num, BLOCK_NUM_BASE).rjust(block_col_width),
|
|
||||||
end=" " * BLOCK_NUM_PADDING_RIGHT + color.RESET
|
|
||||||
)
|
|
||||||
|
|
||||||
hex_reprs = ["" if set(block) != {None} else None for block in line]
|
|
||||||
ascii_reprs = hex_reprs.copy()
|
|
||||||
|
|
||||||
byte_count = 0
|
|
||||||
|
|
||||||
for bgroup in zip(*line):
|
|
||||||
byte_count += 1
|
|
||||||
intgroup = [int.from_bytes(byte, 'little') | 0x100 for byte in bgroup]
|
|
||||||
|
|
||||||
colormap = get_color_map(intgroup)
|
|
||||||
colormap = {byte: color if byte not in global_colormap
|
|
||||||
else global_colormap[byte] for byte, color in colormap.items()}
|
|
||||||
global_colormap.update(colormap)
|
|
||||||
|
|
||||||
for i, byte in zip(range(len(hex_reprs)), intgroup):
|
|
||||||
if hex_reprs[i] is None:
|
|
||||||
continue
|
|
||||||
if byte is None:
|
|
||||||
hex_reprs[i] += " "
|
|
||||||
ascii_reprs[i] += " "
|
|
||||||
else:
|
|
||||||
asuni = unicode_ascii_repr(byte & 0xFF)
|
|
||||||
|
|
||||||
lo = byte & 0x0F
|
|
||||||
hi = byte & 0x1F0
|
|
||||||
|
|
||||||
hi_str = colorize("%01X" % ((byte & 0xF0) >> 4), hi, colormap)
|
|
||||||
lo_str = colorize("%01X" % lo, lo, colormap)
|
|
||||||
|
|
||||||
hex_reprs[i] += hi_str + lo_str
|
|
||||||
ascii_reprs[i] += colorize(asuni, byte, colormap)
|
|
||||||
|
|
||||||
if byte_count < BLOCK_BYTES:
|
|
||||||
hex_reprs[i] += " " * BYTE_SPACING
|
|
||||||
|
|
||||||
reprs = []
|
|
||||||
for hexr, asciir in zip(hex_reprs, ascii_reprs):
|
|
||||||
if not hexr:
|
|
||||||
reprs.append(" " * TOTAL_BLOCK_LENGTH)
|
|
||||||
continue
|
|
||||||
reprs.append(BLOCK_TEMPLATE.format(hexr=hexr, asciir=asciir))
|
|
||||||
|
|
||||||
print((" " * FILE_SPACING).join(reprs))
|
|
||||||
|
|
||||||
|
|
||||||
def print_identical_line_bogus(block_col_width: int, last_line: Sequence[Sequence[Optional[bytes]]]):
|
|
||||||
print(end=color.BRIGHT_BLACK)
|
|
||||||
print(str(VERT_ELLIPSIS).rjust(block_col_width), end=" " * BLOCK_NUM_PADDING_RIGHT)
|
|
||||||
|
|
||||||
bogus_blocks = []
|
|
||||||
for block in last_line:
|
|
||||||
byteset = set([tuple(i) for i in block if i])
|
|
||||||
bogus_char = NOT_ASCII_CHAR if len(byteset) > 0 else " "
|
|
||||||
|
|
||||||
bogus_blocks.append(
|
|
||||||
BLOCK_TEMPLATE.replace("|", bogus_char).format(
|
|
||||||
hexr=(" " * BYTE_SPACING).join(bogus_char * 2 for _ in range(BLOCK_BYTES)),
|
|
||||||
asciir="".join(bogus_char for _ in range(BLOCK_BYTES))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
print((" " * FILE_SPACING).join(bogus_blocks))
|
|
||||||
print(end=color.RESET)
|
|
||||||
|
|
||||||
|
|
||||||
def get_block_col_width(*filenames):
|
|
||||||
max_size = max(
|
|
||||||
map(
|
|
||||||
lambda filename: os.stat(filename).st_size,
|
|
||||||
filenames
|
|
||||||
)
|
|
||||||
)
|
|
||||||
max_block = (max_size + BLOCK_BYTES - (max_size % BLOCK_BYTES)) / BLOCK_BYTES
|
|
||||||
|
|
||||||
digits = 0
|
|
||||||
while max_block > 0:
|
|
||||||
max_block //= BLOCK_NUM_BASE
|
|
||||||
digits += 1
|
|
||||||
|
|
||||||
return max(digits, len(BLOCK_NUM_HEADER))
|
|
||||||
|
|
||||||
|
|
||||||
def print_hexdiff(*filenames: str):
|
|
||||||
names, files = zip(*gen_files(*filenames))
|
|
||||||
block_col_width = get_block_col_width(*filenames)
|
|
||||||
|
|
||||||
print_names(block_col_width, *names)
|
|
||||||
|
|
||||||
identical_lines = 0
|
|
||||||
prev_line = None
|
|
||||||
line = [[] for _ in range(len(files))]
|
|
||||||
block_num = 0
|
|
||||||
block_bytes = 0
|
|
||||||
remaining_files = len(files)
|
|
||||||
|
|
||||||
while remaining_files > 0:
|
|
||||||
if block_bytes >= BLOCK_BYTES:
|
|
||||||
if prev_line == line:
|
|
||||||
identical_lines += 1
|
|
||||||
if identical_lines == 1:
|
|
||||||
print_identical_line_bogus(block_col_width, line)
|
|
||||||
else:
|
|
||||||
identical_lines = 0
|
|
||||||
print_line(block_col_width, block_num, line)
|
|
||||||
prev_line = line
|
|
||||||
|
|
||||||
block_bytes = 0
|
|
||||||
block_num += 1
|
|
||||||
line = [[] for _ in range(len(files))]
|
|
||||||
|
|
||||||
block_bytes += 1
|
|
||||||
|
|
||||||
for i in range(len(files)):
|
|
||||||
f = files[i]
|
|
||||||
file_line = line[i]
|
|
||||||
|
|
||||||
if f.closed:
|
|
||||||
file_line.append(None)
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
byte = f.read(1)
|
|
||||||
if not byte:
|
|
||||||
raise EOFError
|
|
||||||
line[i].append(byte)
|
|
||||||
except EOFError:
|
|
||||||
file_line.append(None)
|
|
||||||
f.close()
|
|
||||||
remaining_files -= 1
|
|
||||||
|
|
||||||
|
|
||||||
def gen_files(*filenames: str) -> Generator[Tuple[str, BinaryIO], None, None]:
|
|
||||||
for filename in filenames:
|
|
||||||
f = open(filename, "rb")
|
|
||||||
basename = os.path.basename(filename)
|
|
||||||
yield (basename, f)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if len(sys.argv) < 2:
|
main()
|
||||||
raise SystemExit("At least one file needs to be specified")
|
|
||||||
|
|
||||||
print_hexdiff(*sys.argv[1:])
|
|
||||||
|
|
267
rebindiff/main.py
Normal file
267
rebindiff/main.py
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import random
|
||||||
|
import sys
|
||||||
|
from typing import Sequence, Generator, Tuple, BinaryIO, Optional, Mapping
|
||||||
|
|
||||||
|
from rebindiff.utils import color
|
||||||
|
from rebindiff.utils.strings import ellipsize
|
||||||
|
from rebindiff.utils.values import str_base, unicode_ascii_repr
|
||||||
|
|
||||||
|
BLOCK_NUM_HEADER = "blk"
|
||||||
|
BLOCK_NUM_PADDING_RIGHT = 2
|
||||||
|
BLOCK_NUM_BASE = 16
|
||||||
|
|
||||||
|
BLOCK_BYTES = 4
|
||||||
|
|
||||||
|
LEFT_PADDING = 1
|
||||||
|
BYTE_SPACING = 1
|
||||||
|
HEX_ASCII_SPACING = 2
|
||||||
|
FILE_SPACING = 4
|
||||||
|
RIGHT_PADDING = 0
|
||||||
|
|
||||||
|
NOT_ASCII_CHAR = "‧"
|
||||||
|
HORZ_ELLIPSIS = "…"
|
||||||
|
VERT_ELLIPSIS = "⋮"
|
||||||
|
|
||||||
|
BLOCK_TEMPLATE = " " * LEFT_PADDING + "{hexr}" + " " * HEX_ASCII_SPACING + "|{asciir}|" + " " * RIGHT_PADDING
|
||||||
|
|
||||||
|
HEX_REPR_LENGTH = (2 + BYTE_SPACING) * BLOCK_BYTES - BYTE_SPACING
|
||||||
|
ASCII_REPR_LENGTH = BLOCK_BYTES + 2
|
||||||
|
|
||||||
|
NAME_MAX_LENGTH = HEX_REPR_LENGTH + HEX_ASCII_SPACING + ASCII_REPR_LENGTH
|
||||||
|
TOTAL_BLOCK_LENGTH = NAME_MAX_LENGTH + LEFT_PADDING + RIGHT_PADDING
|
||||||
|
|
||||||
|
if BLOCK_NUM_BASE > ord('Z') - ord('A') + 10 or BLOCK_NUM_BASE < 2:
|
||||||
|
raise AssertionError("Invalid block number base")
|
||||||
|
|
||||||
|
global_colormap = {}
|
||||||
|
|
||||||
|
|
||||||
|
def print_names(block_col_width: int, *names: str) -> None:
|
||||||
|
print(end=color.BOLD + color.UNDERLINE)
|
||||||
|
print(BLOCK_NUM_HEADER.center(block_col_width), end=" " * BLOCK_NUM_PADDING_RIGHT)
|
||||||
|
|
||||||
|
print(
|
||||||
|
" " * LEFT_PADDING +
|
||||||
|
(" " * (FILE_SPACING + LEFT_PADDING + RIGHT_PADDING)).join(
|
||||||
|
[ellipsize(name, NAME_MAX_LENGTH).center(NAME_MAX_LENGTH) for name in names]
|
||||||
|
) + " " * RIGHT_PADDING
|
||||||
|
)
|
||||||
|
|
||||||
|
print(end=color.RESET)
|
||||||
|
|
||||||
|
|
||||||
|
def colorize(string: str, byte: int, colormap: Mapping[int, str]) -> str:
|
||||||
|
if byte in colormap:
|
||||||
|
return color.colors[colormap[byte]] + string + color.RESET
|
||||||
|
return string
|
||||||
|
|
||||||
|
|
||||||
|
def get_other_halfbyte(half: int, byte: int) -> int:
|
||||||
|
if byte == 0:
|
||||||
|
return 0
|
||||||
|
if half & 0x0F == 0:
|
||||||
|
return byte & 0x0F
|
||||||
|
return byte & 0x1F0
|
||||||
|
|
||||||
|
|
||||||
|
def get_color_map(intgroup: Sequence[Optional[int]]) -> Mapping[int, str]:
|
||||||
|
intset = set(intgroup)
|
||||||
|
if len(intset) == 1:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
from .utils import color
|
||||||
|
# ncolors = list(sorted(color.colors.keys(), key=lambda c: c.replace("BRIGHT_", "")))
|
||||||
|
ncolors = list(color.colors.keys()).copy()
|
||||||
|
random.shuffle(ncolors)
|
||||||
|
ncolors *= 3
|
||||||
|
colormap = {}
|
||||||
|
halfbytes = {}
|
||||||
|
|
||||||
|
for byte in intgroup:
|
||||||
|
lo = byte & 0x0F
|
||||||
|
hi = byte & 0x1F0
|
||||||
|
|
||||||
|
if lo not in halfbytes:
|
||||||
|
halfbytes[lo] = []
|
||||||
|
if hi not in halfbytes:
|
||||||
|
halfbytes[hi] = []
|
||||||
|
|
||||||
|
halfbytes[lo].append(byte)
|
||||||
|
halfbytes[hi].append(byte)
|
||||||
|
|
||||||
|
for halfbyte in halfbytes: # sorted(halfbytes, key=lambda half: int_distance_sort_key(half, halfbytes.keys())):
|
||||||
|
if len(halfbytes[halfbyte]) == len(intgroup):
|
||||||
|
# Halfbyte does not change
|
||||||
|
continue
|
||||||
|
colormap[halfbyte] = ncolors.pop(0)
|
||||||
|
|
||||||
|
for byte in intset:
|
||||||
|
lo = byte & 0x0F
|
||||||
|
hi = byte & 0x1F0
|
||||||
|
|
||||||
|
# The byte gets the color of the half with the least occurrences
|
||||||
|
try:
|
||||||
|
least_common = sorted([hi, lo], key=lambda half: len(halfbytes[half]))[0]
|
||||||
|
colormap[byte] = colormap[least_common]
|
||||||
|
except:
|
||||||
|
print(byte, colormap)
|
||||||
|
|
||||||
|
return colormap
|
||||||
|
|
||||||
|
|
||||||
|
def print_line(block_col_width, block_num: int, line: Sequence[Sequence[Optional[bytes]]]) -> None:
|
||||||
|
global global_colormap
|
||||||
|
print(
|
||||||
|
color.BOLD + str_base(block_num, BLOCK_NUM_BASE).rjust(block_col_width),
|
||||||
|
end=" " * BLOCK_NUM_PADDING_RIGHT + color.RESET
|
||||||
|
)
|
||||||
|
|
||||||
|
hex_reprs = ["" if set(block) != {None} else None for block in line]
|
||||||
|
ascii_reprs = hex_reprs.copy()
|
||||||
|
|
||||||
|
byte_count = 0
|
||||||
|
|
||||||
|
for bgroup in zip(*line):
|
||||||
|
byte_count += 1
|
||||||
|
intgroup = [int.from_bytes(byte, 'little') | 0x100 for byte in bgroup]
|
||||||
|
|
||||||
|
colormap = get_color_map(intgroup)
|
||||||
|
colormap = {byte: color if byte not in global_colormap
|
||||||
|
else global_colormap[byte] for byte, color in colormap.items()}
|
||||||
|
global_colormap.update(colormap)
|
||||||
|
|
||||||
|
for i, byte in zip(range(len(hex_reprs)), intgroup):
|
||||||
|
if hex_reprs[i] is None:
|
||||||
|
continue
|
||||||
|
if byte is None:
|
||||||
|
hex_reprs[i] += " "
|
||||||
|
ascii_reprs[i] += " "
|
||||||
|
else:
|
||||||
|
asuni = unicode_ascii_repr(byte & 0xFF)
|
||||||
|
|
||||||
|
lo = byte & 0x0F
|
||||||
|
hi = byte & 0x1F0
|
||||||
|
|
||||||
|
hi_str = colorize("%01X" % ((byte & 0xF0) >> 4), hi, colormap)
|
||||||
|
lo_str = colorize("%01X" % lo, lo, colormap)
|
||||||
|
|
||||||
|
hex_reprs[i] += hi_str + lo_str
|
||||||
|
ascii_reprs[i] += colorize(asuni, byte, colormap)
|
||||||
|
|
||||||
|
if byte_count < BLOCK_BYTES:
|
||||||
|
hex_reprs[i] += " " * BYTE_SPACING
|
||||||
|
|
||||||
|
reprs = []
|
||||||
|
for hexr, asciir in zip(hex_reprs, ascii_reprs):
|
||||||
|
if not hexr:
|
||||||
|
reprs.append(" " * TOTAL_BLOCK_LENGTH)
|
||||||
|
continue
|
||||||
|
reprs.append(BLOCK_TEMPLATE.format(hexr=hexr, asciir=asciir))
|
||||||
|
|
||||||
|
print((" " * FILE_SPACING).join(reprs))
|
||||||
|
|
||||||
|
|
||||||
|
def print_identical_line_bogus(block_col_width: int, last_line: Sequence[Sequence[Optional[bytes]]]):
|
||||||
|
print(end=color.BRIGHT_BLACK)
|
||||||
|
print(str(VERT_ELLIPSIS).rjust(block_col_width), end=" " * BLOCK_NUM_PADDING_RIGHT)
|
||||||
|
|
||||||
|
bogus_blocks = []
|
||||||
|
for block in last_line:
|
||||||
|
byteset = set([tuple(i) for i in block if i])
|
||||||
|
bogus_char = NOT_ASCII_CHAR if len(byteset) > 0 else " "
|
||||||
|
|
||||||
|
bogus_blocks.append(
|
||||||
|
BLOCK_TEMPLATE.replace("|", bogus_char).format(
|
||||||
|
hexr=(" " * BYTE_SPACING).join(bogus_char * 2 for _ in range(BLOCK_BYTES)),
|
||||||
|
asciir="".join(bogus_char for _ in range(BLOCK_BYTES))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
print((" " * FILE_SPACING).join(bogus_blocks))
|
||||||
|
print(end=color.RESET)
|
||||||
|
|
||||||
|
|
||||||
|
def get_block_col_width(*filenames):
|
||||||
|
max_size = max(
|
||||||
|
map(
|
||||||
|
lambda filename: os.stat(filename).st_size,
|
||||||
|
filenames
|
||||||
|
)
|
||||||
|
)
|
||||||
|
max_block = (max_size + BLOCK_BYTES - (max_size % BLOCK_BYTES)) / BLOCK_BYTES
|
||||||
|
|
||||||
|
digits = 0
|
||||||
|
while max_block > 0:
|
||||||
|
max_block //= BLOCK_NUM_BASE
|
||||||
|
digits += 1
|
||||||
|
|
||||||
|
return max(digits, len(BLOCK_NUM_HEADER))
|
||||||
|
|
||||||
|
|
||||||
|
def print_hexdiff(*filenames: str):
|
||||||
|
names, files = zip(*gen_files(*filenames))
|
||||||
|
block_col_width = get_block_col_width(*filenames)
|
||||||
|
|
||||||
|
print_names(block_col_width, *names)
|
||||||
|
|
||||||
|
identical_lines = 0
|
||||||
|
prev_line = None
|
||||||
|
line = [[] for _ in range(len(files))]
|
||||||
|
block_num = 0
|
||||||
|
block_bytes = 0
|
||||||
|
remaining_files = len(files)
|
||||||
|
|
||||||
|
while remaining_files > 0:
|
||||||
|
if block_bytes >= BLOCK_BYTES:
|
||||||
|
if prev_line == line:
|
||||||
|
identical_lines += 1
|
||||||
|
if identical_lines == 1:
|
||||||
|
print_identical_line_bogus(block_col_width, line)
|
||||||
|
else:
|
||||||
|
identical_lines = 0
|
||||||
|
print_line(block_col_width, block_num, line)
|
||||||
|
prev_line = line
|
||||||
|
|
||||||
|
block_bytes = 0
|
||||||
|
block_num += 1
|
||||||
|
line = [[] for _ in range(len(files))]
|
||||||
|
|
||||||
|
block_bytes += 1
|
||||||
|
|
||||||
|
for i in range(len(files)):
|
||||||
|
f = files[i]
|
||||||
|
file_line = line[i]
|
||||||
|
|
||||||
|
if f.closed:
|
||||||
|
file_line.append(None)
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
byte = f.read(1)
|
||||||
|
if not byte:
|
||||||
|
raise EOFError
|
||||||
|
line[i].append(byte)
|
||||||
|
except EOFError:
|
||||||
|
file_line.append(None)
|
||||||
|
f.close()
|
||||||
|
remaining_files -= 1
|
||||||
|
|
||||||
|
|
||||||
|
def gen_files(*filenames: str) -> Generator[Tuple[str, BinaryIO], None, None]:
|
||||||
|
for filename in filenames:
|
||||||
|
f = open(filename, "rb")
|
||||||
|
basename = os.path.basename(filename)
|
||||||
|
yield (basename, f)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
raise SystemExit("At least one file needs to be specified")
|
||||||
|
|
||||||
|
print_hexdiff(*sys.argv[1:])
|
||||||
|
|
13
setup.py
Normal file
13
setup.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='rebindiff',
|
||||||
|
version='0.1',
|
||||||
|
packages=find_packages(),
|
||||||
|
license='GPL-3.0',
|
||||||
|
author='Davide Depau',
|
||||||
|
author_email='davide@depau.eu',
|
||||||
|
entry_points = {
|
||||||
|
'console_scripts': ['rebindiff=rebindiff.main:main'],
|
||||||
|
}
|
||||||
|
)
|
Loading…
Reference in a new issue