shadacat.py (2684B)
1 #!/usr/bin/env python3 2 3 import os 4 import sys 5 import codecs 6 7 from enum import Enum 8 from datetime import datetime 9 from functools import reduce 10 11 import msgpack 12 13 14 class EntryTypes(Enum): 15 Unknown = -1 16 Missing = 0 17 Header = 1 18 SearchPattern = 2 19 SubString = 3 20 HistoryEntry = 4 21 Register = 5 22 Variable = 6 23 GlobalMark = 7 24 Jump = 8 25 BufferList = 9 26 LocalMark = 10 27 Change = 11 28 29 30 def strtrans_errors(e): 31 if not isinstance(e, UnicodeDecodeError): 32 raise NotImplementedError('don’t know how to handle {0} error'.format( 33 e.__class__.__name__)) 34 return '<{0:x}>'.format(reduce((lambda a, b: a*0x100+b), 35 list(e.object[e.start:e.end]))), e.end 36 37 38 codecs.register_error('strtrans', strtrans_errors) 39 40 41 def idfunc(o): 42 return o 43 44 45 class CharInt(int): 46 def __repr__(self): 47 return super(CharInt, self).__repr__() + ' (\'%s\')' % chr(self) 48 49 50 ctable = { 51 bytes: lambda s: s.decode('utf-8', 'strtrans'), 52 dict: lambda d: dict((mnormalize(k), mnormalize(v)) for k, v in d.items()), 53 list: lambda l: list(mnormalize(i) for i in l), 54 int: lambda n: CharInt(n) if 0x20 <= n <= 0x7E else n, 55 } 56 57 58 def mnormalize(o): 59 return ctable.get(type(o), idfunc)(o) 60 61 62 fname = sys.argv[1] 63 try: 64 filt = sys.argv[2] 65 except IndexError: 66 def filt(entry): return True 67 else: 68 _filt = filt 69 def filt(entry): return eval(_filt, globals(), {'entry': entry}) # noqa 70 71 poswidth = len(str(os.stat(fname).st_size or 1000)) 72 73 74 class FullEntry(dict): 75 def __init__(self, val): 76 self.__dict__.update(val) 77 78 79 with open(fname, 'rb') as fp: 80 unpacker = msgpack.Unpacker(file_like=fp, read_size=1) 81 max_type = max(typ.value for typ in EntryTypes) 82 while True: 83 try: 84 pos = fp.tell() 85 typ = unpacker.unpack() 86 except msgpack.OutOfData: 87 break 88 else: 89 timestamp = unpacker.unpack() 90 time = datetime.fromtimestamp(timestamp) 91 length = unpacker.unpack() 92 if typ > max_type: 93 entry = fp.read(length) 94 typ = EntryTypes.Unknown 95 else: 96 entry = unpacker.unpack() 97 typ = EntryTypes(typ) 98 full_entry = FullEntry({ 99 'value': entry, 100 'timestamp': timestamp, 101 'time': time, 102 'length': length, 103 'pos': pos, 104 'type': typ, 105 }) 106 if not filt(full_entry): 107 continue 108 print('%*u %13s %s %5u %r' % ( 109 poswidth, pos, typ.name, time.isoformat(), length, mnormalize(entry)))