neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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)))