import mailbox, os.path, re, sys import buffer, default, window from mode import Fundamental from mode.mutt import MuttGrammar from lex import Grammar, PatternRule, RegionRule, PatternMatchRule 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}) ([^ ]+)(?: .*)?$') 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 = [ PatternMatchRule( r'x', r'^( *)([0-9]+)( )(.)( )(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)( +)([0-9]+)( +)(.{16})( +)([0-9]+)( +)(.+)$', r'spaces', r'index', r'spaces', r'flag', r'spaces', r'month', r'spaces', r'day', r'spaces', r'sender', r'spaces', r'size', r'spaces', r'subject' ), ] class MboxMsg(Fundamental): name = '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): Fundamental.__init__(self, w) class Mbox(Fundamental): name = '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): 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)