Merge with head.
[gem5.git] / util / style.py
1 #! /usr/bin/env python
2 # Copyright (c) 2007 The Regents of The University of Michigan
3 # All rights reserved.
4 #
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions are
7 # met: redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer;
9 # redistributions in binary form must reproduce the above copyright
10 # notice, this list of conditions and the following disclaimer in the
11 # documentation and/or other materials provided with the distribution;
12 # neither the name of the copyright holders nor the names of its
13 # contributors may be used to endorse or promote products derived from
14 # this software without specific prior written permission.
15 #
16 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #
28 # Authors: Nathan Binkert
29
30 import re
31 import os
32 import sys
33
34 lead = re.compile(r'^([ \t]+)')
35 trail = re.compile(r'([ \t]+)$')
36 any_control = re.compile(r'\b(if|while|for)[ \t]*[(]')
37 good_control = re.compile(r'\b(if|while|for) [(]')
38
39 lang_types = { 'c' : "C",
40 'h' : "C",
41 'cc' : "C++",
42 'hh' : "C++",
43 'cxx' : "C++",
44 'hxx' : "C++",
45 'cpp' : "C++",
46 'hpp' : "C++",
47 'C' : "C++",
48 'H' : "C++",
49 'i' : "swig",
50 'py' : "python",
51 's' : "asm",
52 'S' : "asm",
53 'isa' : "isa" }
54 def file_type(filename):
55 extension = filename.split('.')
56 extension = len(extension) > 1 and extension[-1]
57 return lang_types.get(extension, None)
58
59 whitespace_types = ('C', 'C++', 'swig', 'python', 'asm', 'isa')
60 def whitespace_file(filename):
61 if file_type(filename) in whitespace_types:
62 return True
63
64 if filename.startswith("SCons"):
65 return True
66
67 return False
68
69 format_types = ( 'C', 'C++' )
70 def format_file(filename):
71 if file_type(filename) in format_types:
72 return True
73
74 return False
75
76 def checkwhite_line(line):
77 match = lead.search(line)
78 if match and match.group(1).find('\t') != -1:
79 return False
80
81 match = trail.search(line)
82 if match:
83 return False
84
85 return True
86
87 def checkwhite(filename):
88 if not whitespace_file(filename):
89 return
90
91 try:
92 f = file(filename, 'r+')
93 except OSError, msg:
94 print 'could not open file %s: %s' % (filename, msg)
95 return
96
97 for num,line in enumerate(f):
98 if not checkwhite_line(line):
99 yield line,num + 1
100
101 def fixwhite_line(line, tabsize):
102 if lead.search(line):
103 newline = ''
104 for i,c in enumerate(line):
105 if c == ' ':
106 newline += ' '
107 elif c == '\t':
108 newline += ' ' * (tabsize - len(newline) % tabsize)
109 else:
110 newline += line[i:]
111 break
112
113 line = newline
114
115 return line.rstrip() + '\n'
116
117 def fixwhite(filename, tabsize, fixonly=None):
118 if not whitespace_file(filename):
119 return
120
121 try:
122 f = file(filename, 'r+')
123 except OSError, msg:
124 print 'could not open file %s: %s' % (filename, msg)
125 return
126
127 lines = list(f)
128
129 f.seek(0)
130 f.truncate()
131
132 for i,line in enumerate(lines):
133 if fixonly is None or i in fixonly:
134 line = fixwhite_line(line, tabsize)
135
136 print >>f, line,
137
138 def linelen(line):
139 tabs = line.count('\t')
140 if not tabs:
141 return len(line)
142
143 count = 0
144 for c in line:
145 if c == '\t':
146 count += tabsize - count % tabsize
147 else:
148 count += 1
149
150 return count
151
152 class ValidationStats(object):
153 def __init__(self):
154 self.toolong = 0
155 self.toolong80 = 0
156 self.leadtabs = 0
157 self.trailwhite = 0
158 self.badcontrol = 0
159 self.cret = 0
160
161 def dump(self):
162 print '''\
163 %d violations of lines over 79 chars. %d of which are 80 chars exactly.
164 %d cases of whitespace at the end of a line.
165 %d cases of tabs to indent.
166 %d bad parens after if/while/for.
167 %d carriage returns found.
168 ''' % (self.toolong, self.toolong80, self.trailwhite, self.leadtabs,
169 self.badcontrol, self.cret)
170
171 def __nonzero__(self):
172 return self.toolong or self.toolong80 or self.leadtabs or \
173 self.trailwhite or self.badcontrol or self.cret
174
175 def validate(filename, stats, verbose, exit_code):
176 if not format_file(filename):
177 return
178
179 def msg(lineno, line, message):
180 print '%s:%d>' % (filename, lineno + 1), message
181 if verbose > 2:
182 print line
183
184 def bad():
185 if exit_code is not None:
186 sys.exit(exit_code)
187
188 cpp = filename.endswith('.cc') or filename.endswith('.hh')
189 py = filename.endswith('.py')
190
191 if py + cpp != 1:
192 raise AttributeError, \
193 "I don't know how to deal with the file %s" % filename
194
195 try:
196 f = file(filename, 'r')
197 except OSError:
198 if verbose > 0:
199 print 'could not open file %s' % filename
200 bad()
201 return
202
203 for i,line in enumerate(f):
204 line = line.rstrip('\n')
205
206 # no carriage returns
207 if line.find('\r') != -1:
208 self.cret += 1
209 if verbose > 1:
210 msg(i, line, 'carriage return found')
211 bad()
212
213 # lines max out at 79 chars
214 llen = linelen(line)
215 if llen > 79:
216 stats.toolong += 1
217 if llen == 80:
218 stats.toolong80 += 1
219 if verbose > 1:
220 msg(i, line, 'line too long (%d chars)' % llen)
221 bad()
222
223 # no tabs used to indent
224 match = lead.search(line)
225 if match and match.group(1).find('\t') != -1:
226 stats.leadtabs += 1
227 if verbose > 1:
228 msg(i, line, 'using tabs to indent')
229 bad()
230
231 # no trailing whitespace
232 if trail.search(line):
233 stats.trailwhite +=1
234 if verbose > 1:
235 msg(i, line, 'trailing whitespace')
236 bad()
237
238 # for c++, exactly one space betwen if/while/for and (
239 if cpp:
240 match = any_control.search(line)
241 if match and not good_control.search(line):
242 stats.badcontrol += 1
243 if verbose > 1:
244 msg(i, line, 'improper spacing after %s' % match.group(1))
245 bad()
246
247 def modified_lines(old_data, new_data):
248 from itertools import count
249 from mercurial import bdiff, mdiff
250
251 modified = set()
252 counter = count()
253 for pbeg, pend, fbeg, fend in bdiff.blocks(old_data, new_data):
254 for i in counter:
255 if i < fbeg:
256 modified.add(i)
257 elif i + 1 >= fend:
258 break
259 return modified
260
261 def check_whitespace(ui, repo, hooktype, node, parent1, parent2):
262 from mercurial import mdiff
263
264 if hooktype != 'pretxncommit':
265 raise AttributeError, \
266 "This hook is only meant for pretxncommit, not %s" % hooktype
267
268 tabsize = 8
269 verbose = ui.configbool('style', 'verbose', False)
270 def prompt(name, fixonly=None):
271 result = ui.prompt("(a)bort, (i)gnore, or (f)ix?", "^[aif]$", "a")
272 if result == 'a':
273 return True
274 elif result == 'i':
275 pass
276 elif result == 'f':
277 fixwhite(repo.wjoin(name), tabsize, fixonly)
278 else:
279 raise RepoError, "Invalid response: '%s'" % result
280
281 return False
282
283 modified, added, removed, deleted, unknown, ignore, clean = repo.status()
284
285 for fname in added:
286 ok = True
287 for line,num in checkwhite(fname):
288 ui.write("invalid whitespace in %s:%d\n" % (fname, num))
289 if verbose:
290 ui.write(">>%s<<\n" % line[-1])
291 ok = False
292
293 if not ok:
294 if prompt(fname):
295 return True
296
297 wctx = repo.workingctx()
298 for fname in modified:
299 fctx = wctx.filectx(fname)
300 pctx = fctx.parents()
301 assert len(pctx) in (1, 2)
302
303 file_data = fctx.data()
304 mod_lines = modified_lines(pctx[0].data(), file_data)
305 if len(pctx) == 2:
306 m2 = modified_lines(pctx[1].data(), file_data)
307 mod_lines = mod_lines & m2 # only the lines that are new in both
308
309 fixonly = set()
310 for i,line in enumerate(mdiff.splitnewlines(file_data)):
311 if i not in mod_lines:
312 continue
313
314 if checkwhite_line(line):
315 continue
316
317 ui.write("invalid whitespace: %s:%d\n" % (fname, i+1))
318 if verbose:
319 ui.write(">>%s<<\n" % line[:-1])
320 fixonly.add(i)
321
322 if fixonly:
323 if prompt(fname, fixonly):
324 return True
325
326 def check_format(ui, repo, hooktype, node, parent1, parent2):
327 if hooktype != 'pretxncommit':
328 raise AttributeError, \
329 "This hook is only meant for pretxncommit, not %s" % hooktype
330
331 modified, added, removed, deleted, unknown, ignore, clean = repo.status()
332
333 verbose = 0
334 stats = ValidationStats()
335 for f in modified + added:
336 validate(f, stats, verbose, None)
337
338 if stats:
339 stats.dump()
340 result = ui.prompt("invalid formatting\n(i)gnore or (a)bort?",
341 "^[ia]$", "a")
342 if result.startswith('i'):
343 pass
344 elif result.startswith('a'):
345 return True
346 else:
347 raise RepoError, "Invalid response: '%s'" % result
348
349 return False
350
351 if __name__ == '__main__':
352 import getopt
353
354 progname = sys.argv[0]
355 if len(sys.argv) < 2:
356 sys.exit('usage: %s <command> [<command args>]' % progname)
357
358 fixwhite_usage = '%s fixwhite [-t <tabsize> ] <path> [...] \n' % progname
359 chkformat_usage = '%s chkformat <path> [...] \n' % progname
360 chkwhite_usage = '%s chkwhite <path> [...] \n' % progname
361
362 command = sys.argv[1]
363 if command == 'fixwhite':
364 flags = 't:'
365 usage = fixwhite_usage
366 elif command == 'chkwhite':
367 flags = 'nv'
368 usage = chkwhite_usage
369 elif command == 'chkformat':
370 flags = 'nv'
371 usage = chkformat_usage
372 else:
373 sys.exit(fixwhite_usage + chkwhite_usage + chkformat_usage)
374
375 opts, args = getopt.getopt(sys.argv[2:], flags)
376
377 code = 1
378 verbose = 1
379 tabsize = 8
380 for opt,arg in opts:
381 if opt == '-n':
382 code = None
383 if opt == '-t':
384 tabsize = int(arg)
385 if opt == '-v':
386 verbose += 1
387
388 if command == 'fixwhite':
389 for filename in args:
390 fixwhite(filename, tabsize)
391 elif command == 'chkwhite':
392 for filename in args:
393 for line,num in checkwhite(filename):
394 print 'invalid whitespace: %s:%d' % (filename, num)
395 if verbose:
396 print '>>%s<<' % line[:-1]
397 elif command == 'chkformat':
398 stats = ValidationStats()
399 for filename in files:
400 validate(filename, stats=stats, verbose=verbose, exit_code=code)
401
402 if verbose > 0:
403 stats.dump()
404 else:
405 sys.exit("command '%s' not found" % command)