1 |
#! /usr/bin/env python
|
2 |
|
3 |
# Released to the public domain, by Tim Peters, 03 October 2000.
|
4 |
|
5 |
"""reindent [-d][-r][-v] [ path ... ]
|
6 |
|
7 |
-d (--dryrun) Dry run. Analyze, but don't make any changes to, files.
|
8 |
-r (--recurse) Recurse. Search for all .py files in subdirectories too.
|
9 |
-n (--nobackup) No backup. Does not make a ".bak" file before reindenting.
|
10 |
-v (--verbose) Verbose. Print informative msgs; else no output.
|
11 |
-h (--help) Help. Print this usage information and exit.
|
12 |
|
13 |
Change Python (.py) files to use 4-space indents and no hard tab characters.
|
14 |
Also trim excess spaces and tabs from ends of lines, and remove empty lines
|
15 |
at the end of files. Also ensure the last line ends with a newline.
|
16 |
|
17 |
If no paths are given on the command line, reindent operates as a filter,
|
18 |
reading a single source file from standard input and writing the transformed
|
19 |
source to standard output. In this case, the -d, -r and -v flags are
|
20 |
ignored.
|
21 |
|
22 |
You can pass one or more file and/or directory paths. When a directory
|
23 |
path, all .py files within the directory will be examined, and, if the -r
|
24 |
option is given, likewise recursively for subdirectories.
|
25 |
|
26 |
If output is not to standard output, reindent overwrites files in place,
|
27 |
renaming the originals with a .bak extension. If it finds nothing to
|
28 |
change, the file is left alone. If reindent does change a file, the changed
|
29 |
file is a fixed-point for future runs (i.e., running reindent on the
|
30 |
resulting .py file won't change it again).
|
31 |
|
32 |
The hard part of reindenting is figuring out what to do with comment
|
33 |
lines. So long as the input files get a clean bill of health from
|
34 |
tabnanny.py, reindent should do a good job.
|
35 |
|
36 |
The backup file is a copy of the one that is being reindented. The ".bak"
|
37 |
file is generated with shutil.copy(), but some corner cases regarding
|
38 |
user/group and permissions could leave the backup file more readable that
|
39 |
you'd prefer. You can always use the --nobackup option to prevent this.
|
40 |
"""
|
41 |
|
42 |
__version__ = "1"
|
43 |
|
44 |
import tokenize
|
45 |
import os, shutil
|
46 |
import sys
|
47 |
|
48 |
verbose = 0
|
49 |
recurse = 0
|
50 |
dryrun = 0
|
51 |
makebackup = True
|
52 |
|
53 |
def usage(msg=None):
|
54 |
if msg is not None:
|
55 |
print >> sys.stderr, msg
|
56 |
print >> sys.stderr, __doc__
|
57 |
|
58 |
def errprint(*args):
|
59 |
sep = ""
|
60 |
for arg in args:
|
61 |
sys.stderr.write(sep + str(arg))
|
62 |
sep = " "
|
63 |
sys.stderr.write("\n")
|
64 |
|
65 |
def main():
|
66 |
import getopt
|
67 |
global verbose, recurse, dryrun, makebackup
|
68 |
try:
|
69 |
opts, args = getopt.getopt(sys.argv[1:], "drnvh",
|
70 |
["dryrun", "recurse", "nobackup", "verbose", "help"])
|
71 |
except getopt.error, msg:
|
72 |
usage(msg)
|
73 |
return
|
74 |
for o, a in opts:
|
75 |
if o in ('-d', '--dryrun'):
|
76 |
dryrun += 1
|
77 |
elif o in ('-r', '--recurse'):
|
78 |
recurse += 1
|
79 |
elif o in ('-n', '--nobackup'):
|
80 |
makebackup = False
|
81 |
elif o in ('-v', '--verbose'):
|
82 |
verbose += 1
|
83 |
elif o in ('-h', '--help'):
|
84 |
usage()
|
85 |
return
|
86 |
if not args:
|
87 |
r = Reindenter(sys.stdin)
|
88 |
r.run()
|
89 |
r.write(sys.stdout)
|
90 |
return
|
91 |
for arg in args:
|
92 |
check(arg)
|
93 |
|
94 |
def check(file):
|
95 |
if os.path.isdir(file) and not os.path.islink(file):
|
96 |
if verbose:
|
97 |
print "listing directory", file
|
98 |
names = os.listdir(file)
|
99 |
for name in names:
|
100 |
fullname = os.path.join(file, name)
|
101 |
if ((recurse and os.path.isdir(fullname) and
|
102 |
not os.path.islink(fullname) and
|
103 |
not os.path.split(fullname)[1].startswith("."))
|
104 |
or name.lower().endswith(".py")):
|
105 |
check(fullname)
|
106 |
return
|
107 |
|
108 |
if verbose:
|
109 |
print "checking", file, "...",
|
110 |
try:
|
111 |
f = open(file)
|
112 |
except IOError, msg:
|
113 |
errprint("%s: I/O Error: %s" % (file, str(msg)))
|
114 |
return
|
115 |
|
116 |
r = Reindenter(f)
|
117 |
f.close()
|
118 |
if r.run():
|
119 |
if verbose:
|
120 |
print "changed."
|
121 |
if dryrun:
|
122 |
print "But this is a dry run, so leaving it alone."
|
123 |
if not dryrun:
|
124 |
bak = file + ".bak"
|
125 |
if makebackup:
|
126 |
shutil.copyfile(file, bak)
|
127 |
if verbose:
|
128 |
print "backed up", file, "to", bak
|
129 |
f = open(file, "w")
|
130 |
r.write(f)
|
131 |
f.close()
|
132 |
if verbose:
|
133 |
print "wrote new", file
|
134 |
return True
|
135 |
else:
|
136 |
if verbose:
|
137 |
print "unchanged."
|
138 |
return False
|
139 |
|
140 |
def _rstrip(line, JUNK='\n \t'):
|
141 |
"""Return line stripped of trailing spaces, tabs, newlines.
|
142 |
|
143 |
Note that line.rstrip() instead also strips sundry control characters,
|
144 |
but at least one known Emacs user expects to keep junk like that, not
|
145 |
mentioning Barry by name or anything <wink>.
|
146 |
"""
|
147 |
|
148 |
i = len(line)
|
149 |
while i > 0 and line[i-1] in JUNK:
|
150 |
i -= 1
|
151 |
return line[:i]
|
152 |
|
153 |
class Reindenter:
|
154 |
|
155 |
def __init__(self, f):
|
156 |
self.find_stmt = 1 # next token begins a fresh stmt?
|
157 |
self.level = 0 # current indent level
|
158 |
|
159 |
# Raw file lines.
|
160 |
self.raw = f.readlines()
|
161 |
|
162 |
# File lines, rstripped & tab-expanded. Dummy at start is so
|
163 |
# that we can use tokenize's 1-based line numbering easily.
|
164 |
# Note that a line is all-blank iff it's "\n".
|
165 |
self.lines = [_rstrip(line).expandtabs() + "\n"
|
166 |
for line in self.raw]
|
167 |
self.lines.insert(0, None)
|
168 |
self.index = 1 # index into self.lines of next line
|
169 |
|
170 |
# List of (lineno, indentlevel) pairs, one for each stmt and
|
171 |
# comment line. indentlevel is -1 for comment lines, as a
|
172 |
# signal that tokenize doesn't know what to do about them;
|
173 |
|