255 lines · 7.3 KB · Python
RawDownload
"""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        morecaps = readmailcapfile(fp)        fp.close()        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    print    if not caps: caps = getcaps()    print "Mailcap entries:"    print    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]            print if __name__ == '__main__':    test()