Back to index

python3.2  3.2.2
mailcap.py
Go to the documentation of this file.
00001 """Mailcap file handling.  See RFC 1524."""
00002 
00003 import os
00004 
00005 __all__ = ["getcaps","findmatch"]
00006 
00007 # Part 1: top-level interface.
00008 
00009 def getcaps():
00010     """Return a dictionary containing the mailcap database.
00011 
00012     The dictionary maps a MIME type (in all lowercase, e.g. 'text/plain')
00013     to a list of dictionaries corresponding to mailcap entries.  The list
00014     collects all the entries for that MIME type from all available mailcap
00015     files.  Each dictionary contains key-value pairs for that MIME type,
00016     where the viewing command is stored with the key "view".
00017 
00018     """
00019     caps = {}
00020     for mailcap in listmailcapfiles():
00021         try:
00022             fp = open(mailcap, 'r')
00023         except IOError:
00024             continue
00025         morecaps = readmailcapfile(fp)
00026         fp.close()
00027         for key, value in morecaps.items():
00028             if not key in caps:
00029                 caps[key] = value
00030             else:
00031                 caps[key] = caps[key] + value
00032     return caps
00033 
00034 def listmailcapfiles():
00035     """Return a list of all mailcap files found on the system."""
00036     # XXX Actually, this is Unix-specific
00037     if 'MAILCAPS' in os.environ:
00038         str = os.environ['MAILCAPS']
00039         mailcaps = str.split(':')
00040     else:
00041         if 'HOME' in os.environ:
00042             home = os.environ['HOME']
00043         else:
00044             # Don't bother with getpwuid()
00045             home = '.' # Last resort
00046         mailcaps = [home + '/.mailcap', '/etc/mailcap',
00047                 '/usr/etc/mailcap', '/usr/local/etc/mailcap']
00048     return mailcaps
00049 
00050 
00051 # Part 2: the parser.
00052 
00053 def readmailcapfile(fp):
00054     """Read a mailcap file and return a dictionary keyed by MIME type.
00055 
00056     Each MIME type is mapped to an entry consisting of a list of
00057     dictionaries; the list will contain more than one such dictionary
00058     if a given MIME type appears more than once in the mailcap file.
00059     Each dictionary contains key-value pairs for that MIME type, where
00060     the viewing command is stored with the key "view".
00061     """
00062     caps = {}
00063     while 1:
00064         line = fp.readline()
00065         if not line: break
00066         # Ignore comments and blank lines
00067         if line[0] == '#' or line.strip() == '':
00068             continue
00069         nextline = line
00070         # Join continuation lines
00071         while nextline[-2:] == '\\\n':
00072             nextline = fp.readline()
00073             if not nextline: nextline = '\n'
00074             line = line[:-2] + nextline
00075         # Parse the line
00076         key, fields = parseline(line)
00077         if not (key and fields):
00078             continue
00079         # Normalize the key
00080         types = key.split('/')
00081         for j in range(len(types)):
00082             types[j] = types[j].strip()
00083         key = '/'.join(types).lower()
00084         # Update the database
00085         if key in caps:
00086             caps[key].append(fields)
00087         else:
00088             caps[key] = [fields]
00089     return caps
00090 
00091 def parseline(line):
00092     """Parse one entry in a mailcap file and return a dictionary.
00093 
00094     The viewing command is stored as the value with the key "view",
00095     and the rest of the fields produce key-value pairs in the dict.
00096     """
00097     fields = []
00098     i, n = 0, len(line)
00099     while i < n:
00100         field, i = parsefield(line, i, n)
00101         fields.append(field)
00102         i = i+1 # Skip semicolon
00103     if len(fields) < 2:
00104         return None, None
00105     key, view, rest = fields[0], fields[1], fields[2:]
00106     fields = {'view': view}
00107     for field in rest:
00108         i = field.find('=')
00109         if i < 0:
00110             fkey = field
00111             fvalue = ""
00112         else:
00113             fkey = field[:i].strip()
00114             fvalue = field[i+1:].strip()
00115         if fkey in fields:
00116             # Ignore it
00117             pass
00118         else:
00119             fields[fkey] = fvalue
00120     return key, fields
00121 
00122 def parsefield(line, i, n):
00123     """Separate one key-value pair in a mailcap entry."""
00124     start = i
00125     while i < n:
00126         c = line[i]
00127         if c == ';':
00128             break
00129         elif c == '\\':
00130             i = i+2
00131         else:
00132             i = i+1
00133     return line[start:i].strip(), i
00134 
00135 
00136 # Part 3: using the database.
00137 
00138 def findmatch(caps, MIMEtype, key='view', filename="/dev/null", plist=[]):
00139     """Find a match for a mailcap entry.
00140 
00141     Return a tuple containing the command line, and the mailcap entry
00142     used; (None, None) if no match is found.  This may invoke the
00143     'test' command of several matching entries before deciding which
00144     entry to use.
00145 
00146     """
00147     entries = lookup(caps, MIMEtype, key)
00148     # XXX This code should somehow check for the needsterminal flag.
00149     for e in entries:
00150         if 'test' in e:
00151             test = subst(e['test'], filename, plist)
00152             if test and os.system(test) != 0:
00153                 continue
00154         command = subst(e[key], MIMEtype, filename, plist)
00155         return command, e
00156     return None, None
00157 
00158 def lookup(caps, MIMEtype, key=None):
00159     entries = []
00160     if MIMEtype in caps:
00161         entries = entries + caps[MIMEtype]
00162     MIMEtypes = MIMEtype.split('/')
00163     MIMEtype = MIMEtypes[0] + '/*'
00164     if MIMEtype in caps:
00165         entries = entries + caps[MIMEtype]
00166     if key is not None:
00167         entries = [e for e in entries if key in e]
00168     return entries
00169 
00170 def subst(field, MIMEtype, filename, plist=[]):
00171     # XXX Actually, this is Unix-specific
00172     res = ''
00173     i, n = 0, len(field)
00174     while i < n:
00175         c = field[i]; i = i+1
00176         if c != '%':
00177             if c == '\\':
00178                 c = field[i:i+1]; i = i+1
00179             res = res + c
00180         else:
00181             c = field[i]; i = i+1
00182             if c == '%':
00183                 res = res + c
00184             elif c == 's':
00185                 res = res + filename
00186             elif c == 't':
00187                 res = res + MIMEtype
00188             elif c == '{':
00189                 start = i
00190                 while i < n and field[i] != '}':
00191                     i = i+1
00192                 name = field[start:i]
00193                 i = i+1
00194                 res = res + findparam(name, plist)
00195             # XXX To do:
00196             # %n == number of parts if type is multipart/*
00197             # %F == list of alternating type and filename for parts
00198             else:
00199                 res = res + '%' + c
00200     return res
00201 
00202 def findparam(name, plist):
00203     name = name.lower() + '='
00204     n = len(name)
00205     for p in plist:
00206         if p[:n].lower() == name:
00207             return p[n:]
00208     return ''
00209 
00210 
00211 # Part 4: test program.
00212 
00213 def test():
00214     import sys
00215     caps = getcaps()
00216     if not sys.argv[1:]:
00217         show(caps)
00218         return
00219     for i in range(1, len(sys.argv), 2):
00220         args = sys.argv[i:i+2]
00221         if len(args) < 2:
00222             print("usage: mailcap [MIMEtype file] ...")
00223             return
00224         MIMEtype = args[0]
00225         file = args[1]
00226         command, e = findmatch(caps, MIMEtype, 'view', file)
00227         if not command:
00228             print("No viewer found for", type)
00229         else:
00230             print("Executing:", command)
00231             sts = os.system(command)
00232             if sts:
00233                 print("Exit status:", sts)
00234 
00235 def show(caps):
00236     print("Mailcap files:")
00237     for fn in listmailcapfiles(): print("\t" + fn)
00238     print()
00239     if not caps: caps = getcaps()
00240     print("Mailcap entries:")
00241     print()
00242     ckeys = sorted(caps)
00243     for type in ckeys:
00244         print(type)
00245         entries = caps[type]
00246         for e in entries:
00247             keys = sorted(e)
00248             for k in keys:
00249                 print("  %-15s" % k, e[k])
00250             print()
00251 
00252 if __name__ == '__main__':
00253     test()