aboutsummaryrefslogtreecommitdiff
path: root/scripts/shadacat.py
blob: 2b71fc238578d06587736bfc515d086bf0a82051 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#!/usr/bin/env python3

import os
import sys
import codecs

from enum import Enum
from datetime import datetime
from functools import reduce

import msgpack


class EntryTypes(Enum):
    Unknown = -1
    Missing = 0
    Header = 1
    SearchPattern = 2
    SubString = 3
    HistoryEntry = 4
    Register = 5
    Variable = 6
    GlobalMark = 7
    Jump = 8
    BufferList = 9
    LocalMark = 10
    Change = 11


def strtrans_errors(e):
    if not isinstance(e, UnicodeDecodeError):
        raise NotImplementedError('don’t know how to handle {0} error'.format(
            e.__class__.__name__))
    return '<{0:x}>'.format(reduce((lambda a, b: a*0x100+b),
                                   list(e.object[e.start:e.end]))), e.end


codecs.register_error('strtrans', strtrans_errors)


def idfunc(o):
    return o


class CharInt(int):
    def __repr__(self):
        return super(CharInt, self).__repr__() + ' (\'%s\')' % chr(self)


ctable = {
    bytes: lambda s: s.decode('utf-8', 'strtrans'),
    dict: lambda d: dict((mnormalize(k), mnormalize(v)) for k, v in d.items()),
    list: lambda l: list(mnormalize(i) for i in l),
    int: lambda n: CharInt(n) if 0x20 <= n <= 0x7E else n,
}


def mnormalize(o):
    return ctable.get(type(o), idfunc)(o)


fname = sys.argv[1]
try:
    filt = sys.argv[2]
except IndexError:
    def filt(entry): return True
else:
    _filt = filt
    def filt(entry): return eval(_filt, globals(), {'entry': entry})  # noqa

poswidth = len(str(os.stat(fname).st_size or 1000))


class FullEntry(dict):
    def __init__(self, val):
        self.__dict__.update(val)


with open(fname, 'rb') as fp:
    unpacker = msgpack.Unpacker(file_like=fp, read_size=1)
    max_type = max(typ.value for typ in EntryTypes)
    while True:
        try:
            pos = fp.tell()
            typ = unpacker.unpack()
        except msgpack.OutOfData:
            break
        else:
            timestamp = unpacker.unpack()
            time = datetime.fromtimestamp(timestamp)
            length = unpacker.unpack()
            if typ > max_type:
                entry = fp.read(length)
                typ = EntryTypes.Unknown
            else:
                entry = unpacker.unpack()
                typ = EntryTypes(typ)
            full_entry = FullEntry({
                'value': entry,
                'timestamp': timestamp,
                'time': time,
                'length': length,
                'pos': pos,
                'type': typ,
            })
            if not filt(full_entry):
                continue
            print('%*u %13s %s %5u %r' % (
                poswidth, pos, typ.name, time.isoformat(), length, mnormalize(entry)))