2 # Copyright (c) 2006 The Regents of The University of Michigan
3 # Copyright (c) 2007 The Hewlett-Packard Development Company
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions are
8 # met: redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer;
10 # redistributions in binary form must reproduce the above copyright
11 # notice, this list of conditions and the following disclaimer in the
12 # documentation and/or other materials provided with the distribution;
13 # neither the name of the copyright holders nor the names of its
14 # contributors may be used to endorse or promote products derived from
15 # this software without specific prior written permission.
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 # Authors: Nathan Binkert
35 lead
= re
.compile(r
'^([ \t]+)')
36 trail
= re
.compile(r
'([ \t]+)$')
37 any_control
= re
.compile(r
'\b(if|while|for)[ \t]*[(]')
38 good_control
= re
.compile(r
'\b(if|while|for) [(]')
40 lang_types
= { 'c' : "C",
55 def file_type(filename
):
56 extension
= filename
.split('.')
57 extension
= len(extension
) > 1 and extension
[-1]
58 return lang_types
.get(extension
, None)
60 whitespace_types
= ('C', 'C++', 'swig', 'python', 'asm', 'isa')
61 def whitespace_file(filename
):
62 if file_type(filename
) in whitespace_types
:
65 if filename
.startswith("SCons"):
70 format_types
= ( 'C', 'C++' )
71 def format_file(filename
):
72 if file_type(filename
) in format_types
:
77 def checkwhite_line(line
):
78 match
= lead
.search(line
)
79 if match
and match
.group(1).find('\t') != -1:
82 match
= trail
.search(line
)
88 def checkwhite(filename
):
89 if not whitespace_file(filename
):
93 f
= file(filename
, 'r+')
95 print 'could not open file %s: %s' % (filename
, msg
)
98 for num
,line
in enumerate(f
):
99 if not checkwhite_line(line
):
102 def fixwhite_line(line
, tabsize
):
103 if lead
.search(line
):
105 for i
,c
in enumerate(line
):
109 newline
+= ' ' * (tabsize
- len(newline
) % tabsize
)
116 return line
.rstrip() + '\n'
118 def fixwhite(filename
, tabsize
, fixonly
=None):
119 if not whitespace_file(filename
):
123 f
= file(filename
, 'r+')
125 print 'could not open file %s: %s' % (filename
, msg
)
133 for i
,line
in enumerate(lines
):
134 if fixonly
is None or i
in fixonly
:
135 line
= fixwhite_line(line
, tabsize
)
140 tabs
= line
.count('\t')
147 count
+= tabsize
- count
% tabsize
153 class ValidationStats(object):
164 %d violations of lines over 79 chars. %d of which are 80 chars exactly.
165 %d cases of whitespace at the end of a line.
166 %d cases of tabs to indent.
167 %d bad parens after if/while/for.
168 %d carriage returns found.
169 ''' % (self
.toolong
, self
.toolong80
, self
.trailwhite
, self
.leadtabs
,
170 self
.badcontrol
, self
.cret
)
172 def __nonzero__(self
):
173 return self
.toolong
or self
.toolong80
or self
.leadtabs
or \
174 self
.trailwhite
or self
.badcontrol
or self
.cret
176 def validate(filename
, stats
, verbose
, exit_code
):
177 if not format_file(filename
):
180 def msg(lineno
, line
, message
):
181 print '%s:%d>' % (filename
, lineno
+ 1), message
186 if exit_code
is not None:
189 cpp
= filename
.endswith('.cc') or filename
.endswith('.hh')
190 py
= filename
.endswith('.py')
193 raise AttributeError, \
194 "I don't know how to deal with the file %s" % filename
197 f
= file(filename
, 'r')
200 print 'could not open file %s' % filename
204 for i
,line
in enumerate(f
):
205 line
= line
.rstrip('\n')
207 # no carriage returns
208 if line
.find('\r') != -1:
211 msg(i
, line
, 'carriage return found')
214 # lines max out at 79 chars
221 msg(i
, line
, 'line too long (%d chars)' % llen
)
224 # no tabs used to indent
225 match
= lead
.search(line
)
226 if match
and match
.group(1).find('\t') != -1:
229 msg(i
, line
, 'using tabs to indent')
232 # no trailing whitespace
233 if trail
.search(line
):
236 msg(i
, line
, 'trailing whitespace')
239 # for c++, exactly one space betwen if/while/for and (
241 match
= any_control
.search(line
)
242 if match
and not good_control
.search(line
):
243 stats
.badcontrol
+= 1
245 msg(i
, line
, 'improper spacing after %s' % match
.group(1))
248 def modified_lines(old_data
, new_data
, max_lines
):
249 from itertools
import count
250 from mercurial
import bdiff
, mdiff
254 for pbeg
, pend
, fbeg
, fend
in bdiff
.blocks(old_data
, new_data
):
264 def do_check_whitespace(ui
, repo
, *files
, **args
):
265 """check files for proper m5 style guidelines"""
266 from mercurial
import mdiff
, util
269 files
= frozenset(files
)
272 return files
and name
in files
274 def prompt(name
, fixonly
=None):
275 if args
.get('auto', False):
279 result
= ui
.prompt("(a)bort, (i)gnore, or (f)ix?", default
='a')
286 fixwhite(repo
.wjoin(name
), args
['tabsize'], fixonly
)
290 modified
, added
, removed
, deleted
, unknown
, ignore
, clean
= repo
.status()
297 for line
,num
in checkwhite(repo
.wjoin(fname
)):
298 ui
.write("invalid whitespace in %s:%d\n" % (fname
, num
))
300 ui
.write(">>%s<<\n" % line
[-1])
308 wctx
= repo
.workingctx()
310 from mercurial
import context
311 wctx
= context
.workingctx(repo
)
313 for fname
in modified
:
317 if not whitespace_file(fname
):
320 fctx
= wctx
.filectx(fname
)
321 pctx
= fctx
.parents()
323 file_data
= fctx
.data()
324 lines
= mdiff
.splitnewlines(file_data
)
325 if len(pctx
) in (1, 2):
326 mod_lines
= modified_lines(pctx
[0].data(), file_data
, len(lines
))
328 m2
= modified_lines(pctx
[1].data(), file_data
, len(lines
))
329 # only the lines that are new in both
330 mod_lines
= mod_lines
& m2
332 mod_lines
= xrange(0, len(lines
))
335 for i
,line
in enumerate(lines
):
336 if i
not in mod_lines
:
339 if checkwhite_line(line
):
342 ui
.write("invalid whitespace: %s:%d\n" % (fname
, i
+1))
344 ui
.write(">>%s<<\n" % line
[:-1])
348 if prompt(fname
, fixonly
):
351 def check_whitespace(ui
, repo
, hooktype
, node
, parent1
, parent2
, **kwargs
):
352 if hooktype
!= 'pretxncommit':
353 raise AttributeError, \
354 "This hook is only meant for pretxncommit, not %s" % hooktype
356 args
= { 'tabsize' : 8 }
357 return do_check_whitespace(ui
, repo
, **args
)
359 def check_format(ui
, repo
, hooktype
, node
, parent1
, parent2
, **kwargs
):
360 if hooktype
!= 'pretxncommit':
361 raise AttributeError, \
362 "This hook is only meant for pretxncommit, not %s" % hooktype
364 modified
, added
, removed
, deleted
, unknown
, ignore
, clean
= repo
.status()
367 stats
= ValidationStats()
368 for f
in modified
+ added
:
369 validate(f
, stats
, verbose
, None)
373 result
= ui
.prompt("invalid formatting\n(i)gnore or (a)bort?",
375 if result
.startswith('i'):
377 elif result
.startswith('a'):
380 raise util
.Abort(_("Invalid response: '%s'") % result
)
385 from mercurial
.i18n
import _
392 ( do_check_whitespace
,
393 [ ('a', 'auto', False, _("automatically fix whitespace")),
394 ('t', 'tabsize', 8, _("Number of spaces TAB indents")) ],
395 _('hg m5check [-t <tabsize>] [FILE]...')),
397 if __name__
== '__main__':
400 progname
= sys
.argv
[0]
401 if len(sys
.argv
) < 2:
402 sys
.exit('usage: %s <command> [<command args>]' % progname
)
404 fixwhite_usage
= '%s fixwhite [-t <tabsize> ] <path> [...] \n' % progname
405 chkformat_usage
= '%s chkformat <path> [...] \n' % progname
406 chkwhite_usage
= '%s chkwhite <path> [...] \n' % progname
408 command
= sys
.argv
[1]
409 if command
== 'fixwhite':
411 usage
= fixwhite_usage
412 elif command
== 'chkwhite':
414 usage
= chkwhite_usage
415 elif command
== 'chkformat':
417 usage
= chkformat_usage
419 sys
.exit(fixwhite_usage
+ chkwhite_usage
+ chkformat_usage
)
421 opts
, args
= getopt
.getopt(sys
.argv
[2:], flags
)
434 if command
== 'fixwhite':
435 for filename
in args
:
436 fixwhite(filename
, tabsize
)
437 elif command
== 'chkwhite':
438 for filename
in args
:
439 for line
,num
in checkwhite(filename
):
440 print 'invalid whitespace: %s:%d' % (filename
, num
)
442 print '>>%s<<' % line
[:-1]
443 elif command
== 'chkformat':
444 stats
= ValidationStats()
445 for filename
in args
:
446 validate(filename
, stats
=stats
, verbose
=verbose
, exit_code
=code
)
451 sys
.exit("command '%s' not found" % command
)