2 # Copyright (c) 2006 The Regents of The University of Michigan
3 # Copyright (c) 2007,2011 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
36 from os
.path
import dirname
, join
as joinpath
37 from itertools
import count
38 from mercurial
import bdiff
, mdiff
40 current_dir
= dirname(__file__
)
41 sys
.path
.insert(0, current_dir
)
42 sys
.path
.insert(1, joinpath(dirname(current_dir
), 'src', 'python'))
44 from m5
.util
import neg_inf
, pos_inf
, Region
, Regions
46 from file_types
import lang_type
48 all_regions
= Regions(Region(neg_inf
, pos_inf
))
51 lead
= re
.compile(r
'^([ \t]+)')
52 trail
= re
.compile(r
'([ \t]+)$')
53 any_control
= re
.compile(r
'\b(if|while|for)[ \t]*[(]')
54 good_control
= re
.compile(r
'\b(if|while|for) [(]')
56 format_types
= set(('C', 'C++'))
58 def modified_regions(old_data
, new_data
):
61 for pbeg
, pend
, fbeg
, fend
in bdiff
.blocks(old_data
, new_data
):
62 if beg
is not None and beg
!= fbeg
:
63 regions
.append(beg
, fbeg
)
67 def modregions(wctx
, fname
):
68 fctx
= wctx
.filectx(fname
)
71 file_data
= fctx
.data()
72 lines
= mdiff
.splitnewlines(file_data
)
73 if len(pctx
) in (1, 2):
74 mod_regions
= modified_regions(pctx
[0].data(), file_data
)
76 m2
= modified_regions(pctx
[1].data(), file_data
)
77 # only the lines that are new in both
80 mod_regions
= Regions()
81 mod_regions
.add(0, len(lines
))
85 class UserInterface(object):
86 def __init__(self
, verbose
=False, auto
=False):
88 self
.verbose
= verbose
90 def prompt(self
, prompt
, results
, default
):
95 result
= self
.do_prompt(prompt
, results
, default
)
99 class MercurialUI(UserInterface
):
100 def __init__(self
, ui
, *args
, **kwargs
):
101 super(MercurialUI
, self
).__init
__(*args
, **kwargs
)
104 def do_prompt(self
, prompt
, results
, default
):
105 return self
.ui
.prompt(prompt
, default
=default
)
107 def write(self
, string
):
108 self
.ui
.write(string
)
110 class StdioUI(UserInterface
):
111 def do_prompt(self
, prompt
, results
, default
):
112 return raw_input(prompt
) or default
114 def write(self
, string
):
115 sys
.stdout
.write(string
)
117 class Verifier(object):
118 def __init__(self
, ui
, repo
=None):
124 def __getattr__(self
, attr
):
125 if attr
in ('prompt', 'write'):
126 return getattr(self
.ui
, attr
)
130 wctx
= repo
.workingctx()
132 from mercurial
import context
133 wctx
= context
.workingctx(repo
)
139 def open(self
, filename
, mode
):
141 filename
= self
.repo
.wjoin(filename
)
144 f
= file(filename
, mode
)
146 print 'could not open file %s: %s' % (filename
, msg
)
151 def skip(self
, filename
):
152 return lang_type(filename
) not in self
.languages
154 def check(self
, filename
, regions
=all_regions
):
155 f
= self
.open(filename
, 'r')
158 for num
,line
in enumerate(f
):
159 if num
not in regions
:
161 if not self
.check_line(line
):
162 self
.write("invalid %s in %s:%d\n" % \
163 (self
.test_name
, filename
, num
+ 1))
165 self
.write(">>%s<<\n" % line
[-1])
169 def fix(self
, filename
, regions
=all_regions
):
170 f
= self
.open(filename
, 'r+')
177 for i
,line
in enumerate(lines
):
179 line
= self
.fix_line(line
)
184 def apply(self
, filename
, prompt
, regions
=all_regions
):
185 if not self
.skip(filename
):
186 errors
= self
.check(filename
, regions
)
188 if prompt(filename
, self
.fix
, regions
):
193 class Whitespace(Verifier
):
194 languages
= set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons'))
195 test_name
= 'whitespace'
196 def check_line(self
, line
):
197 match
= lead
.search(line
)
198 if match
and match
.group(1).find('\t') != -1:
201 match
= trail
.search(line
)
207 def fix_line(self
, line
):
208 if lead
.search(line
):
210 for i
,c
in enumerate(line
):
214 newline
+= ' ' * (tabsize
- len(newline
) % tabsize
)
221 return line
.rstrip() + '\n'
223 class SortedIncludes(Verifier
):
224 languages
= sort_includes
.default_languages
225 def __init__(self
, *args
, **kwargs
):
226 super(SortedIncludes
, self
).__init
__(*args
, **kwargs
)
227 self
.sort_includes
= sort_includes
.SortIncludes()
229 def check(self
, filename
, regions
=all_regions
):
230 f
= self
.open(filename
, 'r')
232 lines
= [ l
.rstrip('\n') for l
in f
.xreadlines() ]
233 old
= ''.join(line
+ '\n' for line
in lines
)
239 language
= lang_type(filename
, lines
[0])
240 sort_lines
= list(self
.sort_includes(lines
, filename
, language
))
241 new
= ''.join(line
+ '\n' for line
in sort_lines
)
243 mod
= modified_regions(old
, new
)
244 modified
= mod
& regions
247 self
.write("invalid sorting of includes in %s\n" % (filename
))
249 for start
, end
in modified
.regions
:
250 self
.write("bad region [%d, %d)\n" % (start
, end
))
255 def fix(self
, filename
, regions
=all_regions
):
256 f
= self
.open(filename
, 'r+')
259 lines
= [ l
.rstrip('\n') for l
in old
]
260 language
= lang_type(filename
, lines
[0])
261 sort_lines
= list(self
.sort_includes(lines
, filename
, language
))
262 new
= ''.join(line
+ '\n' for line
in sort_lines
)
267 for i
,line
in enumerate(sort_lines
):
273 tabs
= line
.count('\t')
280 count
+= tabsize
- count
% tabsize
286 class ValidationStats(object):
297 %d violations of lines over 79 chars. %d of which are 80 chars exactly.
298 %d cases of whitespace at the end of a line.
299 %d cases of tabs to indent.
300 %d bad parens after if/while/for.
301 %d carriage returns found.
302 ''' % (self
.toolong
, self
.toolong80
, self
.trailwhite
, self
.leadtabs
,
303 self
.badcontrol
, self
.cret
)
305 def __nonzero__(self
):
306 return self
.toolong
or self
.toolong80
or self
.leadtabs
or \
307 self
.trailwhite
or self
.badcontrol
or self
.cret
309 def validate(filename
, stats
, verbose
, exit_code
):
310 if lang_type(filename
) not in format_types
:
313 def msg(lineno
, line
, message
):
314 print '%s:%d>' % (filename
, lineno
+ 1), message
319 if exit_code
is not None:
323 f
= file(filename
, 'r')
326 print 'could not open file %s' % filename
330 for i
,line
in enumerate(f
):
331 line
= line
.rstrip('\n')
333 # no carriage returns
334 if line
.find('\r') != -1:
337 msg(i
, line
, 'carriage return found')
340 # lines max out at 79 chars
347 msg(i
, line
, 'line too long (%d chars)' % llen
)
350 # no tabs used to indent
351 match
= lead
.search(line
)
352 if match
and match
.group(1).find('\t') != -1:
355 msg(i
, line
, 'using tabs to indent')
358 # no trailing whitespace
359 if trail
.search(line
):
362 msg(i
, line
, 'trailing whitespace')
365 # for c++, exactly one space betwen if/while/for and (
367 match
= any_control
.search(line
)
368 if match
and not good_control
.search(line
):
369 stats
.badcontrol
+= 1
371 msg(i
, line
, 'improper spacing after %s' % match
.group(1))
374 def do_check_style(hgui
, repo
, *files
, **args
):
375 """check files for proper m5 style guidelines"""
376 from mercurial
import mdiff
, util
378 auto
= args
.get('auto', False)
381 ui
= MercurialUI(hgui
, hgui
.verbose
, auto
)
384 files
= frozenset(files
)
387 return files
and name
in files
389 def prompt(name
, func
, regions
=all_regions
):
390 result
= ui
.prompt("(a)bort, (i)gnore, or (f)ix?", 'aif', 'a')
394 func(repo
.wjoin(name
), regions
)
398 modified
, added
, removed
, deleted
, unknown
, ignore
, clean
= repo
.status()
400 whitespace
= Whitespace(ui
)
401 sorted_includes
= SortedIncludes(ui
)
406 fpath
= joinpath(repo
.root
, fname
)
408 if whitespace
.apply(fpath
, prompt
):
411 if sorted_includes
.apply(fpath
, prompt
):
415 wctx
= repo
.workingctx()
417 from mercurial
import context
418 wctx
= context
.workingctx(repo
)
420 for fname
in modified
:
424 fpath
= joinpath(repo
.root
, fname
)
425 regions
= modregions(wctx
, fname
)
427 if whitespace
.apply(fpath
, prompt
, regions
):
430 if sorted_includes
.apply(fpath
, prompt
, regions
):
435 def do_check_format(hgui
, repo
, **args
):
436 ui
= MercurialUI(hgui
, hgui
.verbose
, auto
)
438 modified
, added
, removed
, deleted
, unknown
, ignore
, clean
= repo
.status()
441 stats
= ValidationStats()
442 for f
in modified
+ added
:
443 validate(joinpath(repo
.root
, f
), stats
, verbose
, None)
447 result
= ui
.prompt("invalid formatting\n(i)gnore or (a)bort?",
454 def check_hook(hooktype
):
455 if hooktype
not in ('pretxncommit', 'pre-qrefresh'):
456 raise AttributeError, \
457 "This hook is not meant for %s" % hooktype
459 def check_style(ui
, repo
, hooktype
, **kwargs
):
464 return do_check_style(ui
, repo
, **args
)
467 traceback
.print_exc()
470 def check_format(ui
, repo
, hooktype
, **kwargs
):
475 return do_check_format(ui
, repo
, **args
)
478 traceback
.print_exc()
482 from mercurial
.i18n
import _
490 [ ('a', 'auto', False, _("automatically fix whitespace")) ],
491 _('hg m5style [-a] [FILE]...')),
495 _('hg m5format [FILE]...')),
498 if __name__
== '__main__':
501 progname
= sys
.argv
[0]
502 if len(sys
.argv
) < 2:
503 sys
.exit('usage: %s <command> [<command args>]' % progname
)
505 fixwhite_usage
= '%s fixwhite [-t <tabsize> ] <path> [...] \n' % progname
506 chkformat_usage
= '%s chkformat <path> [...] \n' % progname
507 chkwhite_usage
= '%s chkwhite <path> [...] \n' % progname
509 command
= sys
.argv
[1]
510 if command
== 'fixwhite':
512 usage
= fixwhite_usage
513 elif command
== 'chkwhite':
515 usage
= chkwhite_usage
516 elif command
== 'chkformat':
518 usage
= chkformat_usage
520 sys
.exit(fixwhite_usage
+ chkwhite_usage
+ chkformat_usage
)
522 opts
, args
= getopt
.getopt(sys
.argv
[2:], flags
)
534 if command
== 'fixwhite':
535 for filename
in args
:
536 fixwhite(filename
, tabsize
)
537 elif command
== 'chkwhite':
538 for filename
in args
:
539 for line
,num
in checkwhite(filename
):
540 print 'invalid whitespace: %s:%d' % (filename
, num
)
542 print '>>%s<<' % line
[:-1]
543 elif command
== 'chkformat':
544 stats
= ValidationStats()
545 for filename
in args
:
546 validate(filename
, stats
=stats
, verbose
=verbose
, exit_code
=code
)
551 sys
.exit("command '%s' not found" % command
)