Merge Gabe's changes with mine.
[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 whitespace_types = ('C', 'C++', 'swig', 'python', 'asm', 'isa')
55 format_types = ( 'C', 'C++' )
56
57 def file_type(filename):
58 extension = filename.split('.')
59 extension = len(extension) > 1 and extension[-1]
60 return lang_types.get(extension, None)
61
62 def checkwhite_line(line):
63 match = lead.search(line)
64 if match and match.group(1).find('\t') != -1:
65 return False
66
67 match = trail.search(line)
68 if match:
69 return False
70
71 return True
72
73 def checkwhite(filename):
74 if file_type(filename) not in whitespace_types:
75 return
76
77 try:
78 f = file(filename, 'r+')
79 except OSError, msg:
80 print 'could not open file %s: %s' % (filename, msg)
81 return
82
83 for num,line in enumerate(f):
84 if not checkwhite_line(line):
85 yield line,num + 1
86
87 def fixwhite_line(line):
88 if lead.search(line):
89 newline = ''
90 for i,c in enumerate(line):
91 if c == ' ':
92 newline += ' '
93 elif c == '\t':
94 newline += ' ' * (tabsize - len(newline) % tabsize)
95 else:
96 newline += line[i:]
97 break
98
99 line = newline
100
101 return line.rstrip() + '\n'
102
103 def fixwhite(filename, tabsize, fixonly=None):
104 if file_type(filename) not in whitespace_types:
105 return
106
107 try:
108 f = file(filename, 'r+')
109 except OSError, msg:
110 print 'could not open file %s: %s' % (filename, msg)
111 return
112
113 lines = list(f)
114
115 f.seek(0)
116 f.truncate()
117
118 for i,line in enumerate(lines):
119 if fixonly is None or i in fixonly:
120 line = fixwhite_line(line)
121
122 print >>f, line,
123
124 def linelen(line):
125 tabs = line.count('\t')
126 if not tabs:
127 return len(line)
128
129 count = 0
130 for c in line:
131 if c == '\t':
132 count += tabsize - count % tabsize
133 else:
134 count += 1
135
136 return count
137
138 class ValidationStats(object):
139 def __init__(self):
140 self.toolong = 0
141 self.toolong80 = 0
142 self.leadtabs = 0
143 self.trailwhite = 0
144 self.badcontrol = 0
145 self.cret = 0
146
147 def dump(self):
148 print '''\
149 %d violations of lines over 79 chars. %d of which are 80 chars exactly.
150 %d cases of whitespace at the end of a line.
151 %d cases of tabs to indent.
152 %d bad parens after if/while/for.
153 %d carriage returns found.
154 ''' % (self.toolong, self.toolong80, self.trailwhite, self.leadtabs,
155 self.badcontrol, self.cret)
156
157 def __nonzero__(self):
158 return self.toolong or self.toolong80 or self.leadtabs or \
159 self.trailwhite or self.badcontrol or self.cret
160
161 def validate(filename, stats, verbose, exit_code):
162 if file_type(filename) not in format_types:
163 return
164
165 def msg(lineno, line, message):
166 print '%s:%d>' % (filename, lineno + 1), message
167 if verbose > 2:
168 print line
169
170 def bad():
171 if exit_code is not None:
172 sys.exit(exit_code)
173
174 cpp = filename.endswith('.cc') or filename.endswith('.hh')
175 py = filename.endswith('.py')
176
177 if py + cpp != 1:
178 raise AttributeError, \
179 "I don't know how to deal with the file %s" % filename
180
181 try:
182 f = file(filename, 'r')
183 except OSError:
184 if verbose > 0:
185 print 'could not open file %s' % filename
186 bad()
187 return
188
189 for i,line in enumerate(f):
190 line = line.rstrip('\n')
191
192 # no carriage returns
193 if line.find('\r') != -1:
194 self.cret += 1
195 if verbose > 1:
196 msg(i, line, 'carriage return found')
197 bad()
198
199 # lines max out at 79 chars
200 llen = linelen(line)
201 if llen > 79:
202 stats.toolong += 1
203 if llen == 80:
204 stats.toolong80 += 1
205 if verbose > 1:
206 msg(i, line, 'line too long (%d chars)' % llen)
207 bad()
208
209 # no tabs used to indent
210 match = lead.search(line)
211 if match and match.group(1).find('\t') != -1:
212 stats.leadtabs += 1
213 if verbose > 1:
214 msg(i, line, 'using tabs to indent')
215 bad()
216
217 # no trailing whitespace
218 if trail.search(line):
219 stats.trailwhite +=1
220 if verbose > 1:
221 msg(i, line, 'trailing whitespace')
222 bad()
223
224 # for c++, exactly one space betwen if/while/for and (
225 if cpp:
226 match = any_control.search(line)
227 if match and not good_control.search(line):
228 stats.badcontrol += 1
229 if verbose > 1:
230 msg(i, line, 'improper spacing after %s' % match.group(1))
231 bad()
232
233 def check_whitespace(ui, repo, hooktype, node, parent1, parent2):
234 from mercurial import bdiff, mdiff, util
235 if hooktype != 'pretxncommit':
236 raise AttributeError, \
237 "This hook is only meant for pretxncommit, not %s" % hooktype
238
239 tabsize = 8
240 verbose = ui.configbool('style', 'verbose', False)
241 def prompt(name, fixonly=None):
242 result = ui.prompt("(a)bort, (i)gnore, or (f)ix?", "^[aif]$", "a")
243 if result == 'a':
244 return True
245 elif result == 'i':
246 pass
247 elif result == 'f':
248 fixwhite(name, tabsize, fixonly)
249 else:
250 raise RepoError, "Invalid response: '%s'" % result
251
252 return False
253
254 modified, added, removed, deleted, unknown, ignore, clean = repo.status()
255
256 for fname in added:
257 ok = True
258 for line,num in checkwhite(fname):
259 ui.write("invalid whitespace in %s:%d\n" % (fname, num))
260 if verbose:
261 ui.write(">>%s<<\n" % line[-1])
262 ok = False
263
264 if not ok:
265 if prompt(fname):
266 return True
267
268 wctx = repo.workingctx()
269 for fname in modified:
270 fctx = wctx.filectx(fname)
271 pctx = fctx.parents()
272 assert len(pctx) == 1
273
274 pdata = pctx[0].data()
275 fdata = fctx.data()
276
277 fixonly = set()
278 lines = enumerate(mdiff.splitnewlines(fdata))
279 for pbeg, pend, fbeg, fend in bdiff.blocks(pdata, fdata):
280 for i, line in lines:
281 if i < fbeg:
282 if checkwhite_line(line):
283 continue
284
285 ui.write("invalid whitespace: %s:%d\n" % (fname, i+1))
286 if verbose:
287 ui.write(">>%s<<\n" % line[:-1])
288 fixonly.add(i)
289 elif i + 1 >= fend:
290 break
291
292 if fixonly:
293 if prompt(fname, fixonly):
294 return True
295
296 def check_format(ui, repo, hooktype, node, parent1, parent2):
297 if hooktype != 'pretxncommit':
298 raise AttributeError, \
299 "This hook is only meant for pretxncommit, not %s" % hooktype
300
301 modified, added, removed, deleted, unknown, ignore, clean = repo.status()
302
303 verbose = 0
304 stats = ValidationStats()
305 for f in modified + added:
306 validate(f, stats, verbose, None)
307
308 if stats:
309 stats.dump()
310 result = ui.prompt("invalid formatting\n(i)gnore or (a)bort?",
311 "^[ia]$", "a")
312 if result.startswith('i'):
313 pass
314 elif result.startswith('a'):
315 return True
316 else:
317 raise RepoError, "Invalid response: '%s'" % result
318
319 return False
320
321 if __name__ == '__main__':
322 import getopt
323
324 progname = sys.argv[0]
325 if len(sys.argv) < 2:
326 sys.exit('usage: %s <command> [<command args>]' % progname)
327
328 fixwhite_usage = '%s fixwhite [-t <tabsize> ] <path> [...] \n' % progname
329 chkformat_usage = '%s chkformat <path> [...] \n' % progname
330 chkwhite_usage = '%s chkwhite <path> [...] \n' % progname
331
332 command = sys.argv[1]
333 if command == 'fixwhite':
334 flags = 't:'
335 usage = fixwhite_usage
336 elif command == 'chkwhite':
337 flags = 'nv'
338 usage = chkwhite_usage
339 elif command == 'chkformat':
340 flags = 'nv'
341 usage = chkformat_usage
342 else:
343 sys.exit(fixwhite_usage + chkwhite_usage + chkformat_usage)
344
345 opts, args = getopt.getopt(sys.argv[2:], flags)
346
347 code = 1
348 verbose = 1
349 tabsize = 8
350 for opt,arg in opts:
351 if opt == '-n':
352 code = None
353 if opt == '-t':
354 tabsize = int(arg)
355 if opt == '-v':
356 verbose += 1
357
358 if command == 'fixwhite':
359 for filename in args:
360 fixwhite(filename, tabsize)
361 elif command == 'chkwhite':
362 for filename in args:
363 line = checkwhite(filename)
364 if line:
365 print 'invalid whitespace at %s:%d' % (filename, line)
366 elif command == 'chkformat':
367 stats = ValidationStats()
368 for filename in files:
369 validate(filename, stats=stats, verbose=verbose, exit_code=code)
370
371 if verbose > 0:
372 stats.dump()
373 else:
374 sys.exit("command '%s' not found" % command)