"""Mailcap file handling. See RFC 1524.""" | |
import os | |
__all__ = ["getcaps","findmatch"] | |
# Part 1: top-level interface. | |
def getcaps(): | |
"""Return a dictionary containing the mailcap database. | |
The dictionary maps a MIME type (in all lowercase, e.g. 'text/plain') | |
to a list of dictionaries corresponding to mailcap entries. The list | |
collects all the entries for that MIME type from all available mailcap | |
files. Each dictionary contains key-value pairs for that MIME type, | |
where the viewing command is stored with the key "view". | |
""" | |
caps = {} | |
for mailcap in listmailcapfiles(): | |
try: | |
fp = open(mailcap, 'r') | |
except IOError: | |
continue | |
with fp: | |
morecaps = readmailcapfile(fp) | |
for key, value in morecaps.iteritems(): | |
if not key in caps: | |
caps[key] = value | |
else: | |
caps[key] = caps[key] + value | |
return caps | |
def listmailcapfiles(): | |
"""Return a list of all mailcap files found on the system.""" | |
# XXX Actually, this is Unix-specific | |
if 'MAILCAPS' in os.environ: | |
str = os.environ['MAILCAPS'] | |
mailcaps = str.split(':') | |
else: | |
if 'HOME' in os.environ: | |
home = os.environ['HOME'] | |
else: | |
# Don't bother with getpwuid() | |
home = '.' # Last resort | |
mailcaps = [home + '/.mailcap', '/etc/mailcap', | |
'/usr/etc/mailcap', '/usr/local/etc/mailcap'] | |
return mailcaps | |
# Part 2: the parser. | |
def readmailcapfile(fp): | |
"""Read a mailcap file and return a dictionary keyed by MIME type. | |
Each MIME type is mapped to an entry consisting of a list of | |
dictionaries; the list will contain more than one such dictionary | |
if a given MIME type appears more than once in the mailcap file. | |
Each dictionary contains key-value pairs for that MIME type, where | |
the viewing command is stored with the key "view". | |
""" | |
caps = {} | |
while 1: | |
line = fp.readline() | |
if not line: break | |
# Ignore comments and blank lines | |
if line[0] == '#' or line.strip() == '': | |
continue | |
nextline = line | |
# Join continuation lines | |
while nextline[-2:] == '\\\n': | |
nextline = fp.readline() | |
if not nextline: nextline = '\n' | |
line = line[:-2] + nextline | |
# Parse the line | |
key, fields = parseline(line) | |
if not (key and fields): | |
continue | |
# Normalize the key | |
types = key.split('/') | |
for j in range(len(types)): | |
types[j] = types[j].strip() | |
key = '/'.join(types).lower() | |
# Update the database | |
if key in caps: | |
caps[key].append(fields) | |
else: | |
caps[key] = [fields] | |
return caps | |
def parseline(line): | |
"""Parse one entry in a mailcap file and return a dictionary. | |
The viewing command is stored as the value with the key "view", | |
and the rest of the fields produce key-value pairs in the dict. | |
""" | |
fields = [] | |
i, n = 0, len(line) | |
while i < n: | |
field, i = parsefield(line, i, n) | |
fields.append(field) | |
i = i+1 # Skip semicolon | |
if len(fields) < 2: | |
return None, None | |
key, view, rest = fields[0], fields[1], fields[2:] | |
fields = {'view': view} | |
for field in rest: | |
i = field.find('=') | |
if i < 0: | |
fkey = field | |
fvalue = "" | |
else: | |
fkey = field[:i].strip() | |
fvalue = field[i+1:].strip() | |
if fkey in fields: | |
# Ignore it | |
pass | |
else: | |
fields[fkey] = fvalue | |
return key, fields | |
def parsefield(line, i, n): | |
"""Separate one key-value pair in a mailcap entry.""" | |
start = i | |
while i < n: | |
c = line[i] | |
if c == ';': | |
break | |
elif c == '\\': | |
i = i+2 | |
else: | |
i = i+1 | |
return line[start:i].strip(), i | |
# Part 3: using the database. | |
def findmatch(caps, MIMEtype, key='view', filename="/dev/null", plist=[]): | |
"""Find a match for a mailcap entry. | |
Return a tuple containing the command line, and the mailcap entry | |
used; (None, None) if no match is found. This may invoke the | |
'test' command of several matching entries before deciding which | |
entry to use. | |
""" | |
entries = lookup(caps, MIMEtype, key) | |
# XXX This code should somehow check for the needsterminal flag. | |
for e in entries: | |
if 'test' in e: | |
test = subst(e['test'], filename, plist) | |
if test and os.system(test) != 0: | |
continue | |
command = subst(e[key], MIMEtype, filename, plist) | |
return command, e | |
return None, None | |
def lookup(caps, MIMEtype, key=None): | |
entries = [] | |
if MIMEtype in caps: | |
entries = entries + caps[MIMEtype] | |
MIMEtypes = MIMEtype.split('/') | |
MIMEtype = MIMEtypes[0] + '/*' | |
if MIMEtype in caps: | |
entries = entries + caps[MIMEtype] | |
if key is not None: | |
entries = filter(lambda e, key=key: key in e, entries) | |
return entries | |
def subst(field, MIMEtype, filename, plist=[]): | |
# XXX Actually, this is Unix-specific | |
res = '' | |
i, n = 0, len(field) | |
while i < n: | |
c = field[i]; i = i+1 | |
if c != '%': | |
if c == '\\': | |
c = field[i:i+1]; i = i+1 | |
res = res + c | |
else: | |
c = field[i]; i = i+1 | |
if c == '%': | |
res = res + c | |
elif c == 's': | |
res = res + filename | |
elif c == 't': | |
res = res + MIMEtype | |
elif c == '{': | |
start = i | |
while i < n and field[i] != '}': | |
i = i+1 | |
name = field[start:i] | |
i = i+1 | |
res = res + findparam(name, plist) | |
# XXX To do: | |
# %n == number of parts if type is multipart/* | |
# %F == list of alternating type and filename for parts | |
else: | |
res = res + '%' + c | |
return res | |
def findparam(name, plist): | |
name = name.lower() + '=' | |
n = len(name) | |
for p in plist: | |
if p[:n].lower() == name: | |
return p[n:] | |
return '' | |
# Part 4: test program. | |
def test(): | |
import sys | |
caps = getcaps() | |
if not sys.argv[1:]: | |
show(caps) | |
return | |
for i in range(1, len(sys.argv), 2): | |
args = sys.argv[i:i+2] | |
if len(args) < 2: | |
print "usage: mailcap [MIMEtype file] ..." | |
return | |
MIMEtype = args[0] | |
file = args[1] | |
command, e = findmatch(caps, MIMEtype, 'view', file) | |
if not command: | |
print "No viewer found for", type | |
else: | |
print "Executing:", command | |
sts = os.system(command) | |
if sts: | |
print "Exit status:", sts | |
def show(caps): | |
print "Mailcap files:" | |
for fn in listmailcapfiles(): print "\t" + fn | |
if not caps: caps = getcaps() | |
print "Mailcap entries:" | |
ckeys = caps.keys() | |
ckeys.sort() | |
for type in ckeys: | |
print type | |
entries = caps[type] | |
for e in entries: | |
keys = e.keys() | |
keys.sort() | |
for k in keys: | |
print " %-15s" % k, e[k] | |
if __name__ == '__main__': | |
test() |