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 sys
.path
.insert(0, os
.path
.dirname(__file__
))
37 from file_types
import lang_type
40 lead
= re
.compile(r
'^([ \t]+)')
41 trail
= re
.compile(r
'([ \t]+)$')
42 any_control
= re
.compile(r
'\b(if|while|for)[ \t]*[(]')
43 good_control
= re
.compile(r
'\b(if|while|for) [(]')
45 whitespace_types
= set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons'))
46 format_types
= set(('C', 'C++'))
48 def checkwhite_line(line
):
49 match
= lead
.search(line
)
50 if match
and match
.group(1).find('\t') != -1:
53 match
= trail
.search(line
)
59 def checkwhite(filename
):
60 if lang_type(filename
) not in whitespace_types
:
64 f
= file(filename
, 'r+')
66 print 'could not open file %s: %s' % (filename
, msg
)
69 for num
,line
in enumerate(f
):
70 if not checkwhite_line(line
):
73 def fixwhite_line(line
):
76 for i
,c
in enumerate(line
):
80 newline
+= ' ' * (tabsize
- len(newline
) % tabsize
)
87 return line
.rstrip() + '\n'
89 def fixwhite(filename
, fixonly
=None):
90 if lang_type(filename
) not in whitespace_types
:
94 f
= file(filename
, 'r+')
96 print 'could not open file %s: %s' % (filename
, msg
)
104 for i
,line
in enumerate(lines
):
105 if fixonly
is None or i
in fixonly
:
106 line
= fixwhite_line(line
)
111 tabs
= line
.count('\t')
118 count
+= tabsize
- count
% tabsize
124 class ValidationStats(object):
135 %d violations of lines over 79 chars. %d of which are 80 chars exactly.
136 %d cases of whitespace at the end of a line.
137 %d cases of tabs to indent.
138 %d bad parens after if/while/for.
139 %d carriage returns found.
140 ''' % (self
.toolong
, self
.toolong80
, self
.trailwhite
, self
.leadtabs
,
141 self
.badcontrol
, self
.cret
)
143 def __nonzero__(self
):
144 return self
.toolong
or self
.toolong80
or self
.leadtabs
or \
145 self
.trailwhite
or self
.badcontrol
or self
.cret
147 def validate(filename
, stats
, verbose
, exit_code
):
148 if lang_type(filename
) not in format_types
:
151 def msg(lineno
, line
, message
):
152 print '%s:%d>' % (filename
, lineno
+ 1), message
157 if exit_code
is not None:
161 f
= file(filename
, 'r')
164 print 'could not open file %s' % filename
168 for i
,line
in enumerate(f
):
169 line
= line
.rstrip('\n')
171 # no carriage returns
172 if line
.find('\r') != -1:
175 msg(i
, line
, 'carriage return found')
178 # lines max out at 79 chars
185 msg(i
, line
, 'line too long (%d chars)' % llen
)
188 # no tabs used to indent
189 match
= lead
.search(line
)
190 if match
and match
.group(1).find('\t') != -1:
193 msg(i
, line
, 'using tabs to indent')
196 # no trailing whitespace
197 if trail
.search(line
):
200 msg(i
, line
, 'trailing whitespace')
203 # for c++, exactly one space betwen if/while/for and (
205 match
= any_control
.search(line
)
206 if match
and not good_control
.search(line
):
207 stats
.badcontrol
+= 1
209 msg(i
, line
, 'improper spacing after %s' % match
.group(1))
212 def modified_lines(old_data
, new_data
, max_lines
):
213 from itertools
import count
214 from mercurial
import bdiff
, mdiff
218 for pbeg
, pend
, fbeg
, fend
in bdiff
.blocks(old_data
, new_data
):
228 def do_check_style(ui
, repo
, *files
, **args
):
229 """check files for proper m5 style guidelines"""
230 from mercurial
import mdiff
, util
233 files
= frozenset(files
)
236 return files
and name
in files
238 def prompt(name
, func
, fixonly
=None):
239 if args
.get('auto', False):
243 result
= ui
.prompt("(a)bort, (i)gnore, or (f)ix?", default
='a')
250 func(repo
.wjoin(name
), fixonly
)
254 modified
, added
, removed
, deleted
, unknown
, ignore
, clean
= repo
.status()
261 for line
,num
in checkwhite(repo
.wjoin(fname
)):
262 ui
.write("invalid whitespace in %s:%d\n" % (fname
, num
))
264 ui
.write(">>%s<<\n" % line
[-1])
268 if prompt(fname
, fixwhite
):
272 wctx
= repo
.workingctx()
274 from mercurial
import context
275 wctx
= context
.workingctx(repo
)
277 for fname
in modified
:
281 if lang_type(fname
) not in whitespace_types
:
284 fctx
= wctx
.filectx(fname
)
285 pctx
= fctx
.parents()
287 file_data
= fctx
.data()
288 lines
= mdiff
.splitnewlines(file_data
)
289 if len(pctx
) in (1, 2):
290 mod_lines
= modified_lines(pctx
[0].data(), file_data
, len(lines
))
292 m2
= modified_lines(pctx
[1].data(), file_data
, len(lines
))
293 # only the lines that are new in both
294 mod_lines
= mod_lines
& m2
296 mod_lines
= xrange(0, len(lines
))
299 for i
,line
in enumerate(lines
):
300 if i
not in mod_lines
:
303 if checkwhite_line(line
):
306 ui
.write("invalid whitespace: %s:%d\n" % (fname
, i
+1))
308 ui
.write(">>%s<<\n" % line
[:-1])
312 if prompt(fname
, fixwhite
, fixonly
):
315 def do_check_format(ui
, repo
, **args
):
316 modified
, added
, removed
, deleted
, unknown
, ignore
, clean
= repo
.status()
319 stats
= ValidationStats()
320 for f
in modified
+ added
:
321 validate(f
, stats
, verbose
, None)
325 result
= ui
.prompt("invalid formatting\n(i)gnore or (a)bort?",
327 if result
.startswith('i'):
329 elif result
.startswith('a'):
332 raise util
.Abort(_("Invalid response: '%s'") % result
)
336 def check_hook(hooktype
):
337 if hooktype
not in ('pretxncommit', 'pre-qrefresh'):
338 raise AttributeError, \
339 "This hook is not meant for %s" % hooktype
341 def check_style(ui
, repo
, hooktype
, **kwargs
):
346 return do_check_style(ui
, repo
, **args
)
349 traceback
.print_exc()
352 def check_format(ui
, repo
, hooktype
, **kwargs
):
357 return do_check_format(ui
, repo
, **args
)
360 traceback
.print_exc()
364 from mercurial
.i18n
import _
372 [ ('a', 'auto', False, _("automatically fix whitespace")) ],
373 _('hg m5style [-a] [FILE]...')),
377 _('hg m5format [FILE]...')),
380 if __name__
== '__main__':
383 progname
= sys
.argv
[0]
384 if len(sys
.argv
) < 2:
385 sys
.exit('usage: %s <command> [<command args>]' % progname
)
387 fixwhite_usage
= '%s fixwhite [-t <tabsize> ] <path> [...] \n' % progname
388 chkformat_usage
= '%s chkformat <path> [...] \n' % progname
389 chkwhite_usage
= '%s chkwhite <path> [...] \n' % progname
391 command
= sys
.argv
[1]
392 if command
== 'fixwhite':
394 usage
= fixwhite_usage
395 elif command
== 'chkwhite':
397 usage
= chkwhite_usage
398 elif command
== 'chkformat':
400 usage
= chkformat_usage
402 sys
.exit(fixwhite_usage
+ chkwhite_usage
+ chkformat_usage
)
404 opts
, args
= getopt
.getopt(sys
.argv
[2:], flags
)
416 if command
== 'fixwhite':
417 for filename
in args
:
418 fixwhite(filename
, tabsize
)
419 elif command
== 'chkwhite':
420 for filename
in args
:
421 for line
,num
in checkwhite(filename
):
422 print 'invalid whitespace: %s:%d' % (filename
, num
)
424 print '>>%s<<' % line
[:-1]
425 elif command
== 'chkformat':
426 stats
= ValidationStats()
427 for filename
in args
:
428 validate(filename
, stats
=stats
, verbose
=verbose
, exit_code
=code
)
433 sys
.exit("command '%s' not found" % command
)