#!/usr/bin/env python3
# -*- coding: ascii -*-

# A script dumping Instant message logs in a format similar to the copy-paste
# pretty-printer.

import sys
import operator
import instabot, id2time, scribe

# Provide a fancy __name__ for help display.
class msgid(id2time.MessageID): pass

# Return msglist sorted in the order it would be put into by the Web client.
# NOTE that messages' immediate parent might be missing.
def sort_threads(msglist):
    def dump(k):
        if k in index:
            ret.append(index[k])
        ids = [m['id'] for m in children.get(k, ())]
        ids.sort()
        for k in ids:
            dump(k)
    # Put messages into indexes
    index, children = {}, {}
    for m in msglist:
        index[m['id']] = m
        children.setdefault(m.get('parent'), []).append(m)
    roots = [k for k in children if k is not None and
             (k not in index or index[k].get('parent') is None)]
    roots.sort()
    # Extract messages again
    ret = []
    for k in roots: dump(k)
    return ret

# Return a plain-text representation of msg.
# NOTE that there is no trailing newline.
def format_message(msg, indent='', mono=False):
    prefix, lines = '<%s> ' % msg['nick'], msg['text'].split('\n')
    align = ' ' * len(prefix) if mono else '  '
    res = [(prefix if n == 0 else align) + l for n, l in enumerate(lines)]
    return '\n'.join(indent + l for l in res)

# Return a plain-text representation of the messages in msglist.
# NOTE that msglist is used in the order given, and there is no trailing
#      newline.
def format_logs(msglist, mono=False):
    stack, res = [], []
    for m in msglist:
        while stack and stack[-1] != m.get('parent'): stack.pop()
        if m.get('parent') is not None and not stack:
            res.append('| ' * len(stack) + '...')
            stack.append(m['parent'])
        res.append(format_message(m, '| ' * len(stack), mono))
        stack.append(m['id'])
    return '\n'.join(res)

def main():
    # Parse command line
    p = instabot.OptionParser(sys.argv[0])
    p.help_action()
    p.option('from', short='f', type=msgid,
             help='Minimal (earliest) message ID to output')
    p.option('to', short='t', type=msgid,
             help='Maximal (latest) message ID to output')
    p.option('length', short='l', type=int,
             help='Maximal amount of messages to output')
    p.flag('mono', short='m',
           help='Optimize indents for monospaced display')
    p.argument('msgdb', help='Database file to dump logs from')
    p.parse(sys.argv[1:])
    # Retrieve messages
    lfrom, lto, amount = p.get('from', 'to', 'length')
    if lfrom is not None: lfrom = lfrom.format_id()
    if lto is not None: lto = lto.format_id()
    db = scribe.LogDBSQLite(p.get('msgdb'))
    db.init()
    try:
        messages = db.query(lfrom, lto, amount)
    finally:
        db.close()
    # Format messages
    print (format_logs(sort_threads(messages), p.get('mono')))

if __name__ == '__main__': main()

@@ -1021,16 +1021,102 @@
 rn ret%0A%0A
+class LogFormatter:%0A    def __init__(self, mono=False):%0A        self.mono = mono%0A%0A    
 # Return
@@ -1152,16 +1152,20 @@
 of msg.%0A
+    
 # NOTE t
@@ -1190,32 +1190,36 @@
 ailing newline.%0A
+    
 def format_messa
@@ -1221,16 +1221,22 @@
 message(
+self, 
 msg, ind
@@ -1241,35 +1241,27 @@
 ndent=''
-, mono=False
 ):%0A
+   
     
+ 
 prefix, 
@@ -1315,16 +1315,20 @@
 t('%5Cn')%0A
+    
     alig
@@ -1352,16 +1352,21 @@
 fix) if 
+self.
 mono els
@@ -1372,16 +1372,20 @@
 se '  '%0A
+    
     res 
@@ -1416,24 +1416,39 @@
 e align) + l
+%0A              
  for n, l in
@@ -1466,16 +1466,20 @@
 lines)%5D%0A
+    
     retu
@@ -1517,16 +1517,20 @@
 n res)%0A%0A
+    
 # Return
@@ -1586,16 +1586,20 @@
 sglist.%0A
+    
 # NOTE t
@@ -1663,16 +1663,20 @@
 railing%0A
+    
 #      n
@@ -1683,16 +1683,20 @@
 ewline.%0A
+    
 def form
@@ -1707,30 +1707,28 @@
 ogs(
-msglist, mono=False):%0A
+self, msglist):%0A    
     
@@ -1747,24 +1747,28 @@
  %5B%5D, %5B%5D%0A    
+ 
+   
 for m in msg
@@ -1765,32 +1765,36 @@
 r m in msglist:%0A
+    
         while st
@@ -1851,16 +1851,20 @@
         
+    
 if m.get
@@ -1905,32 +1905,36 @@
 ck:%0A            
+   
+ 
 res.append('%7C ' 
@@ -1959,32 +1959,36 @@
 .')%0A            
+    
 stack.append(m%5B'
@@ -1997,32 +1997,36 @@
 rent'%5D)%0A        
+  
+  
 res.append(forma
@@ -2020,16 +2020,21 @@
 .append(
+self.
 format_m
@@ -2064,17 +2064,15 @@
 ack)
-, mono))%0A
+))%0A    
     
@@ -2093,24 +2093,28 @@
 nd(m%5B'id'%5D)%0A
+    
     return '
@@ -3057,24 +3057,67 @@
 at messages%0A
+    fmt = LogFormatter(mono=p.get('mono'))%0A
     print (f
@@ -3115,16 +3115,20 @@
  print (
+fmt.
 format_l
@@ -3157,23 +3157,8 @@
 ges)
-, p.get('mono')
 ))%0A%0A

Refactor log dumper to use OOP