ยปCore Development>Code coverage>Tools/scripts/cleanfuture.py

Python code coverage for Tools/scripts/cleanfuture.py

#countcontent
1n/a#! /usr/bin/env python3
2n/a
3n/a"""cleanfuture [-d][-r][-v] path ...
4n/a
5n/a-d Dry run. Analyze, but don't make any changes to, files.
6n/a-r Recurse. Search for all .py files in subdirectories too.
7n/a-v Verbose. Print informative msgs.
8n/a
9n/aSearch Python (.py) files for future statements, and remove the features
10n/afrom such statements that are already mandatory in the version of Python
11n/ayou're using.
12n/a
13n/aPass one or more file and/or directory paths. When a directory path, all
14n/a.py files within the directory will be examined, and, if the -r option is
15n/agiven, likewise recursively for subdirectories.
16n/a
17n/aOverwrites files in place, renaming the originals with a .bak extension. If
18n/acleanfuture finds nothing to change, the file is left alone. If cleanfuture
19n/adoes change a file, the changed file is a fixed-point (i.e., running
20n/acleanfuture on the resulting .py file won't change it again, at least not
21n/auntil you try it again with a later Python release).
22n/a
23n/aLimitations: You can do these things, but this tool won't help you then:
24n/a
25n/a+ A future statement cannot be mixed with any other statement on the same
26n/a physical line (separated by semicolon).
27n/a
28n/a+ A future statement cannot contain an "as" clause.
29n/a
30n/aExample: Assuming you're using Python 2.2, if a file containing
31n/a
32n/afrom __future__ import nested_scopes, generators
33n/a
34n/ais analyzed by cleanfuture, the line is rewritten to
35n/a
36n/afrom __future__ import generators
37n/a
38n/abecause nested_scopes is no longer optional in 2.2 but generators is.
39n/a"""
40n/a
41n/aimport __future__
42n/aimport tokenize
43n/aimport os
44n/aimport sys
45n/a
46n/adryrun = 0
47n/arecurse = 0
48n/averbose = 0
49n/a
50n/adef errprint(*args):
51n/a strings = map(str, args)
52n/a msg = ' '.join(strings)
53n/a if msg[-1:] != '\n':
54n/a msg += '\n'
55n/a sys.stderr.write(msg)
56n/a
57n/adef main():
58n/a import getopt
59n/a global verbose, recurse, dryrun
60n/a try:
61n/a opts, args = getopt.getopt(sys.argv[1:], "drv")
62n/a except getopt.error as msg:
63n/a errprint(msg)
64n/a return
65n/a for o, a in opts:
66n/a if o == '-d':
67n/a dryrun += 1
68n/a elif o == '-r':
69n/a recurse += 1
70n/a elif o == '-v':
71n/a verbose += 1
72n/a if not args:
73n/a errprint("Usage:", __doc__)
74n/a return
75n/a for arg in args:
76n/a check(arg)
77n/a
78n/adef check(file):
79n/a if os.path.isdir(file) and not os.path.islink(file):
80n/a if verbose:
81n/a print("listing directory", file)
82n/a names = os.listdir(file)
83n/a for name in names:
84n/a fullname = os.path.join(file, name)
85n/a if ((recurse and os.path.isdir(fullname) and
86n/a not os.path.islink(fullname))
87n/a or name.lower().endswith(".py")):
88n/a check(fullname)
89n/a return
90n/a
91n/a if verbose:
92n/a print("checking", file, "...", end=' ')
93n/a try:
94n/a f = open(file)
95n/a except IOError as msg:
96n/a errprint("%r: I/O Error: %s" % (file, str(msg)))
97n/a return
98n/a
99n/a ff = FutureFinder(f, file)
100n/a changed = ff.run()
101n/a if changed:
102n/a ff.gettherest()
103n/a f.close()
104n/a if changed:
105n/a if verbose:
106n/a print("changed.")
107n/a if dryrun:
108n/a print("But this is a dry run, so leaving it alone.")
109n/a for s, e, line in changed:
110n/a print("%r lines %d-%d" % (file, s+1, e+1))
111n/a for i in range(s, e+1):
112n/a print(ff.lines[i], end=' ')
113n/a if line is None:
114n/a print("-- deleted")
115n/a else:
116n/a print("-- change to:")
117n/a print(line, end=' ')
118n/a if not dryrun:
119n/a bak = file + ".bak"
120n/a if os.path.exists(bak):
121n/a os.remove(bak)
122n/a os.rename(file, bak)
123n/a if verbose:
124n/a print("renamed", file, "to", bak)
125n/a g = open(file, "w")
126n/a ff.write(g)
127n/a g.close()
128n/a if verbose:
129n/a print("wrote new", file)
130n/a else:
131n/a if verbose:
132n/a print("unchanged.")
133n/a
134n/aclass FutureFinder:
135n/a
136n/a def __init__(self, f, fname):
137n/a self.f = f
138n/a self.fname = fname
139n/a self.ateof = 0
140n/a self.lines = [] # raw file lines
141n/a
142n/a # List of (start_index, end_index, new_line) triples.
143n/a self.changed = []
144n/a
145n/a # Line-getter for tokenize.
146n/a def getline(self):
147n/a if self.ateof:
148n/a return ""
149n/a line = self.f.readline()
150n/a if line == "":
151n/a self.ateof = 1
152n/a else:
153n/a self.lines.append(line)
154n/a return line
155n/a
156n/a def run(self):
157n/a STRING = tokenize.STRING
158n/a NL = tokenize.NL
159n/a NEWLINE = tokenize.NEWLINE
160n/a COMMENT = tokenize.COMMENT
161n/a NAME = tokenize.NAME
162n/a OP = tokenize.OP
163n/a
164n/a changed = self.changed
165n/a get = tokenize.generate_tokens(self.getline).__next__
166n/a type, token, (srow, scol), (erow, ecol), line = get()
167n/a
168n/a # Chew up initial comments and blank lines (if any).
169n/a while type in (COMMENT, NL, NEWLINE):
170n/a type, token, (srow, scol), (erow, ecol), line = get()
171n/a
172n/a # Chew up docstring (if any -- and it may be implicitly catenated!).
173n/a while type is STRING:
174n/a type, token, (srow, scol), (erow, ecol), line = get()
175n/a
176n/a # Analyze the future stmts.
177n/a while 1:
178n/a # Chew up comments and blank lines (if any).
179n/a while type in (COMMENT, NL, NEWLINE):
180n/a type, token, (srow, scol), (erow, ecol), line = get()
181n/a
182n/a if not (type is NAME and token == "from"):
183n/a break
184n/a startline = srow - 1 # tokenize is one-based
185n/a type, token, (srow, scol), (erow, ecol), line = get()
186n/a
187n/a if not (type is NAME and token == "__future__"):
188n/a break
189n/a type, token, (srow, scol), (erow, ecol), line = get()
190n/a
191n/a if not (type is NAME and token == "import"):
192n/a break
193n/a type, token, (srow, scol), (erow, ecol), line = get()
194n/a
195n/a # Get the list of features.
196n/a features = []
197n/a while type is NAME:
198n/a features.append(token)
199n/a type, token, (srow, scol), (erow, ecol), line = get()
200n/a
201n/a if not (type is OP and token == ','):
202n/a break
203n/a type, token, (srow, scol), (erow, ecol), line = get()
204n/a
205n/a # A trailing comment?
206n/a comment = None
207n/a if type is COMMENT:
208n/a comment = token
209n/a type, token, (srow, scol), (erow, ecol), line = get()
210n/a
211n/a if type is not NEWLINE:
212n/a errprint("Skipping file %r; can't parse line %d:\n%s" %
213n/a (self.fname, srow, line))
214n/a return []
215n/a
216n/a endline = srow - 1
217n/a
218n/a # Check for obsolete features.
219n/a okfeatures = []
220n/a for f in features:
221n/a object = getattr(__future__, f, None)
222n/a if object is None:
223n/a # A feature we don't know about yet -- leave it in.
224n/a # They'll get a compile-time error when they compile
225n/a # this program, but that's not our job to sort out.
226n/a okfeatures.append(f)
227n/a else:
228n/a released = object.getMandatoryRelease()
229n/a if released is None or released <= sys.version_info:
230n/a # Withdrawn or obsolete.
231n/a pass
232n/a else:
233n/a okfeatures.append(f)
234n/a
235n/a # Rewrite the line if at least one future-feature is obsolete.
236n/a if len(okfeatures) < len(features):
237n/a if len(okfeatures) == 0:
238n/a line = None
239n/a else:
240n/a line = "from __future__ import "
241n/a line += ', '.join(okfeatures)
242n/a if comment is not None:
243n/a line += ' ' + comment
244n/a line += '\n'
245n/a changed.append((startline, endline, line))
246n/a
247n/a # Loop back for more future statements.
248n/a
249n/a return changed
250n/a
251n/a def gettherest(self):
252n/a if self.ateof:
253n/a self.therest = ''
254n/a else:
255n/a self.therest = self.f.read()
256n/a
257n/a def write(self, f):
258n/a changed = self.changed
259n/a assert changed
260n/a # Prevent calling this again.
261n/a self.changed = []
262n/a # Apply changes in reverse order.
263n/a changed.reverse()
264n/a for s, e, line in changed:
265n/a if line is None:
266n/a # pure deletion
267n/a del self.lines[s:e+1]
268n/a else:
269n/a self.lines[s:e+1] = [line]
270n/a f.writelines(self.lines)
271n/a # Copy over the remainder of the file.
272n/a if self.therest:
273n/a f.write(self.therest)
274n/a
275n/aif __name__ == '__main__':
276n/a main()