| """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 OSError: |
| continue |
| morecaps = readmailcapfile(fp) |
| fp.close() |
| for key, value in morecaps.items(): |
| 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.""" |
| # This is mostly a Unix thing, but we use the OS path separator anyway |
| if 'MAILCAPS' in os.environ: |
| pathstr = os.environ['MAILCAPS'] |
| mailcaps = pathstr.split(os.pathsep) |
| 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 = [e for e in entries if key in e] |
| 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) |
| print() |
| if not caps: caps = getcaps() |
| print("Mailcap entries:") |
| print() |
| ckeys = sorted(caps) |
| for type in ckeys: |
| print(type) |
| entries = caps[type] |
| for e in entries: |
| keys = sorted(e) |
| for k in keys: |
| print(" %-15s" % k, e[k]) |
| print() |
| |
| if __name__ == '__main__': |
| test() |