0001 """Mailcap file handling. See RFC 1524.""" 0002 0003 import os 0004 0005 __all__ = ["getcaps","findmatch"] 0006 0007 # Part 1: top-level interface. 0008 0009 def getcaps(): 0010 """Return a dictionary containing the mailcap database. 0011 0012 The dictionary maps a MIME type (in all lowercase, e.g. 'text/plain') 0013 to a list of dictionaries corresponding to mailcap entries. The list 0014 collects all the entries for that MIME type from all available mailcap 0015 files. Each dictionary contains key-value pairs for that MIME type, 0016 where the viewing command is stored with the key "view". 0017 0018 """ 0019 caps = {} 0020 for mailcap in listmailcapfiles(): 0021 try: 0022 fp = open(mailcap, 'r') 0023 except IOError: 0024 continue 0025 morecaps = readmailcapfile(fp) 0026 fp.close() 0027 for key, value in morecaps.iteritems(): 0028 if not key in caps: 0029 caps[key] = value 0030 else: 0031 caps[key] = caps[key] + value 0032 return caps 0033 0034 def listmailcapfiles(): 0035 """Return a list of all mailcap files found on the system.""" 0036 # XXX Actually, this is Unix-specific 0037 if 'MAILCAPS' in os.environ: 0038 str = os.environ['MAILCAPS'] 0039 mailcaps = str.split(':') 0040 else: 0041 if 'HOME' in os.environ: 0042 home = os.environ['HOME'] 0043 else: 0044 # Don't bother with getpwuid() 0045 home = '.' # Last resort 0046 mailcaps = [home + '/.mailcap', '/etc/mailcap', 0047 '/usr/etc/mailcap', '/usr/local/etc/mailcap'] 0048 return mailcaps 0049 0050 0051 # Part 2: the parser. 0052 0053 def readmailcapfile(fp): 0054 """Read a mailcap file and return a dictionary keyed by MIME type. 0055 0056 Each MIME type is mapped to an entry consisting of a list of 0057 dictionaries; the list will contain more than one such dictionary 0058 if a given MIME type appears more than once in the mailcap file. 0059 Each dictionary contains key-value pairs for that MIME type, where 0060 the viewing command is stored with the key "view". 0061 """ 0062 caps = {} 0063 while 1: 0064 line = fp.readline() 0065 if not line: break 0066 # Ignore comments and blank lines 0067 if line[0] == '#' or line.strip() == '': 0068 continue 0069 nextline = line 0070 # Join continuation lines 0071 while nextline[-2:] == '\\\n': 0072 nextline = fp.readline() 0073 if not nextline: nextline = '\n' 0074 line = line[:-2] + nextline 0075 # Parse the line 0076 key, fields = parseline(line) 0077 if not (key and fields): 0078 continue 0079 # Normalize the key 0080 types = key.split('/') 0081 for j in range(len(types)): 0082 types[j] = types[j].strip() 0083 key = '/'.join(types).lower() 0084 # Update the database 0085 if key in caps: 0086 caps[key].append(fields) 0087 else: 0088 caps[key] = [fields] 0089 return caps 0090 0091 def parseline(line): 0092 """Parse one entry in a mailcap file and return a dictionary. 0093 0094 The viewing command is stored as the value with the key "view", 0095 and the rest of the fields produce key-value pairs in the dict. 0096 """ 0097 fields = [] 0098 i, n = 0, len(line) 0099 while i < n: 0100 field, i = parsefield(line, i, n) 0101 fields.append(field) 0102 i = i+1 # Skip semicolon 0103 if len(fields) < 2: 0104 return None, None 0105 key, view, rest = fields[0], fields[1], fields[2:] 0106 fields = {'view': view} 0107 for field in rest: 0108 i = field.find('=') 0109 if i < 0: 0110 fkey = field 0111 fvalue = "" 0112 else: 0113 fkey = field[:i].strip() 0114 fvalue = field[i+1:].strip() 0115 if fkey in fields: 0116 # Ignore it 0117 pass 0118 else: 0119 fields[fkey] = fvalue 0120 return key, fields 0121 0122 def parsefield(line, i, n): 0123 """Separate one key-value pair in a mailcap entry.""" 0124 start = i 0125 while i < n: 0126 c = line[i] 0127 if c == ';': 0128 break 0129 elif c == '\\': 0130 i = i+2 0131 else: 0132 i = i+1 0133 return line[start:i].strip(), i 0134 0135 0136 # Part 3: using the database. 0137 0138 def findmatch(caps, MIMEtype, key='view', filename="/dev/null", plist=[]): 0139 """Find a match for a mailcap entry. 0140 0141 Return a tuple containing the command line, and the mailcap entry 0142 used; (None, None) if no match is found. This may invoke the 0143 'test' command of several matching entries before deciding which 0144 entry to use. 0145 0146 """ 0147 entries = lookup(caps, MIMEtype, key) 0148 # XXX This code should somehow check for the needsterminal flag. 0149 for e in entries: 0150 if 'test' in e: 0151 test = subst(e['test'], filename, plist) 0152 if test and os.system(test) != 0: 0153 continue 0154 command = subst(e[key], MIMEtype, filename, plist) 0155 return command, e 0156 return None, None 0157 0158 def lookup(caps, MIMEtype, key=None): 0159 entries = [] 0160 if MIMEtype in caps: 0161 entries = entries + caps[MIMEtype] 0162 MIMEtypes = MIMEtype.split('/') 0163 MIMEtype = MIMEtypes[0] + '/*' 0164 if MIMEtype in caps: 0165 entries = entries + caps[MIMEtype] 0166 if key is not None: 0167 entries = filter(lambda e, key=key: key in e, entries) 0168 return entries 0169 0170 def subst(field, MIMEtype, filename, plist=[]): 0171 # XXX Actually, this is Unix-specific 0172 res = '' 0173 i, n = 0, len(field) 0174 while i < n: 0175 c = field[i]; i = i+1 0176 if c != '%': 0177 if c == '\\': 0178 c = field[i:i+1]; i = i+1 0179 res = res + c 0180 else: 0181 c = field[i]; i = i+1 0182 if c == '%': 0183 res = res + c 0184 elif c == 's': 0185 res = res + filename 0186 elif c == 't': 0187 res = res + MIMEtype 0188 elif c == '{': 0189 start = i 0190 while i < n and field[i] != '}': 0191 i = i+1 0192 name = field[start:i] 0193 i = i+1 0194 res = res + findparam(name, plist) 0195 # XXX To do: 0196 # %n == number of parts if type is multipart/* 0197 # %F == list of alternating type and filename for parts 0198 else: 0199 res = res + '%' + c 0200 return res 0201 0202 def findparam(name, plist): 0203 name = name.lower() + '=' 0204 n = len(name) 0205 for p in plist: 0206 if p[:n].lower() == name: 0207 return p[n:] 0208 return '' 0209 0210 0211 # Part 4: test program. 0212 0213 def test(): 0214 import sys 0215 caps = getcaps() 0216 if not sys.argv[1:]: 0217 show(caps) 0218 return 0219 for i in range(1, len(sys.argv), 2): 0220 args = sys.argv[i:i+2] 0221 if len(args) < 2: 0222 print "usage: mailcap [MIMEtype file] ..." 0223 return 0224 MIMEtype = args[0] 0225 file = args[1] 0226 command, e = findmatch(caps, MIMEtype, 'view', file) 0227 if not command: 0228 print "No viewer found for", type 0229 else: 0230 print "Executing:", command 0231 sts = os.system(command) 0232 if sts: 0233 print "Exit status:", sts 0234 0235 def show(caps): 0236 print "Mailcap files:" 0237 for fn in listmailcapfiles(): print "\t" + fn 0238 print 0239 if not caps: caps = getcaps() 0240 print "Mailcap entries:" 0241 print 0242 ckeys = caps.keys() 0243 ckeys.sort() 0244 for type in ckeys: 0245 print type 0246 entries = caps[type] 0247 for e in entries: 0248 keys = e.keys() 0249 keys.sort() 0250 for k in keys: 0251 print " %-15s" % k, e[k] 0252 print 0253 0254 if __name__ == '__main__': 0255 test() 0256
Generated by PyXR 0.9.4