PageRenderTime 443ms CodeModel.GetById 161ms app.highlight 144ms RepoModel.GetById 128ms app.codeStats 0ms

/Lib/mailcap.py

http://unladen-swallow.googlecode.com/
Python | 255 lines | 237 code | 7 blank | 11 comment | 0 complexity | a26af918e2f1352dc66a113c74dd95bc MD5 | raw file
  1"""Mailcap file handling.  See RFC 1524."""
  2
  3import os
  4
  5__all__ = ["getcaps","findmatch"]
  6
  7# Part 1: top-level interface.
  8
  9def getcaps():
 10    """Return a dictionary containing the mailcap database.
 11
 12    The dictionary maps a MIME type (in all lowercase, e.g. 'text/plain')
 13    to a list of dictionaries corresponding to mailcap entries.  The list
 14    collects all the entries for that MIME type from all available mailcap
 15    files.  Each dictionary contains key-value pairs for that MIME type,
 16    where the viewing command is stored with the key "view".
 17
 18    """
 19    caps = {}
 20    for mailcap in listmailcapfiles():
 21        try:
 22            fp = open(mailcap, 'r')
 23        except IOError:
 24            continue
 25        morecaps = readmailcapfile(fp)
 26        fp.close()
 27        for key, value in morecaps.iteritems():
 28            if not key in caps:
 29                caps[key] = value
 30            else:
 31                caps[key] = caps[key] + value
 32    return caps
 33
 34def listmailcapfiles():
 35    """Return a list of all mailcap files found on the system."""
 36    # XXX Actually, this is Unix-specific
 37    if 'MAILCAPS' in os.environ:
 38        str = os.environ['MAILCAPS']
 39        mailcaps = str.split(':')
 40    else:
 41        if 'HOME' in os.environ:
 42            home = os.environ['HOME']
 43        else:
 44            # Don't bother with getpwuid()
 45            home = '.' # Last resort
 46        mailcaps = [home + '/.mailcap', '/etc/mailcap',
 47                '/usr/etc/mailcap', '/usr/local/etc/mailcap']
 48    return mailcaps
 49
 50
 51# Part 2: the parser.
 52
 53def readmailcapfile(fp):
 54    """Read a mailcap file and return a dictionary keyed by MIME type.
 55
 56    Each MIME type is mapped to an entry consisting of a list of
 57    dictionaries; the list will contain more than one such dictionary
 58    if a given MIME type appears more than once in the mailcap file.
 59    Each dictionary contains key-value pairs for that MIME type, where
 60    the viewing command is stored with the key "view".
 61    """
 62    caps = {}
 63    while 1:
 64        line = fp.readline()
 65        if not line: break
 66        # Ignore comments and blank lines
 67        if line[0] == '#' or line.strip() == '':
 68            continue
 69        nextline = line
 70        # Join continuation lines
 71        while nextline[-2:] == '\\\n':
 72            nextline = fp.readline()
 73            if not nextline: nextline = '\n'
 74            line = line[:-2] + nextline
 75        # Parse the line
 76        key, fields = parseline(line)
 77        if not (key and fields):
 78            continue
 79        # Normalize the key
 80        types = key.split('/')
 81        for j in range(len(types)):
 82            types[j] = types[j].strip()
 83        key = '/'.join(types).lower()
 84        # Update the database
 85        if key in caps:
 86            caps[key].append(fields)
 87        else:
 88            caps[key] = [fields]
 89    return caps
 90
 91def parseline(line):
 92    """Parse one entry in a mailcap file and return a dictionary.
 93
 94    The viewing command is stored as the value with the key "view",
 95    and the rest of the fields produce key-value pairs in the dict.
 96    """
 97    fields = []
 98    i, n = 0, len(line)
 99    while i < n:
100        field, i = parsefield(line, i, n)
101        fields.append(field)
102        i = i+1 # Skip semicolon
103    if len(fields) < 2:
104        return None, None
105    key, view, rest = fields[0], fields[1], fields[2:]
106    fields = {'view': view}
107    for field in rest:
108        i = field.find('=')
109        if i < 0:
110            fkey = field
111            fvalue = ""
112        else:
113            fkey = field[:i].strip()
114            fvalue = field[i+1:].strip()
115        if fkey in fields:
116            # Ignore it
117            pass
118        else:
119            fields[fkey] = fvalue
120    return key, fields
121
122def parsefield(line, i, n):
123    """Separate one key-value pair in a mailcap entry."""
124    start = i
125    while i < n:
126        c = line[i]
127        if c == ';':
128            break
129        elif c == '\\':
130            i = i+2
131        else:
132            i = i+1
133    return line[start:i].strip(), i
134
135
136# Part 3: using the database.
137
138def findmatch(caps, MIMEtype, key='view', filename="/dev/null", plist=[]):
139    """Find a match for a mailcap entry.
140
141    Return a tuple containing the command line, and the mailcap entry
142    used; (None, None) if no match is found.  This may invoke the
143    'test' command of several matching entries before deciding which
144    entry to use.
145
146    """
147    entries = lookup(caps, MIMEtype, key)
148    # XXX This code should somehow check for the needsterminal flag.
149    for e in entries:
150        if 'test' in e:
151            test = subst(e['test'], filename, plist)
152            if test and os.system(test) != 0:
153                continue
154        command = subst(e[key], MIMEtype, filename, plist)
155        return command, e
156    return None, None
157
158def lookup(caps, MIMEtype, key=None):
159    entries = []
160    if MIMEtype in caps:
161        entries = entries + caps[MIMEtype]
162    MIMEtypes = MIMEtype.split('/')
163    MIMEtype = MIMEtypes[0] + '/*'
164    if MIMEtype in caps:
165        entries = entries + caps[MIMEtype]
166    if key is not None:
167        entries = filter(lambda e, key=key: key in e, entries)
168    return entries
169
170def subst(field, MIMEtype, filename, plist=[]):
171    # XXX Actually, this is Unix-specific
172    res = ''
173    i, n = 0, len(field)
174    while i < n:
175        c = field[i]; i = i+1
176        if c != '%':
177            if c == '\\':
178                c = field[i:i+1]; i = i+1
179            res = res + c
180        else:
181            c = field[i]; i = i+1
182            if c == '%':
183                res = res + c
184            elif c == 's':
185                res = res + filename
186            elif c == 't':
187                res = res + MIMEtype
188            elif c == '{':
189                start = i
190                while i < n and field[i] != '}':
191                    i = i+1
192                name = field[start:i]
193                i = i+1
194                res = res + findparam(name, plist)
195            # XXX To do:
196            # %n == number of parts if type is multipart/*
197            # %F == list of alternating type and filename for parts
198            else:
199                res = res + '%' + c
200    return res
201
202def findparam(name, plist):
203    name = name.lower() + '='
204    n = len(name)
205    for p in plist:
206        if p[:n].lower() == name:
207            return p[n:]
208    return ''
209
210
211# Part 4: test program.
212
213def test():
214    import sys
215    caps = getcaps()
216    if not sys.argv[1:]:
217        show(caps)
218        return
219    for i in range(1, len(sys.argv), 2):
220        args = sys.argv[i:i+2]
221        if len(args) < 2:
222            print "usage: mailcap [MIMEtype file] ..."
223            return
224        MIMEtype = args[0]
225        file = args[1]
226        command, e = findmatch(caps, MIMEtype, 'view', file)
227        if not command:
228            print "No viewer found for", type
229        else:
230            print "Executing:", command
231            sts = os.system(command)
232            if sts:
233                print "Exit status:", sts
234
235def show(caps):
236    print "Mailcap files:"
237    for fn in listmailcapfiles(): print "\t" + fn
238    print
239    if not caps: caps = getcaps()
240    print "Mailcap entries:"
241    print
242    ckeys = caps.keys()
243    ckeys.sort()
244    for type in ckeys:
245        print type
246        entries = caps[type]
247        for e in entries:
248            keys = e.keys()
249            keys.sort()
250            for k in keys:
251                print "  %-15s" % k, e[k]
252            print
253
254if __name__ == '__main__':
255    test()