]> git.ipfire.org Git - ipfire-3.x.git/blob - tools/code-beautify
Added new package: dosfstools.
[ipfire-3.x.git] / tools / code-beautify
1 #! /usr/bin/python
2
3 """code-saner [-d][-r][-v] [ path ... ]
4
5 -d (--dryrun) Dry run. Analyze, but don't make any changes to, files.
6 -r (--recurse) Recurse. Search for all .py files in subdirectories too.
7 -b (--backup) Keep backups of files.
8 -v (--verbose) Verbose. Print informative msgs; else no output.
9 -h (--help) Help. Print this usage information and exit.
10 """
11
12 __version__ = "1"
13
14 import tokenize
15 import os
16 import sys
17
18 verbose = 0
19 recurse = 0
20 dryrun = 0
21 backup = 0
22
23 def usage(msg=None):
24 if msg is not None:
25 print >> sys.stderr, msg
26 print >> sys.stderr, __doc__
27
28 def errprint(*args):
29 sep = ""
30 for arg in args:
31 sys.stderr.write(sep + str(arg))
32 sep = " "
33 sys.stderr.write("\n")
34
35 def main():
36 import getopt
37 global verbose, recurse, dryrun, backup
38 try:
39 opts, args = getopt.getopt(sys.argv[1:], "drbvh",
40 ["dryrun", "recurse", "backup" "verbose", "help"])
41 except getopt.error, msg:
42 usage(msg)
43 return
44 for o, a in opts:
45 if o in ('-d', '--dryrun'):
46 dryrun += 1
47 elif o in ('-r', '--recurse'):
48 recurse += 1
49 elif o in ('-b', '--backup'):
50 backup += 1
51 elif o in ('-v', '--verbose'):
52 verbose += 1
53 elif o in ('-h', '--help'):
54 usage()
55 return
56 if not args:
57 r = Reindenter(sys.stdin)
58 r.run()
59 r.write(sys.stdout)
60 return
61 for arg in args:
62 check(arg)
63
64 endings = {
65 'images' : (".png", ".gif", ".jpg", ".jpeg",),
66 'styles' : (".css",),
67 'config' : (".conf", ".cnf", ".cf", ".cfg", ".config",),
68 'python' : (".py",),
69 'script' : (".pl", ".c", ".h", ".sh", ".txt",),
70 }
71
72 def FileObject(file):
73 object = None
74 try:
75 f = open(file)
76 except IOError, msg:
77 errprint("%s: I/O Error: %s" % (file, str(msg)))
78 raise
79
80 # Python
81 for ending in endings['python']:
82 if file.endswith(ending):
83 object = PythonFile(f=f, file=file)
84
85 # Configs
86 for ending in endings['config']:
87 if file.endswith(ending):
88 object = ConfigFile(f=f, file=file)
89
90 # Perl
91 for ending in endings['script']:
92 if file.endswith(ending):
93 object = ScriptFile(f=f, file=file)
94
95 # Styles
96 for ending in endings['styles']:
97 if file.endswith(ending):
98 object = StyleFile(f=f, file=file)
99
100 # Images
101 for ending in endings['images']:
102 if file.endswith(ending):
103 object = ImageFile(f=f, file=file)
104
105 f.close()
106 return object or UnknownFile(f=f, file=file)
107
108 def check(file):
109 if os.path.isdir(file) and not os.path.islink(file):
110 if verbose:
111 print "listing directory", file
112 names = os.listdir(file)
113 for name in names:
114 fullname = os.path.join(file, name)
115 if ((recurse and not os.path.islink(fullname))):
116 check(fullname)
117 return
118
119 if verbose:
120 print "checking", file, "...",
121 try:
122 r = FileObject(file)
123 except IOError:
124 return
125
126 if r.run():
127 if verbose:
128 print "changed."
129 if dryrun:
130 print "But this is a dry run, so leaving it alone."
131 if not dryrun:
132 if backup:
133 bak = file + ".bak"
134 if os.path.exists(bak):
135 os.remove(bak)
136 os.rename(file, bak)
137 if verbose:
138 print "renamed", file, "to", bak
139 f = open(file, "w")
140 r.write(f)
141 f.close()
142 if verbose:
143 print "wrote new", file
144 else:
145 if verbose:
146 print "unchanged."
147
148 def _rstrip(line, junk='\n \t'):
149 """Return line stripped of trailing spaces, tabs, newlines.
150
151 Note that line.rstrip() instead also strips sundry control characters,
152 but at least one known Emacs user expects to keep junk like that, not
153 mentioning Barry by name or anything <wink>.
154 """
155
156 i = len(line)
157 while i > 0 and line[i-1] in junk:
158 i -= 1
159 return line[:i]
160
161 # Count number of leading blanks.
162 def getlspace(line):
163 i, n = 0, len(line)
164 while i < n and line[i] == " ":
165 i += 1
166 return i
167
168 class DefaultFile:
169 def __init__(self, f, file=None):
170 self.file = file
171
172 def run(self):
173 pass
174 #if verbose:
175 # errprint("Can't guess filetype of given file %s" % self.file)
176
177 def rm_trailing_lines(self, lines):
178 # Remove trailing empty lines.
179 while lines and lines[-1] == "\n":
180 lines.pop()
181 return lines
182
183 def expand_tabs(self, lines):
184 return [_rstrip(line).expandtabs() + "\n" for line in lines]
185
186 class TextFile(DefaultFile):
187 def __init__(self, f, file=None):
188 self.file = file # Save filename.
189 self.raw = f.readlines() # Raw file lines.
190
191 # File lines, rstripped. Dummy at start is so
192 # that we can use tokenize's 1-based line numbering easily.
193 # Note that a line is all-blank iff it's "\n".
194 self.lines = [_rstrip(line) + "\n" for line in self.raw]
195
196 def run(self):
197 self.after = self.lines
198 return self.raw != self.after
199
200 def write(self, f):
201 f.writelines(self.after)
202
203 class PythonFile(TextFile):
204 def __init__(self, f, file=None):
205 TextFile.__init__(self, f, file)
206 self.find_stmt = 1 # next token begins a fresh stmt?
207 self.level = 0 # current indent level
208
209 self.lines = self.expand_tabs(self.lines)
210 self.lines.insert(0, None)
211 self.index = 1 # index into self.lines of next line
212
213 # List of (lineno, indentlevel) pairs, one for each stmt and
214 # comment line. indentlevel is -1 for comment lines, as a
215 # signal that tokenize doesn't know what to do about them;
216 # indeed, they're our headache!
217 self.stats = []
218
219 def run(self):
220 tokenize.tokenize(self.getline, self.tokeneater)
221 # Remove trailing empty lines.
222 lines = self.rm_trailing_lines(self.lines)
223 # Sentinel.
224 stats = self.stats
225 stats.append((len(lines), 0))
226 # Map count of leading spaces to # we want.
227 have2want = {}
228 # Program after transformation.
229 after = self.after = []
230 # Copy over initial empty lines -- there's nothing to do until
231 # we see a line with *something* on it.
232 i = stats[0][0]
233 after.extend(lines[1:i])
234 for i in range(len(stats)-1):
235 thisstmt, thislevel = stats[i]
236 nextstmt = stats[i+1][0]
237 have = getlspace(lines[thisstmt])
238 want = thislevel * 4
239 if want < 0:
240 # A comment line.
241 if have:
242 # An indented comment line. If we saw the same
243 # indentation before, reuse what it most recently
244 # mapped to.
245 want = have2want.get(have, -1)
246 if want < 0:
247 # Then it probably belongs to the next real stmt.
248 for j in xrange(i+1, len(stats)-1):
249 jline, jlevel = stats[j]
250 if jlevel >= 0:
251 if have == getlspace(lines[jline]):
252 want = jlevel * 4
253 break
254 if want < 0: # Maybe it's a hanging
255 # comment like this one,
256 # in which case we should shift it like its base
257 # line got shifted.
258 for j in xrange(i-1, -1, -1):
259 jline, jlevel = stats[j]
260 if jlevel >= 0:
261 want = have + getlspace(after[jline-1]) - \
262 getlspace(lines[jline])
263 break
264 if want < 0:
265 # Still no luck -- leave it alone.
266 want = have
267 else:
268 want = 0
269 assert want >= 0
270 have2want[have] = want
271 diff = want - have
272 if diff == 0 or have == 0:
273 after.extend(lines[thisstmt:nextstmt])
274 else:
275 for line in lines[thisstmt:nextstmt]:
276 if diff > 0:
277 if line == "\n":
278 after.append(line)
279 else:
280 after.append(" " * diff + line)
281 else:
282 remove = min(getlspace(line), -diff)
283 after.append(line[remove:])
284 return self.raw != self.after
285
286 # Line-getter for tokenize.
287 def getline(self):
288 if self.index >= len(self.lines):
289 line = ""
290 else:
291 line = self.lines[self.index]
292 self.index += 1
293 return line
294
295 # Line-eater for tokenize.
296 def tokeneater(self, type, token, (sline, scol), end, line,
297 INDENT=tokenize.INDENT,
298 DEDENT=tokenize.DEDENT,
299 NEWLINE=tokenize.NEWLINE,
300 COMMENT=tokenize.COMMENT,
301 NL=tokenize.NL):
302
303 if type == NEWLINE:
304 # A program statement, or ENDMARKER, will eventually follow,
305 # after some (possibly empty) run of tokens of the form
306 # (NL | COMMENT)* (INDENT | DEDENT+)?
307 self.find_stmt = 1
308
309 elif type == INDENT:
310 self.find_stmt = 1
311 self.level += 1
312
313 elif type == DEDENT:
314 self.find_stmt = 1
315 self.level -= 1
316
317 elif type == COMMENT:
318 if self.find_stmt:
319 self.stats.append((sline, -1))
320 # but we're still looking for a new stmt, so leave
321 # find_stmt alone
322
323 elif type == NL:
324 pass
325
326 elif self.find_stmt:
327 # This is the first "real token" following a NEWLINE, so it
328 # must be the first token of the next program statement, or an
329 # ENDMARKER.
330 self.find_stmt = 0
331 if line: # not endmarker
332 self.stats.append((sline, self.level))
333
334 UnknownFile = ImageFile = DefaultFile # just the empty file object, do nothing
335 ConfigFile = ScriptFile = StyleFile = TextFile # simple textfiles
336
337 if __name__ == '__main__':
338 main()