ยปCore Development>Code coverage>Lib/mailcap.py

Python code coverage for Lib/mailcap.py

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