pmacs3/mode/mbox.py

254 lines
9.5 KiB
Python

import commands, dirutil, grp, mailbox, method, mode, os.path, pwd, re, sys
import buffer, default, window
from mode.mutt import MuttGrammar
from lex import Grammar, PatternRule, RegionRule, PatternGroupRule
from point import Point
from buffer import Buffer
from method import Method, Argument, arg
import util
cont_re = re.compile(r' *\n *| +')
class LineGrammar(Grammar):
rules = [PatternRule(name=r'data', pattern=r'^.*\n$')]
class MailMsgGrammar(Grammar):
rules = [
PatternRule(name=r'mail_pgp', pattern=r'^-----BEGIN PGP SIGNED MESSAGE-----\n$'),
RegionRule(r'mail_signature', r'^-----BEGIN PGP SIGNATURE-----\n$', LineGrammar, r'^-----END PGP SIGNATURE-----\n$'),
PatternRule(name=r'mail_header', pattern=r'^(?:From|To|Cc|Bcc|Subject|Reply-To|In-Reply-To|Delivered-To|Date):'),
PatternRule(name=r'mail_quoteb', pattern=r'^ *(?:(?: *>){3})*(?: *>){2}.*$'),
PatternRule(name=r'mail_quotea', pattern=r'^ *(?:(?: *>){3})*(?: *>){1}.*$'),
PatternRule(name=r'mail_quotec', pattern=r'^ *(?:(?: *>){3})*(?: *>){3}.*$'),
PatternRule(name=r'mail_email', pattern=r'(?:^|(?<=[ :]))<?[^<>@\n ]+@(?:[^<>@\.\n ]+\.)*[^<>@\.\n ]+>?'),
PatternRule(name=r'mail_url', pattern=r'(?:^|(?<= ))(?:http|https|ftp|sftp|file|smtp|smtps|torrent|news|jabber|irc|telnet)://(?:[^\.\n ]+\.)*[^\.\n ]+'),
]
class MboxMsgBuffer(Buffer):
btype = 'mboxmsg'
def __init__(self, path, lineno, msg):
Buffer.__init__(self)
self.path = os.path.realpath(path)
self.base = os.path.basename(self.path)
self.lineno = lineno
self.msg = msg
def changed(self): return False
def readonly(self): return True
def path_exists(self): raise Exception
def _make_path(self, name): raise Exception
def save(self, force=False): raise Exception, "can't save an mbox message"
def save_as(self, path): raise Exception, "can't save an mbox message"
def name(self): return 'mbox:%s:%s' % (self.base, self.lineno)
def _get_msg_lines(self, msg):
if msg.is_multipart():
lines = []
for msg2 in msg.get_payload():
lines.extend(self._get_msg_lines(msg2))
return lines
elif msg.get_content_type() == 'text/plain':
return msg.get_payload().split('\n')
else:
return []
def _get_lines(self):
d = {}
for name in ('delivered-to', 'to', 'from', 'subject', 'date'):
s = self.msg.get(name, '')
d[name] = cont_re.sub(' ', s.replace('\t', ' '))
lines = [
'Delivered-To: ' + d['delivered-to'],
'To: ' + d['to'],
'From: ' + d['from'],
'Subject: ' + d['subject'],
'Date: ' + d['date'],
'',
]
lines.extend(self._get_msg_lines(self.msg))
return lines
def open(self):
self.lines = self._get_lines()
def reload(self):
lines = self._get_lines()
self.set_lines(lines, force=True)
class MboxListBuffer(Buffer):
btype = 'mboxlist'
reverse = True
format = '%(pos)4d %(replied)1.1s %(month)3.3s %(day)2.2s %(fromname)-15.15s %(size)6.6s %(subject)s'
from_re1 = re.compile(r'^ *" *([^"]+) *" *< *(.+) *> *$')
from_re2 = re.compile(r'^ *([^" ].*?) *< *(.+) *> *$')
from_re3 = re.compile(r'^ *([^\'" ]+) *$')
from_re4 = re.compile(r'^ *([^\'" ]+) *\(([^\)]+)\) *$')
date_re1 = re.compile(r'^(Sun|Mon|Tue|Wed|Thu|Fri|Sat), (\d{1,2}) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\d{4}) (\d{2}):(\d{2}):(\d{2}) ([-\+]\d{4})(?: .*)?$')
def __init__(self, path):
Buffer.__init__(self)
self.path = os.path.realpath(path)
self.base = os.path.basename(self.path)
self.size = 0
def changed(self): return False
def readonly(self): return True
def path_exists(self): raise Exception
def _make_path(self, name): raise Exception
def save(self, force=False): raise Exception, "can't save an mbox"
def save_as(self, path): raise Exception, "can't save an mbox"
def name(self): return 'mbox:%s' % (self.base)
def open(self):
self.lines = self._get_lines()
def reload(self):
lines = self._get_lines()
self.set_lines(lines, force=True)
def _parse_msg_from(self, msg):
m = self.from_re1.match(msg['from'])
if m: return m.groups()
m = self.from_re2.match(msg['from'])
if m: return m.groups()
m = self.from_re3.match(msg['from'])
if m: return m.group(1), m.group(1)
m = self.from_re4.match(msg['from'])
if m: return m.group(2), m.group(1)
return 'unknown', 'unknown'
def _parse_date(self, msg):
fields = ['', '', '', '', '', '', '', '']
m = self.date_re1.match(msg['date'])
if m:
fields = list(m.groups())
fields[1] = '%02d' % int(fields[1])
return fields
def _create_msg_dict(self, pos, msg):
d = {}
for key in msg.keys():
d[key.lower()] = msg[key]
d['fromname'], d['fromaddr'] = self._parse_msg_from(msg)
d['dow'], d['day'], d['month'], d['year'], \
d['hour'], d['min'], d['sec'], d['tz'] = self._parse_date(msg)
if 'A' in msg.get_flags():
d['replied'] = 'r'
else:
d['replied'] = ' '
d['pos'] = pos
d['flags'] = ''.join(msg.get_flags())
d['size'] = len(str(msg))
for key in d:
if type(d[key]) == type(''):
d[key] = d[key].replace('\t', ' ')
d[key] = cont_re.sub(' ', d[key])
return d
def _get_lines(self):
f = open(self.path, 'r')
self.size = len(f.read())
f.close()
if sys.version_info[1] == 4:
fp = open(self.path, 'rb')
self.mbox = mailbox.UnixMailbox(fp)
else:
self.mbox = mailbox.mbox(self.path)
lines = []
pos = 1
msgs = list(self.mbox)
if self.reverse:
msgs.reverse()
for msg in msgs:
d = self._create_msg_dict(pos, msg)
s = self.format % d
lines.append(s.rstrip())
pos += 1
return lines
class MboxRefresh(Method):
def _execute(self, w, **vargs):
w.buffer.reload()
class MboxReadMsg(Method):
def _execute(self, w, **vargs):
b = w.buffer
if w.buffer.reverse:
i = len(b.mbox) - 1 - w.cursor.y
else:
i = w.cursor.y
b = MboxMsgBuffer(b.path, i, b.mbox[i])
b.open()
window.Window(b, w.application, height=0, width=0, mode_name='mboxmsg')
w.application.add_buffer(b)
w.application.switch_buffer(b)
class MboxOpenPath(Method):
args = [arg('mbox', dt="path", p="Open Mbox: ", dv=default.path_dirname,
ld=True, h="mbox to open")]
def _execute(self, w, **vargs):
path = util.expand_tilde(vargs['mbox'])
path = os.path.abspath(os.path.realpath(path))
b = MboxListBuffer(path)
b.open()
window.Window(b, w.application, height=0, width=0, mode_name='mbox')
w.application.add_buffer(b)
w.application.switch_buffer(b)
class MailListGrammar(Grammar):
rules = [
PatternGroupRule(r'xyz',
r'index', r'^ *[0-9]+',
r'spaces', r' ',
r'flag', r'.',
r'spaces', r' ',
r'month', r'Jan|Fed|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec',
#r'dow', r'Mon|Tue|Wed|Thu|Fri|Sat|Sun',
r'spaces', r' +',
r'day', r'[0-9]+',
r'spaces', r' +',
r'sender', r'.{16}',
r'spaces', r' +',
r'size', r'[0-9]+',
r'spaces', ' +',
r'subject', r'.+$'),
]
class MboxMsg(mode.Fundamental):
modename = 'MboxMsg'
colors = {
'mail_pgp': ('red', 'default', 'bold'),
'mail_signature.start': ('red', 'default', 'bold'),
'mail_signature.data': ('red', 'default', 'bold'),
'mail_signature.null': ('red', 'default', 'bold'),
'mail_signature.end': ('red', 'default', 'bold'),
'mail_header': ('green', 'default', 'bold'),
'mail_email': ('cyan', 'default', 'bold'),
'mail_url': ('cyan', 'default', 'bold'),
'mail_quotea': ('yellow', 'default', 'bold'),
'mail_quoteb': ('cyan', 'default', 'bold'),
'mail_quotec': ('magenta', 'default', 'bold'),
}
actions = []
grammar = MailMsgGrammar
def __init__(self, w):
mode.Fundamental.__init__(self, w)
class Mbox(mode.Fundamental):
modename = 'Mbox'
grammar = MailListGrammar
actions = [MboxRefresh, MboxOpenPath, MboxReadMsg]
colors = {
'index': ('default', 'default', 'bold'),
'flag': ('yellow', 'default', 'bold'),
'month': ('green', 'default', 'bold'),
'dow': ('green', 'default', 'bold'),
'day': ('green', 'default', 'bold'),
'sender': ('default', 'default', 'bold'),
'size': ('cyan', 'default', 'bold'),
'subject': ('default', 'default', 'bold'),
}
def __init__(self, w):
mode.Fundamental.__init__(self, w)
self.add_bindings('mbox-refresh', ('C-c r',))
self.add_bindings('mbox-read-msg', ('RETURN',))
def install(*args):
Mbox.install(*args)
MboxMsg.install(*args)