#!/usr/bin/env python3 """Hexview — hex dump viewer with ASCII side panel.""" import argparse, os, sys def color(text, code): return f"\033[{code}m{text}\033[0m" if sys.stdout.isatty() else text def hex_dump(data, width=16, show_ascii=True, show_offset=True, color_ascii=True, group=2): result = [] addr_len = max(4, len(str(len(data) - 1))) for i in range(0, len(data), width): chunk = data[i:i + width] line = "" # Offset if show_offset: off = color(f"{i:0{addr_len}x}", "36") if color_ascii else f"{i:0{addr_len}x}" line += f"{off} " # Hex bytes hex_parts = [] for j in range(0, len(chunk), group): group_bytes = chunk[j:j + group] hex_parts.append(" ".join(f"{b:02x}" for b in group_bytes)) hex_str = " ".join(hex_parts) hex_str = color(hex_str, "37") if color_ascii else hex_str line += hex_str # Padding for short lines expected = width * 2 + (width // group - 1) * (group * 2 + 1) # Calculate required padding line += " " * (3 * (width // group) + 1) # ASCII representation if show_ascii: line += " " for b in chunk: if 32 <= b < 127: char = chr(b) if char in "<>&'\"": char = "." line += color(char, "32") if color_ascii else char else: line += color(".", "90") if color_ascii else "." result.append(line.rstrip()) return "\n".join(result) def main(): parser = argparse.ArgumentParser( description="Hexview — hex dump viewer", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=( "Examples:\n" " hexview file.bin # Hex dump a file\n" " hexview file.bin --width 32 # 32 bytes per row\n" " hexview file.bin --no-ascii # Hide ASCII panel\n" " hexview file.bin --raw # No color, pipe-friendly\n" ), ) parser.add_argument("file", help="File to dump") parser.add_argument("--width", type=int, default=16, help="Bytes per row (default: 16)") parser.add_argument("--no-ascii", action="store_true", help="Hide ASCII representation") parser.add_argument("--no-offset", action="store_true", help="Hide offset column") parser.add_argument("--no-color", action="store_true", help="Disable color output") parser.add_argument("--group", type=int, default=2, help="Bytes per group (default: 2)") parser.add_argument("--length", "-n", type=int, help="Number of bytes to show") parser.add_argument("--offset", type=lambda x: int(x, 0), default=0, help="Start offset") parser.add_argument("--version", action="version", version="hexview 1.0.0") args = parser.parse_args() if not os.path.exists(args.file): print(f"Error: File '{args.file}' not found", file=sys.stderr) sys.exit(1) try: with open(args.file, "rb") as f: if args.offset: f.seek(args.offset) data = f.read(args.length or -1) except PermissionError: print(f"Error: Permission denied reading '{args.file}'", file=sys.stderr) sys.exit(1) except Exception as e: print(f"Error: {e}", file=sys.stderr) sys.exit(1) if not data: print(f"{color('(empty)', '90')}") return show_ascii = not args.no_ascii show_offset = not args.no_offset use_color = sys.stdout.isatty() and not args.no_color print(f"File: {args.file} ({len(data)} bytes)") print(f"{color('─' * 60, '90')}") output = hex_dump(data, width=args.width, show_ascii=show_ascii, show_offset=show_offset, color_ascii=use_color, group=args.group) print(output) if __name__ == "__main__": main()