"""Mailcap file handling. See RFC 1524.""" | |
import os | |
import string | |
# 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 corresponding mailcap entries. | |
""" | |
caps = {} | |
for mailcap in listmailcapfiles(): | |
try: | |
fp = open(mailcap, 'r') | |
except: | |
continue | |
morecaps = readmailcapfile(fp) | |
fp.close() | |
for key in morecaps.keys(): | |
if not caps.has_key(key): | |
caps[key] = morecaps[key] | |
else: | |
caps[key] = caps[key] + morecaps[key] | |
return caps | |
def listmailcapfiles(): | |
"""Return a list of all mailcap files found on the system.""" | |
# XXX Actually, this is Unix-specific | |
if os.environ.has_key('MAILCAPS'): | |
str = os.environ['MAILCAPS'] | |
mailcaps = string.splitfields(str, ':') | |
else: | |
if os.environ.has_key('HOME'): | |
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): | |
caps = {} | |
while 1: | |
line = fp.readline() | |
if not line: break | |
# Ignore comments and blank lines | |
if line[0] == '#' or string.strip(line) == '': | |
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 = string.splitfields(key, '/') | |
for j in range(len(types)): | |
types[j] = string.strip(types[j]) | |
key = string.lower(string.joinfields(types, '/')) | |
# Update the database | |
if caps.has_key(key): | |
caps[key].append(fields) | |
else: | |
caps[key] = [fields] | |
return caps | |
def parseline(line): | |
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 = string.find(field, '=') | |
if i < 0: | |
fkey = field | |
fvalue = "" | |
else: | |
fkey = string.strip(field[:i]) | |
fvalue = string.strip(field[i+1:]) | |
if fields.has_key(fkey): | |
# Ignore it | |
pass | |
else: | |
fields[fkey] = fvalue | |
return key, fields | |
def parsefield(line, i, n): | |
start = i | |
while i < n: | |
c = line[i] | |
if c == ';': | |
break | |
elif c == '\\': | |
i = i+2 | |
else: | |
i = i+1 | |
return string.strip(line[start:i]), 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 e.has_key('test'): | |
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 caps.has_key(MIMEtype): | |
entries = entries + caps[MIMEtype] | |
MIMEtypes = string.splitfields(MIMEtype, '/') | |
MIMEtype = MIMEtypes[0] + '/*' | |
if caps.has_key(MIMEtype): | |
entries = entries + caps[MIMEtype] | |
if key is not None: | |
entries = filter(lambda e, key=key: e.has_key(key), 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 = string.lower(name) + '=' | |
n = len(name) | |
for p in plist: | |
if string.lower(p[:n]) == 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() |