2 # Copyright (c) 2007 The Regents of The University of Michigan
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.
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.
28 # Authors: Nathan Binkert
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) [(]')
39 lang_types
= { 'c' : "C",
54 whitespace_types
= ('C', 'C++', 'swig', 'python', 'asm', 'isa')
55 format_types
= ( 'C', 'C++' )
57 def file_type(filename
):
58 extension
= filename
.split('.')
59 extension
= len(extension
) > 1 and extension
[-1]
60 return lang_types
.get(extension
, None)
62 def checkwhite_line(line
):
63 match
= lead
.search(line
)
64 if match
and match
.group(1).find('\t') != -1:
67 match
= trail
.search(line
)
73 def checkwhite(filename
):
74 if file_type(filename
) not in whitespace_types
:
78 f
= file(filename
, 'r+')
80 print 'could not open file %s: %s' % (filename
, msg
)
83 for num
,line
in enumerate(f
):
84 if not checkwhite_line(line
):
87 def fixwhite_line(line
):
90 for i
,c
in enumerate(line
):
94 newline
+= ' ' * (tabsize
- len(newline
) % tabsize
)
101 return line
.rstrip() + '\n'
103 def fixwhite(filename
, tabsize
, fixonly
=None):
104 if file_type(filename
) not in whitespace_types
:
108 f
= file(filename
, 'r+')
110 print 'could not open file %s: %s' % (filename
, msg
)
118 for i
,line
in enumerate(lines
):
119 if fixonly
is None or i
in fixonly
:
120 line
= fixwhite_line(line
)
125 tabs
= line
.count('\t')
132 count
+= tabsize
- count
% tabsize
138 class ValidationStats(object):
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
)
157 def __nonzero__(self
):
158 return self
.toolong
or self
.toolong80
or self
.leadtabs
or \
159 self
.trailwhite
or self
.badcontrol
or self
.cret
161 def validate(filename
, stats
, verbose
, exit_code
):
162 if file_type(filename
) not in format_types
:
165 def msg(lineno
, line
, message
):
166 print '%s:%d>' % (filename
, lineno
+ 1), message
171 if exit_code
is not None:
174 cpp
= filename
.endswith('.cc') or filename
.endswith('.hh')
175 py
= filename
.endswith('.py')
178 raise AttributeError, \
179 "I don't know how to deal with the file %s" % filename
182 f
= file(filename
, 'r')
185 print 'could not open file %s' % filename
189 for i
,line
in enumerate(f
):
190 line
= line
.rstrip('\n')
192 # no carriage returns
193 if line
.find('\r') != -1:
196 msg(i
, line
, 'carriage return found')
199 # lines max out at 79 chars
206 msg(i
, line
, 'line too long (%d chars)' % llen
)
209 # no tabs used to indent
210 match
= lead
.search(line
)
211 if match
and match
.group(1).find('\t') != -1:
214 msg(i
, line
, 'using tabs to indent')
217 # no trailing whitespace
218 if trail
.search(line
):
221 msg(i
, line
, 'trailing whitespace')
224 # for c++, exactly one space betwen if/while/for and (
226 match
= any_control
.search(line
)
227 if match
and not good_control
.search(line
):
228 stats
.badcontrol
+= 1
230 msg(i
, line
, 'improper spacing after %s' % match
.group(1))
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
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")
248 fixwhite(name
, tabsize
, fixonly
)
250 raise RepoError
, "Invalid response: '%s'" % result
254 modified
, added
, removed
, deleted
, unknown
, ignore
, clean
= repo
.status()
258 for line
,num
in checkwhite(fname
):
259 ui
.write("invalid whitespace in %s:%d\n" % (fname
, num
))
261 ui
.write(">>%s<<\n" % line
[-1])
268 wctx
= repo
.workingctx()
269 for fname
in modified
:
270 fctx
= wctx
.filectx(fname
)
271 pctx
= fctx
.parents()
272 assert len(pctx
) == 1
274 pdata
= pctx
[0].data()
278 lines
= enumerate(mdiff
.splitnewlines(fdata
))
279 for pbeg
, pend
, fbeg
, fend
in bdiff
.blocks(pdata
, fdata
):
280 for i
, line
in lines
:
282 if checkwhite_line(line
):
285 ui
.write("invalid whitespace: %s:%d\n" % (fname
, i
+1))
287 ui
.write(">>%s<<\n" % line
[:-1])
293 if prompt(fname
, fixonly
):
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
301 modified
, added
, removed
, deleted
, unknown
, ignore
, clean
= repo
.status()
304 stats
= ValidationStats()
305 for f
in modified
+ added
:
306 validate(f
, stats
, verbose
, None)
310 result
= ui
.prompt("invalid formatting\n(i)gnore or (a)bort?",
312 if result
.startswith('i'):
314 elif result
.startswith('a'):
317 raise RepoError
, "Invalid response: '%s'" % result
321 if __name__
== '__main__':
324 progname
= sys
.argv
[0]
325 if len(sys
.argv
) < 2:
326 sys
.exit('usage: %s <command> [<command args>]' % progname
)
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
332 command
= sys
.argv
[1]
333 if command
== 'fixwhite':
335 usage
= fixwhite_usage
336 elif command
== 'chkwhite':
338 usage
= chkwhite_usage
339 elif command
== 'chkformat':
341 usage
= chkformat_usage
343 sys
.exit(fixwhite_usage
+ chkwhite_usage
+ chkformat_usage
)
345 opts
, args
= getopt
.getopt(sys
.argv
[2:], flags
)
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
)
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
)
374 sys
.exit("command '%s' not found" % command
)