3 # Released to the public domain, by Tim Peters, 03 October 2000.
5 """reindent [-d][-r][-v] path ...
7 -d Dry run. Analyze, but don't make any changes to, files.
8 -r Recurse. Search for all .py files in subdirectories too.
9 -v Verbose. Print informative msgs; else no output.
11 Change Python (.py) files to use 4-space indents and no hard tab characters.
12 Also trim excess whitespace from ends of lines, and empty lines at the ends
13 of files. Ensure the last line ends with a newline.
15 Pass one or more file and/or directory paths. When a directory path, all
16 .py files within the directory will be examined, and, if the -r option is
17 given, likewise recursively for subdirectories.
19 Overwrites files in place, renaming the originals with a .bak extension.
20 If reindent finds nothing to change, the file is left alone. If reindent
21 does change a file, the changed file is a fixed-point for reindent (i.e.,
22 running reindent on the resulting .py file won't change it again).
24 The hard part of reindenting is figuring out what to do with comment
25 lines. So long as the input files get a clean bill of health from
26 tabnanny.py, reindent should do a good job.
42 sys
.stderr
.write(sep
+ str(arg
))
44 sys
.stderr
.write("\n")
48 global verbose
, recurse
, dryrun
50 opts
, args
= getopt
.getopt(sys
.argv
[1:], "drv")
51 except getopt
.error
, msg
:
62 errprint("Usage:", __doc__
)
68 if os
.path
.isdir(file) and not os
.path
.islink(file):
70 print "listing directory", file
71 names
= os
.listdir(file)
73 fullname
= os
.path
.join(file, name
)
74 if ((recurse
and os
.path
.isdir(fullname
) and
75 not os
.path
.islink(fullname
))
76 or name
.lower().endswith(".py")):
81 print "checking", file, "...",
85 errprint("%s: I/O Error: %s" % (file, str(msg
)))
94 print "But this is a dry run, so leaving it alone."
97 if os
.path
.exists(bak
):
101 print "renamed", file, "to", bak
106 print "wrote new", file
113 def __init__(self
, f
):
114 self
.find_stmt
= 1 # next token begins a fresh stmt?
115 self
.level
= 0 # current indent level
118 self
.raw
= f
.readlines()
120 # File lines, rstripped & tab-expanded. Dummy at start is so
121 # that we can use tokenize's 1-based line numbering easily.
122 # Note that a line is all-blank iff it's "\n".
123 self
.lines
= [line
.rstrip().expandtabs() + "\n"
124 for line
in self
.raw
]
125 self
.lines
.insert(0, None)
126 self
.index
= 1 # index into self.lines of next line
128 # List of (lineno, indentlevel) pairs, one for each stmt and
129 # comment line. indentlevel is -1 for comment lines, as a
130 # signal that tokenize doesn't know what to do about them;
131 # indeed, they're our headache!
135 tokenize
.tokenize(self
.getline
, self
.tokeneater
)
136 # Remove trailing empty lines.
138 while lines
and lines
[-1] == "\n":
142 stats
.append((len(lines
), 0))
143 # Map count of leading spaces to # we want.
145 # Program after transformation.
146 after
= self
.after
= []
147 for i
in range(len(stats
)-1):
148 thisstmt
, thislevel
= stats
[i
]
149 nextstmt
= stats
[i
+1][0]
150 have
= getlspace(lines
[thisstmt
])
155 # An indented comment line. If we saw the same
156 # indentation before, reuse what it most recently
158 want
= have2want
.get(have
, -1)
160 # Then it probably belongs to the next real stmt.
161 for j
in xrange(i
+1, len(stats
)-1):
162 jline
, jlevel
= stats
[j
]
164 if have
== getlspace(lines
[jline
]):
167 if want
< 0: # Maybe it's a hanging
168 # comment like this one,
169 # in which case we should shift it like its base
171 for j
in xrange(i
-1, -1, -1):
172 jline
, jlevel
= stats
[j
]
174 want
= have
+ getlspace(after
[jline
-1]) - \
175 getlspace(lines
[jline
])
178 # Still no luck -- leave it alone.
183 have2want
[have
] = want
185 if diff
== 0 or have
== 0:
186 after
.extend(lines
[thisstmt
:nextstmt
])
188 for line
in lines
[thisstmt
:nextstmt
]:
193 after
.append(" " * diff
+ line
)
195 remove
= min(getlspace(line
), -diff
)
196 after
.append(line
[remove
:])
197 return self
.raw
!= self
.after
200 f
.writelines(self
.after
)
202 # Line-getter for tokenize.
204 if self
.index
>= len(self
.lines
):
207 line
= self
.lines
[self
.index
]
211 # Line-eater for tokenize.
212 def tokeneater(self
, type, token
, (sline
, scol
), end
, line
,
213 INDENT
=tokenize
.INDENT
,
214 DEDENT
=tokenize
.DEDENT
,
215 NEWLINE
=tokenize
.NEWLINE
,
216 COMMENT
=tokenize
.COMMENT
,
220 # A program statement, or ENDMARKER, will eventually follow,
221 # after some (possibly empty) run of tokens of the form
222 # (NL | COMMENT)* (INDENT | DEDENT+)?
233 elif type == COMMENT
:
235 self
.stats
.append((sline
, -1))
236 # but we're still looking for a new stmt, so leave
243 # This is the first "real token" following a NEWLINE, so it
244 # must be the first token of the next program statement, or an
247 if line
: # not endmarker
248 self
.stats
.append((sline
, self
.level
))
250 # Count number of leading blanks.
253 while i
< n
and line
[i
] == " ":
257 if __name__
== '__main__':