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