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

Python code coverage for Tools/scripts/reindent.py

#countcontent
1n/a#! /usr/bin/env python3
2n/a
3n/a# Released to the public domain, by Tim Peters, 03 October 2000.
4n/a
5n/a"""reindent [-d][-r][-v] [ path ... ]
6n/a
7n/a-d (--dryrun) Dry run. Analyze, but don't make any changes to, files.
8n/a-r (--recurse) Recurse. Search for all .py files in subdirectories too.
9n/a-n (--nobackup) No backup. Does not make a ".bak" file before reindenting.
10n/a-v (--verbose) Verbose. Print informative msgs; else no output.
11n/a (--newline) Newline. Specify the newline character to use (CRLF, LF).
12n/a Default is the same as the original file.
13n/a-h (--help) Help. Print this usage information and exit.
14n/a
15n/aChange Python (.py) files to use 4-space indents and no hard tab characters.
16n/aAlso trim excess spaces and tabs from ends of lines, and remove empty lines
17n/aat the end of files. Also ensure the last line ends with a newline.
18n/a
19n/aIf no paths are given on the command line, reindent operates as a filter,
20n/areading a single source file from standard input and writing the transformed
21n/asource to standard output. In this case, the -d, -r and -v flags are
22n/aignored.
23n/a
24n/aYou can pass one or more file and/or directory paths. When a directory
25n/apath, all .py files within the directory will be examined, and, if the -r
26n/aoption is given, likewise recursively for subdirectories.
27n/a
28n/aIf output is not to standard output, reindent overwrites files in place,
29n/arenaming the originals with a .bak extension. If it finds nothing to
30n/achange, the file is left alone. If reindent does change a file, the changed
31n/afile is a fixed-point for future runs (i.e., running reindent on the
32n/aresulting .py file won't change it again).
33n/a
34n/aThe hard part of reindenting is figuring out what to do with comment
35n/alines. So long as the input files get a clean bill of health from
36n/atabnanny.py, reindent should do a good job.
37n/a
38n/aThe backup file is a copy of the one that is being reindented. The ".bak"
39n/afile is generated with shutil.copy(), but some corner cases regarding
40n/auser/group and permissions could leave the backup file more readable than
41n/ayou'd prefer. You can always use the --nobackup option to prevent this.
42n/a"""
43n/a
44n/a__version__ = "1"
45n/a
46n/aimport tokenize
47n/aimport os
48n/aimport shutil
49n/aimport sys
50n/a
51n/averbose = False
52n/arecurse = False
53n/adryrun = False
54n/amakebackup = True
55n/a# A specified newline to be used in the output (set by --newline option)
56n/aspec_newline = None
57n/a
58n/a
59n/adef usage(msg=None):
60n/a if msg is None:
61n/a msg = __doc__
62n/a print(msg, file=sys.stderr)
63n/a
64n/a
65n/adef errprint(*args):
66n/a sys.stderr.write(" ".join(str(arg) for arg in args))
67n/a sys.stderr.write("\n")
68n/a
69n/adef main():
70n/a import getopt
71n/a global verbose, recurse, dryrun, makebackup, spec_newline
72n/a try:
73n/a opts, args = getopt.getopt(sys.argv[1:], "drnvh",
74n/a ["dryrun", "recurse", "nobackup", "verbose", "newline=", "help"])
75n/a except getopt.error as msg:
76n/a usage(msg)
77n/a return
78n/a for o, a in opts:
79n/a if o in ('-d', '--dryrun'):
80n/a dryrun = True
81n/a elif o in ('-r', '--recurse'):
82n/a recurse = True
83n/a elif o in ('-n', '--nobackup'):
84n/a makebackup = False
85n/a elif o in ('-v', '--verbose'):
86n/a verbose = True
87n/a elif o in ('--newline',):
88n/a if not a.upper() in ('CRLF', 'LF'):
89n/a usage()
90n/a return
91n/a spec_newline = dict(CRLF='\r\n', LF='\n')[a.upper()]
92n/a elif o in ('-h', '--help'):
93n/a usage()
94n/a return
95n/a if not args:
96n/a r = Reindenter(sys.stdin)
97n/a r.run()
98n/a r.write(sys.stdout)
99n/a return
100n/a for arg in args:
101n/a check(arg)
102n/a
103n/a
104n/adef check(file):
105n/a if os.path.isdir(file) and not os.path.islink(file):
106n/a if verbose:
107n/a print("listing directory", file)
108n/a names = os.listdir(file)
109n/a for name in names:
110n/a fullname = os.path.join(file, name)
111n/a if ((recurse and os.path.isdir(fullname) and
112n/a not os.path.islink(fullname) and
113n/a not os.path.split(fullname)[1].startswith("."))
114n/a or name.lower().endswith(".py")):
115n/a check(fullname)
116n/a return
117n/a
118n/a if verbose:
119n/a print("checking", file, "...", end=' ')
120n/a with open(file, 'rb') as f:
121n/a encoding, _ = tokenize.detect_encoding(f.readline)
122n/a try:
123n/a with open(file, encoding=encoding) as f:
124n/a r = Reindenter(f)
125n/a except IOError as msg:
126n/a errprint("%s: I/O Error: %s" % (file, str(msg)))
127n/a return
128n/a
129n/a newline = spec_newline if spec_newline else r.newlines
130n/a if isinstance(newline, tuple):
131n/a errprint("%s: mixed newlines detected; cannot continue without --newline" % file)
132n/a return
133n/a
134n/a if r.run():
135n/a if verbose:
136n/a print("changed.")
137n/a if dryrun:
138n/a print("But this is a dry run, so leaving it alone.")
139n/a if not dryrun:
140n/a bak = file + ".bak"
141n/a if makebackup:
142n/a shutil.copyfile(file, bak)
143n/a if verbose:
144n/a print("backed up", file, "to", bak)
145n/a with open(file, "w", encoding=encoding, newline=newline) as f:
146n/a r.write(f)
147n/a if verbose:
148n/a print("wrote new", file)
149n/a return True
150n/a else:
151n/a if verbose:
152n/a print("unchanged.")
153n/a return False
154n/a
155n/a
156n/adef _rstrip(line, JUNK='\n \t'):
157n/a """Return line stripped of trailing spaces, tabs, newlines.
158n/a
159n/a Note that line.rstrip() instead also strips sundry control characters,
160n/a but at least one known Emacs user expects to keep junk like that, not
161n/a mentioning Barry by name or anything <wink>.
162n/a """
163n/a
164n/a i = len(line)
165n/a while i > 0 and line[i - 1] in JUNK:
166n/a i -= 1
167n/a return line[:i]
168n/a
169n/a
170n/aclass Reindenter:
171n/a
172n/a def __init__(self, f):
173n/a self.find_stmt = 1 # next token begins a fresh stmt?
174n/a self.level = 0 # current indent level
175n/a
176n/a # Raw file lines.
177n/a self.raw = f.readlines()
178n/a
179n/a # File lines, rstripped & tab-expanded. Dummy at start is so
180n/a # that we can use tokenize's 1-based line numbering easily.
181n/a # Note that a line is all-blank iff it's "\n".
182n/a self.lines = [_rstrip(line).expandtabs() + "\n"
183n/a for line in self.raw]
184n/a self.lines.insert(0, None)
185n/a self.index = 1 # index into self.lines of next line
186n/a
187n/a # List of (lineno, indentlevel) pairs, one for each stmt and
188n/a # comment line. indentlevel is -1 for comment lines, as a
189n/a # signal that tokenize doesn't know what to do about them;
190n/a # indeed, they're our headache!
191n/a self.stats = []
192n/a
193n/a # Save the newlines found in the file so they can be used to
194n/a # create output without mutating the newlines.
195n/a self.newlines = f.newlines
196n/a
197n/a def run(self):
198n/a tokens = tokenize.generate_tokens(self.getline)
199n/a for _token in tokens:
200n/a self.tokeneater(*_token)
201n/a # Remove trailing empty lines.
202n/a lines = self.lines
203n/a while lines and lines[-1] == "\n":
204n/a lines.pop()
205n/a # Sentinel.
206n/a stats = self.stats
207n/a stats.append((len(lines), 0))
208n/a # Map count of leading spaces to # we want.
209n/a have2want = {}
210n/a # Program after transformation.
211n/a after = self.after = []
212n/a # Copy over initial empty lines -- there's nothing to do until
213n/a # we see a line with *something* on it.
214n/a i = stats[0][0]
215n/a after.extend(lines[1:i])
216n/a for i in range(len(stats) - 1):
217n/a thisstmt, thislevel = stats[i]
218n/a nextstmt = stats[i + 1][0]
219n/a have = getlspace(lines[thisstmt])
220n/a want = thislevel * 4
221n/a if want < 0:
222n/a # A comment line.
223n/a if have:
224n/a # An indented comment line. If we saw the same
225n/a # indentation before, reuse what it most recently
226n/a # mapped to.
227n/a want = have2want.get(have, -1)
228n/a if want < 0:
229n/a # Then it probably belongs to the next real stmt.
230n/a for j in range(i + 1, len(stats) - 1):
231n/a jline, jlevel = stats[j]
232n/a if jlevel >= 0:
233n/a if have == getlspace(lines[jline]):
234n/a want = jlevel * 4
235n/a break
236n/a if want < 0: # Maybe it's a hanging
237n/a # comment like this one,
238n/a # in which case we should shift it like its base
239n/a # line got shifted.
240n/a for j in range(i - 1, -1, -1):
241n/a jline, jlevel = stats[j]
242n/a if jlevel >= 0:
243n/a want = have + (getlspace(after[jline - 1]) -
244n/a getlspace(lines[jline]))
245n/a break
246n/a if want < 0:
247n/a # Still no luck -- leave it alone.
248n/a want = have
249n/a else:
250n/a want = 0
251n/a assert want >= 0
252n/a have2want[have] = want
253n/a diff = want - have
254n/a if diff == 0 or have == 0:
255n/a after.extend(lines[thisstmt:nextstmt])
256n/a else:
257n/a for line in lines[thisstmt:nextstmt]:
258n/a if diff > 0:
259n/a if line == "\n":
260n/a after.append(line)
261n/a else:
262n/a after.append(" " * diff + line)
263n/a else:
264n/a remove = min(getlspace(line), -diff)
265n/a after.append(line[remove:])
266n/a return self.raw != self.after
267n/a
268n/a def write(self, f):
269n/a f.writelines(self.after)
270n/a
271n/a # Line-getter for tokenize.
272n/a def getline(self):
273n/a if self.index >= len(self.lines):
274n/a line = ""
275n/a else:
276n/a line = self.lines[self.index]
277n/a self.index += 1
278n/a return line
279n/a
280n/a # Line-eater for tokenize.
281n/a def tokeneater(self, type, token, slinecol, end, line,
282n/a INDENT=tokenize.INDENT,
283n/a DEDENT=tokenize.DEDENT,
284n/a NEWLINE=tokenize.NEWLINE,
285n/a COMMENT=tokenize.COMMENT,
286n/a NL=tokenize.NL):
287n/a
288n/a if type == NEWLINE:
289n/a # A program statement, or ENDMARKER, will eventually follow,
290n/a # after some (possibly empty) run of tokens of the form
291n/a # (NL | COMMENT)* (INDENT | DEDENT+)?
292n/a self.find_stmt = 1
293n/a
294n/a elif type == INDENT:
295n/a self.find_stmt = 1
296n/a self.level += 1
297n/a
298n/a elif type == DEDENT:
299n/a self.find_stmt = 1
300n/a self.level -= 1
301n/a
302n/a elif type == COMMENT:
303n/a if self.find_stmt:
304n/a self.stats.append((slinecol[0], -1))
305n/a # but we're still looking for a new stmt, so leave
306n/a # find_stmt alone
307n/a
308n/a elif type == NL:
309n/a pass
310n/a
311n/a elif self.find_stmt:
312n/a # This is the first "real token" following a NEWLINE, so it
313n/a # must be the first token of the next program statement, or an
314n/a # ENDMARKER.
315n/a self.find_stmt = 0
316n/a if line: # not endmarker
317n/a self.stats.append((slinecol[0], self.level))
318n/a
319n/a
320n/a# Count number of leading blanks.
321n/adef getlspace(line):
322n/a i, n = 0, len(line)
323n/a while i < n and line[i] == " ":
324n/a i += 1
325n/a return i
326n/a
327n/a
328n/aif __name__ == '__main__':
329n/a main()