blob: 4fa5de901f4babac95dee4ebe2180b19f88d3240 [file] [log] [blame]
Guido van Rossum8988fb21995-09-30 16:52:24 +00001# Mailcap file handling. See RFC 1524.
2
3import os
4import string
5import tempfile
6
7
8# Part 1: top-level interface.
9
10def getcaps():
11 caps = {}
12 for mailcap in listmailcapfiles():
13 try:
14 fp = open(mailcap, 'r')
15 except:
16 continue
17 morecaps = readmailcapfile(fp)
18 fp.close()
19 for key in morecaps.keys():
20 if not caps.has_key(key):
21 caps[key] = morecaps[key]
22 else:
23 caps[key] = caps[key] + morecaps[key]
24 return caps
25
26def listmailcapfiles():
27 # XXX Actually, this is Unix-specific
28 if os.environ.has_key('MAILCAPS'):
29 str = os.environ['MAILCAPS']
30 mailcaps = string.splitfields(str, ':')
31 else:
32 if os.environ.has_key('HOME'):
33 home = os.environ['HOME']
34 else:
35 # Don't bother with getpwuid()
36 home = '.' # Last resort
37 mailcaps = [home + '/.mailcap', '/etc/mailcap',
38 '/usr/etc/mailcap', '/usr/local/etc/mailcap']
39 return mailcaps
40
41
42# Part 2: the parser.
43
44def readmailcapfile(fp):
45 caps = {}
46 while 1:
47 line = fp.readline()
48 if not line: break
49 # Ignore comments and blank lines
50 if line[0] == '#' or string.strip(line) == '':
51 continue
52 nextline = line
53 # Join continuation lines
54 while nextline[-2:] == '\\\n':
55 nextline = fp.readline()
56 if not nextline: nextline = '\n'
57 line = line[:-2] + nextline
58 # Parse the line
59 key, fields = parseline(line)
60 if not (key and fields):
Guido van Rossumcfd89351996-08-26 16:20:31 +000061 continue
Guido van Rossum8988fb21995-09-30 16:52:24 +000062 # Normalize the key
63 types = string.splitfields(key, '/')
64 for j in range(len(types)):
65 types[j] = string.strip(types[j])
66 key = string.lower(string.joinfields(types, '/'))
67 # Update the database
68 if caps.has_key(key):
69 caps[key].append(fields)
70 else:
71 caps[key] = [fields]
72 return caps
73
74def parseline(line):
75 fields = []
76 i, n = 0, len(line)
77 while i < n:
78 field, i = parsefield(line, i, n)
79 fields.append(field)
80 i = i+1 # Skip semicolon
81 if len(fields) < 2:
82 return None, None
83 key, view, rest = fields[0], fields[1], fields[2:]
84 fields = {'view': view}
85 for field in rest:
86 i = string.find(field, '=')
87 if i < 0:
88 fkey = field
89 fvalue = ""
90 else:
91 fkey = string.strip(field[:i])
92 fvalue = string.strip(field[i+1:])
93 if fields.has_key(fkey):
94 # Ignore it
95 pass
96 else:
97 fields[fkey] = fvalue
98 return key, fields
99
100def parsefield(line, i, n):
101 start = i
102 while i < n:
103 c = line[i]
104 if c == ';':
105 break
106 elif c == '\\':
107 i = i+2
108 else:
109 i = i+1
110 return string.strip(line[start:i]), i
111
112
113# Part 3: using the database.
114
115def findmatch(caps, type, key='view', filename="/dev/null", plist=[]):
116 entries = lookup(caps, type, key)
117 for e in entries:
118 if e.has_key('test'):
119 test = subst(e['test'], filename, plist)
120 if test and os.system(test) != 0:
121 continue
122 command = subst(e[key], type, filename, plist)
123 return command, e
124 return None, None
125
126def lookup(caps, type, key=None):
127 entries = []
128 if caps.has_key(type):
129 entries = entries + caps[type]
130 types = string.splitfields(type, '/')
131 type = types[0] + '/*'
132 if caps.has_key(type):
133 entries = entries + caps[type]
134 if key is not None:
135 entries = filter(lambda e, key=key: e.has_key(key), entries)
136 return entries
137
138def subst(field, type, filename, plist=[]):
139 # XXX Actually, this is Unix-specific
140 res = ''
141 i, n = 0, len(field)
142 while i < n:
143 c = field[i]; i = i+1
144 if c <> '%':
145 if c == '\\':
146 c = field[i:i+1]; i = i+1
147 res = res + c
148 else:
149 c = field[i]; i = i+1
150 if c == '%':
151 res = res + c
152 elif c == 's':
153 res = res + filename
154 elif c == 't':
155 res = res + type
156 elif c == '{':
157 start = i
158 while i < n and field[i] <> '}':
159 i = i+1
160 name = field[start:i]
161 i = i+1
162 res = res + findparam(name, plist)
163 # XXX To do:
164 # %n == number of parts if type is multipart/*
165 # %F == list of alternating type and filename for parts
166 else:
167 res = res + '%' + c
168 return res
169
170def findparam(name, plist):
171 name = string.lower(name) + '='
172 n = len(name)
173 for p in plist:
174 if string.lower(p[:n]) == name:
175 return p[n:]
176 return ''
177
178
179# Part 4: test program.
180
181def test():
182 import sys
183 caps = getcaps()
184 if not sys.argv[1:]:
185 show(caps)
186 return
187 for i in range(1, len(sys.argv), 2):
188 args = sys.argv[i:i+2]
189 if len(args) < 2:
190 print "usage: mailcap [type file] ..."
191 return
192 type = args[0]
193 file = args[1]
194 command, e = findmatch(caps, type, 'view', file)
195 if not command:
196 print "No viewer found for", type
197 else:
198 print "Executing:", command
199 sts = os.system(command)
200 if sts:
201 print "Exit status:", sts
202
203def show(caps):
204 print "Mailcap files:"
205 for fn in listmailcapfiles(): print "\t" + fn
206 print
207 if not caps: caps = getcaps()
208 print "Mailcap entries:"
209 print
210 ckeys = caps.keys()
211 ckeys.sort()
212 for type in ckeys:
213 print type
214 entries = caps[type]
215 for e in entries:
216 keys = e.keys()
217 keys.sort()
218 for k in keys:
219 print " %-15s" % k, e[k]
220 print
221
222if __name__ == '__main__':
223 test()