]> git.lizzy.rs Git - plan9front.git/blob - sys/lib/python/mailcap.py
cdproto: add spim
[plan9front.git] / sys / lib / python / mailcap.py
1 """Mailcap file handling.  See RFC 1524."""
2
3 import os
4
5 __all__ = ["getcaps","findmatch"]
6
7 # Part 1: top-level interface.
8
9 def 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
34 def 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
53 def 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
91 def 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
122 def 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
138 def 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
158 def 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
170 def 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
202 def 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
213 def 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
235 def 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
254 if __name__ == '__main__':
255     test()