style.py (10692:ab81a0feab55) style.py (11319:7ca84595249c)
1#! /usr/bin/env python
2# Copyright (c) 2014 ARM Limited
3# All rights reserved
4#
5# The license below extends only to copyright in the software and shall
6# not be construed as granting a license to any other intellectual
7# property including but not limited to intellectual property relating
8# to a hardware implementation of the functionality of the software
9# licensed hereunder. You may use the software subject to the license
10# terms below provided that you ensure that this notice is replicated
11# unmodified and in its entirety in all distributions of the software,
12# modified or unmodified, in source code or in binary form.
13#
14# Copyright (c) 2006 The Regents of The University of Michigan
15# Copyright (c) 2007,2011 The Hewlett-Packard Development Company
1#! /usr/bin/env python
2# Copyright (c) 2014 ARM Limited
3# All rights reserved
4#
5# The license below extends only to copyright in the software and shall
6# not be construed as granting a license to any other intellectual
7# property including but not limited to intellectual property relating
8# to a hardware implementation of the functionality of the software
9# licensed hereunder. You may use the software subject to the license
10# terms below provided that you ensure that this notice is replicated
11# unmodified and in its entirety in all distributions of the software,
12# modified or unmodified, in source code or in binary form.
13#
14# Copyright (c) 2006 The Regents of The University of Michigan
15# Copyright (c) 2007,2011 The Hewlett-Packard Development Company
16# Copyright (c) 2016 Advanced Micro Devices, Inc.
16# All rights reserved.
17#
18# Redistribution and use in source and binary forms, with or without
19# modification, are permitted provided that the following conditions are
20# met: redistributions of source code must retain the above copyright
21# notice, this list of conditions and the following disclaimer;
22# redistributions in binary form must reproduce the above copyright
23# notice, this list of conditions and the following disclaimer in the
24# documentation and/or other materials provided with the distribution;
25# neither the name of the copyright holders nor the names of its
26# contributors may be used to endorse or promote products derived from
27# this software without specific prior written permission.
28#
29# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40#
41# Authors: Nathan Binkert
17# All rights reserved.
18#
19# Redistribution and use in source and binary forms, with or without
20# modification, are permitted provided that the following conditions are
21# met: redistributions of source code must retain the above copyright
22# notice, this list of conditions and the following disclaimer;
23# redistributions in binary form must reproduce the above copyright
24# notice, this list of conditions and the following disclaimer in the
25# documentation and/or other materials provided with the distribution;
26# neither the name of the copyright holders nor the names of its
27# contributors may be used to endorse or promote products derived from
28# this software without specific prior written permission.
29#
30# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
31# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
32# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
33# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
34# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
36# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
37# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
38# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
39# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
40# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41#
42# Authors: Nathan Binkert
43# Steve Reinhardt
42
43import heapq
44import os
45import re
46import sys
47
48from os.path import dirname, join as joinpath
49from itertools import count
50from mercurial import bdiff, mdiff, commands
51
52current_dir = dirname(__file__)
53sys.path.insert(0, current_dir)
54sys.path.insert(1, joinpath(dirname(current_dir), 'src', 'python'))
55
56from m5.util import neg_inf, pos_inf, Region, Regions
57import sort_includes
58from file_types import lang_type
59
60all_regions = Regions(Region(neg_inf, pos_inf))
61
62tabsize = 8
63lead = re.compile(r'^([ \t]+)')
64trail = re.compile(r'([ \t]+)$')
44
45import heapq
46import os
47import re
48import sys
49
50from os.path import dirname, join as joinpath
51from itertools import count
52from mercurial import bdiff, mdiff, commands
53
54current_dir = dirname(__file__)
55sys.path.insert(0, current_dir)
56sys.path.insert(1, joinpath(dirname(current_dir), 'src', 'python'))
57
58from m5.util import neg_inf, pos_inf, Region, Regions
59import sort_includes
60from file_types import lang_type
61
62all_regions = Regions(Region(neg_inf, pos_inf))
63
64tabsize = 8
65lead = re.compile(r'^([ \t]+)')
66trail = re.compile(r'([ \t]+)$')
65any_control = re.compile(r'\b(if|while|for)[ \t]*[(]')
66good_control = re.compile(r'\b(if|while|for) [(]')
67any_control = re.compile(r'\b(if|while|for)([ \t]*)\(')
67
68format_types = set(('C', 'C++'))
69
70
71def re_ignore(expr):
72 """Helper function to create regular expression ignore file
73 matcher functions"""
74
75 rex = re.compile(expr)
76 def match_re(fname):
77 return rex.match(fname)
78 return match_re
79
80# This list contains a list of functions that are called to determine
81# if a file should be excluded from the style matching rules or
82# not. The functions are called with the file name relative to the
83# repository root (without a leading slash) as their argument. A file
84# is excluded if any function in the list returns true.
85style_ignores = [
86 # Ignore external projects as they are unlikely to follow the gem5
87 # coding convention.
88 re_ignore("^ext/"),
89]
90
91def check_ignores(fname):
92 """Check if a file name matches any of the ignore rules"""
93
94 for rule in style_ignores:
95 if rule(fname):
96 return True
97
98 return False
99
100
101def modified_regions(old_data, new_data):
102 regions = Regions()
103 beg = None
104 for pbeg, pend, fbeg, fend in bdiff.blocks(old_data, new_data):
105 if beg is not None and beg != fbeg:
106 regions.append(beg, fbeg)
107 beg = fend
108 return regions
109
110def modregions(wctx, fname):
111 fctx = wctx.filectx(fname)
112 pctx = fctx.parents()
113
114 file_data = fctx.data()
115 lines = mdiff.splitnewlines(file_data)
116 if len(pctx) in (1, 2):
117 mod_regions = modified_regions(pctx[0].data(), file_data)
118 if len(pctx) == 2:
119 m2 = modified_regions(pctx[1].data(), file_data)
120 # only the lines that are new in both
121 mod_regions &= m2
122 else:
123 mod_regions = Regions()
124 mod_regions.append(0, len(lines))
125
126 return mod_regions
127
128class UserInterface(object):
129 def __init__(self, verbose=False):
130 self.verbose = verbose
131
132 def prompt(self, prompt, results, default):
133 while True:
134 result = self.do_prompt(prompt, results, default)
135 if result in results:
136 return result
137
138class MercurialUI(UserInterface):
139 def __init__(self, ui, *args, **kwargs):
140 super(MercurialUI, self).__init__(*args, **kwargs)
141 self.ui = ui
142
143 def do_prompt(self, prompt, results, default):
144 return self.ui.prompt(prompt, default=default)
145
146 def write(self, string):
147 self.ui.write(string)
148
149class StdioUI(UserInterface):
150 def do_prompt(self, prompt, results, default):
151 return raw_input(prompt) or default
152
153 def write(self, string):
154 sys.stdout.write(string)
155
68
69format_types = set(('C', 'C++'))
70
71
72def re_ignore(expr):
73 """Helper function to create regular expression ignore file
74 matcher functions"""
75
76 rex = re.compile(expr)
77 def match_re(fname):
78 return rex.match(fname)
79 return match_re
80
81# This list contains a list of functions that are called to determine
82# if a file should be excluded from the style matching rules or
83# not. The functions are called with the file name relative to the
84# repository root (without a leading slash) as their argument. A file
85# is excluded if any function in the list returns true.
86style_ignores = [
87 # Ignore external projects as they are unlikely to follow the gem5
88 # coding convention.
89 re_ignore("^ext/"),
90]
91
92def check_ignores(fname):
93 """Check if a file name matches any of the ignore rules"""
94
95 for rule in style_ignores:
96 if rule(fname):
97 return True
98
99 return False
100
101
102def modified_regions(old_data, new_data):
103 regions = Regions()
104 beg = None
105 for pbeg, pend, fbeg, fend in bdiff.blocks(old_data, new_data):
106 if beg is not None and beg != fbeg:
107 regions.append(beg, fbeg)
108 beg = fend
109 return regions
110
111def modregions(wctx, fname):
112 fctx = wctx.filectx(fname)
113 pctx = fctx.parents()
114
115 file_data = fctx.data()
116 lines = mdiff.splitnewlines(file_data)
117 if len(pctx) in (1, 2):
118 mod_regions = modified_regions(pctx[0].data(), file_data)
119 if len(pctx) == 2:
120 m2 = modified_regions(pctx[1].data(), file_data)
121 # only the lines that are new in both
122 mod_regions &= m2
123 else:
124 mod_regions = Regions()
125 mod_regions.append(0, len(lines))
126
127 return mod_regions
128
129class UserInterface(object):
130 def __init__(self, verbose=False):
131 self.verbose = verbose
132
133 def prompt(self, prompt, results, default):
134 while True:
135 result = self.do_prompt(prompt, results, default)
136 if result in results:
137 return result
138
139class MercurialUI(UserInterface):
140 def __init__(self, ui, *args, **kwargs):
141 super(MercurialUI, self).__init__(*args, **kwargs)
142 self.ui = ui
143
144 def do_prompt(self, prompt, results, default):
145 return self.ui.prompt(prompt, default=default)
146
147 def write(self, string):
148 self.ui.write(string)
149
150class StdioUI(UserInterface):
151 def do_prompt(self, prompt, results, default):
152 return raw_input(prompt) or default
153
154 def write(self, string):
155 sys.stdout.write(string)
156
157
156class Verifier(object):
158class Verifier(object):
157 def __init__(self, ui, repo):
159 """Base class for style verifier objects
160
161 Subclasses must define these class attributes:
162 languages = set of strings identifying applicable languages
163 test_name = long descriptive name of test, will be used in
164 messages such as "error in <foo>" or "invalid <foo>"
165 opt_name = short name used to generate command-line options to
166 control the test (--fix-<foo>, --ignore-<foo>, etc.)
167 """
168
169 def __init__(self, ui, repo, opts):
158 self.ui = ui
159 self.repo = repo
170 self.ui = ui
171 self.repo = repo
172 # opt_name must be defined as a class attribute of derived classes.
173 # Check test-specific opts first as these have precedence.
174 self.opt_fix = opts.get('fix_' + self.opt_name, False)
175 self.opt_ignore = opts.get('ignore_' + self.opt_name, False)
176 self.opt_skip = opts.get('skip_' + self.opt_name, False)
177 # If no test-specific opts were set, then set based on "-all" opts.
178 if not (self.opt_fix or self.opt_ignore or self.opt_skip):
179 self.opt_fix = opts.get('fix_all', False)
180 self.opt_ignore = opts.get('ignore_all', False)
181 self.opt_skip = opts.get('skip_all', False)
160
161 def __getattr__(self, attr):
162 if attr in ('prompt', 'write'):
163 return getattr(self.ui, attr)
164
165 if attr == 'wctx':
166 try:
167 wctx = repo.workingctx()
168 except:
169 from mercurial import context
170 wctx = context.workingctx(repo)
171 self.wctx = wctx
172 return wctx
173
174 raise AttributeError
175
176 def open(self, filename, mode):
177 filename = self.repo.wjoin(filename)
178
179 try:
180 f = file(filename, mode)
181 except OSError, msg:
182 print 'could not open file %s: %s' % (filename, msg)
183 return None
184
185 return f
186
187 def skip(self, filename):
188 filename = self.repo.wjoin(filename)
189
190 # We never want to handle symlinks, so always skip them: If the location
191 # pointed to is a directory, skip it. If the location is a file inside
192 # the gem5 directory, it will be checked as a file, so symlink can be
193 # skipped. If the location is a file outside gem5, we don't want to
194 # check it anyway.
195 if os.path.islink(filename):
196 return True
197 return lang_type(filename) not in self.languages
198
199 def check(self, filename, regions=all_regions):
182
183 def __getattr__(self, attr):
184 if attr in ('prompt', 'write'):
185 return getattr(self.ui, attr)
186
187 if attr == 'wctx':
188 try:
189 wctx = repo.workingctx()
190 except:
191 from mercurial import context
192 wctx = context.workingctx(repo)
193 self.wctx = wctx
194 return wctx
195
196 raise AttributeError
197
198 def open(self, filename, mode):
199 filename = self.repo.wjoin(filename)
200
201 try:
202 f = file(filename, mode)
203 except OSError, msg:
204 print 'could not open file %s: %s' % (filename, msg)
205 return None
206
207 return f
208
209 def skip(self, filename):
210 filename = self.repo.wjoin(filename)
211
212 # We never want to handle symlinks, so always skip them: If the location
213 # pointed to is a directory, skip it. If the location is a file inside
214 # the gem5 directory, it will be checked as a file, so symlink can be
215 # skipped. If the location is a file outside gem5, we don't want to
216 # check it anyway.
217 if os.path.islink(filename):
218 return True
219 return lang_type(filename) not in self.languages
220
221 def check(self, filename, regions=all_regions):
222 """Check specified regions of file 'filename'.
223
224 Line-by-line checks can simply provide a check_line() method
225 that returns True if the line is OK and False if it has an
226 error. Verifiers that need a multi-line view (like
227 SortedIncludes) must override this entire function.
228
229 Returns a count of errors (0 if none), though actual non-zero
230 count value is not currently used anywhere.
231 """
232
200 f = self.open(filename, 'r')
201
202 errors = 0
203 for num,line in enumerate(f):
204 if num not in regions:
205 continue
206 if not self.check_line(line):
207 self.write("invalid %s in %s:%d\n" % \
233 f = self.open(filename, 'r')
234
235 errors = 0
236 for num,line in enumerate(f):
237 if num not in regions:
238 continue
239 if not self.check_line(line):
240 self.write("invalid %s in %s:%d\n" % \
208 (self.test_name, filename, num + 1))
241 (self.test_name, filename, num + 1))
209 if self.ui.verbose:
242 if self.ui.verbose:
210 self.write(">>%s<<\n" % line[-1])
243 self.write(">>%s<<\n" % line[:-1])
211 errors += 1
212 return errors
213
214 def fix(self, filename, regions=all_regions):
244 errors += 1
245 return errors
246
247 def fix(self, filename, regions=all_regions):
248 """Fix specified regions of file 'filename'.
249
250 Line-by-line fixes can simply provide a fix_line() method that
251 returns the fixed line. Verifiers that need a multi-line view
252 (like SortedIncludes) must override this entire function.
253 """
254
215 f = self.open(filename, 'r+')
216
217 lines = list(f)
218
219 f.seek(0)
220 f.truncate()
221
222 for i,line in enumerate(lines):
223 if i in regions:
224 line = self.fix_line(line)
225
226 f.write(line)
227 f.close()
228
255 f = self.open(filename, 'r+')
256
257 lines = list(f)
258
259 f.seek(0)
260 f.truncate()
261
262 for i,line in enumerate(lines):
263 if i in regions:
264 line = self.fix_line(line)
265
266 f.write(line)
267 f.close()
268
229 def apply(self, filename, prompt, regions=all_regions):
230 if not self.skip(filename):
269
270 def apply(self, filename, regions=all_regions):
271 """Possibly apply to specified regions of file 'filename'.
272
273 Verifier is skipped if --skip-<test> option was provided or if
274 file is not of an applicable type. Otherwise file is checked
275 and error messages printed. Errors are fixed or ignored if
276 the corresponding --fix-<test> or --ignore-<test> options were
277 provided. If neither, the user is prompted for an action.
278
279 Returns True to abort, False otherwise.
280 """
281 if not (self.opt_skip or self.skip(filename)):
231 errors = self.check(filename, regions)
282 errors = self.check(filename, regions)
232 if errors:
233 if prompt(filename, self.fix, regions):
234 return True
283 if errors and not self.opt_ignore:
284 if self.opt_fix:
285 self.fix(filename, regions)
286 else:
287 result = self.ui.prompt("(a)bort, (i)gnore, or (f)ix?",
288 'aif', 'a')
289 if result == 'f':
290 self.fix(filename, regions)
291 elif result == 'a':
292 return True # abort
293
235 return False
236
237
238class Whitespace(Verifier):
294 return False
295
296
297class Whitespace(Verifier):
298 """Check whitespace.
299
300 Specifically:
301 - No tabs used for indent
302 - No trailing whitespace
303 """
304
239 languages = set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons'))
240 test_name = 'whitespace'
305 languages = set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons'))
306 test_name = 'whitespace'
307 opt_name = 'white'
308
241 def check_line(self, line):
242 match = lead.search(line)
243 if match and match.group(1).find('\t') != -1:
244 return False
245
246 match = trail.search(line)
247 if match:
248 return False
249
250 return True
251
252 def fix_line(self, line):
253 if lead.search(line):
254 newline = ''
255 for i,c in enumerate(line):
256 if c == ' ':
257 newline += ' '
258 elif c == '\t':
259 newline += ' ' * (tabsize - len(newline) % tabsize)
260 else:
261 newline += line[i:]
262 break
263
264 line = newline
265
266 return line.rstrip() + '\n'
267
309 def check_line(self, line):
310 match = lead.search(line)
311 if match and match.group(1).find('\t') != -1:
312 return False
313
314 match = trail.search(line)
315 if match:
316 return False
317
318 return True
319
320 def fix_line(self, line):
321 if lead.search(line):
322 newline = ''
323 for i,c in enumerate(line):
324 if c == ' ':
325 newline += ' '
326 elif c == '\t':
327 newline += ' ' * (tabsize - len(newline) % tabsize)
328 else:
329 newline += line[i:]
330 break
331
332 line = newline
333
334 return line.rstrip() + '\n'
335
336
337class ControlSpace(Verifier):
338 """Check for exactly one space after if/while/for"""
339
340 languages = set(('C', 'C++'))
341 test_name = 'spacing after if/while/for'
342 opt_name = 'control'
343
344 def check_line(self, line):
345 match = any_control.search(line)
346 return not (match and match.group(2) != " ")
347
348 def fix_line(self, line):
349 new_line = any_control.sub(r'\1 (', line)
350 return new_line
351
352
268class SortedIncludes(Verifier):
353class SortedIncludes(Verifier):
354 """Check for proper sorting of include statements"""
355
269 languages = sort_includes.default_languages
356 languages = sort_includes.default_languages
357 test_name = 'include file order'
358 opt_name = 'include'
359
270 def __init__(self, *args, **kwargs):
271 super(SortedIncludes, self).__init__(*args, **kwargs)
272 self.sort_includes = sort_includes.SortIncludes()
273
274 def check(self, filename, regions=all_regions):
275 f = self.open(filename, 'r')
276
277 lines = [ l.rstrip('\n') for l in f.xreadlines() ]
278 old = ''.join(line + '\n' for line in lines)
279 f.close()
280
281 if len(lines) == 0:
282 return 0
283
284 language = lang_type(filename, lines[0])
285 sort_lines = list(self.sort_includes(lines, filename, language))
286 new = ''.join(line + '\n' for line in sort_lines)
287
288 mod = modified_regions(old, new)
289 modified = mod & regions
290
291 if modified:
292 self.write("invalid sorting of includes in %s\n" % (filename))
293 if self.ui.verbose:
294 for start, end in modified.regions:
295 self.write("bad region [%d, %d)\n" % (start, end))
296 return 1
297
298 return 0
299
300 def fix(self, filename, regions=all_regions):
301 f = self.open(filename, 'r+')
302
303 old = f.readlines()
304 lines = [ l.rstrip('\n') for l in old ]
305 language = lang_type(filename, lines[0])
306 sort_lines = list(self.sort_includes(lines, filename, language))
307 new = ''.join(line + '\n' for line in sort_lines)
308
309 f.seek(0)
310 f.truncate()
311
312 for i,line in enumerate(sort_lines):
313 f.write(line)
314 f.write('\n')
315 f.close()
316
360 def __init__(self, *args, **kwargs):
361 super(SortedIncludes, self).__init__(*args, **kwargs)
362 self.sort_includes = sort_includes.SortIncludes()
363
364 def check(self, filename, regions=all_regions):
365 f = self.open(filename, 'r')
366
367 lines = [ l.rstrip('\n') for l in f.xreadlines() ]
368 old = ''.join(line + '\n' for line in lines)
369 f.close()
370
371 if len(lines) == 0:
372 return 0
373
374 language = lang_type(filename, lines[0])
375 sort_lines = list(self.sort_includes(lines, filename, language))
376 new = ''.join(line + '\n' for line in sort_lines)
377
378 mod = modified_regions(old, new)
379 modified = mod & regions
380
381 if modified:
382 self.write("invalid sorting of includes in %s\n" % (filename))
383 if self.ui.verbose:
384 for start, end in modified.regions:
385 self.write("bad region [%d, %d)\n" % (start, end))
386 return 1
387
388 return 0
389
390 def fix(self, filename, regions=all_regions):
391 f = self.open(filename, 'r+')
392
393 old = f.readlines()
394 lines = [ l.rstrip('\n') for l in old ]
395 language = lang_type(filename, lines[0])
396 sort_lines = list(self.sort_includes(lines, filename, language))
397 new = ''.join(line + '\n' for line in sort_lines)
398
399 f.seek(0)
400 f.truncate()
401
402 for i,line in enumerate(sort_lines):
403 f.write(line)
404 f.write('\n')
405 f.close()
406
407# list of all verifier classes
408all_verifiers = [
409 Whitespace,
410 ControlSpace,
411 SortedIncludes
412]
413
317def linelen(line):
318 tabs = line.count('\t')
319 if not tabs:
320 return len(line)
321
322 count = 0
323 for c in line:
324 if c == '\t':
325 count += tabsize - count % tabsize
326 else:
327 count += 1
328
329 return count
330
331class ValidationStats(object):
332 def __init__(self):
333 self.toolong = 0
334 self.toolong80 = 0
335 self.leadtabs = 0
336 self.trailwhite = 0
337 self.badcontrol = 0
338 self.cret = 0
339
340 def dump(self):
341 print '''\
342%d violations of lines over 79 chars. %d of which are 80 chars exactly.
343%d cases of whitespace at the end of a line.
344%d cases of tabs to indent.
345%d bad parens after if/while/for.
346%d carriage returns found.
347''' % (self.toolong, self.toolong80, self.trailwhite, self.leadtabs,
348 self.badcontrol, self.cret)
349
350 def __nonzero__(self):
351 return self.toolong or self.toolong80 or self.leadtabs or \
352 self.trailwhite or self.badcontrol or self.cret
353
354def validate(filename, stats, verbose, exit_code):
355 lang = lang_type(filename)
356 if lang not in format_types:
357 return
358
359 def msg(lineno, line, message):
360 print '%s:%d>' % (filename, lineno + 1), message
361 if verbose > 2:
362 print line
363
364 def bad():
365 if exit_code is not None:
366 sys.exit(exit_code)
367
368 try:
369 f = file(filename, 'r')
370 except OSError:
371 if verbose > 0:
372 print 'could not open file %s' % filename
373 bad()
374 return
375
376 for i,line in enumerate(f):
377 line = line.rstrip('\n')
378
379 # no carriage returns
380 if line.find('\r') != -1:
381 self.cret += 1
382 if verbose > 1:
383 msg(i, line, 'carriage return found')
384 bad()
385
386 # lines max out at 79 chars
387 llen = linelen(line)
388 if llen > 79:
389 stats.toolong += 1
390 if llen == 80:
391 stats.toolong80 += 1
392 if verbose > 1:
393 msg(i, line, 'line too long (%d chars)' % llen)
394 bad()
395
396 # no tabs used to indent
397 match = lead.search(line)
398 if match and match.group(1).find('\t') != -1:
399 stats.leadtabs += 1
400 if verbose > 1:
401 msg(i, line, 'using tabs to indent')
402 bad()
403
404 # no trailing whitespace
405 if trail.search(line):
406 stats.trailwhite +=1
407 if verbose > 1:
408 msg(i, line, 'trailing whitespace')
409 bad()
410
411 # for c++, exactly one space betwen if/while/for and (
412 if lang == 'C++':
413 match = any_control.search(line)
414def linelen(line):
415 tabs = line.count('\t')
416 if not tabs:
417 return len(line)
418
419 count = 0
420 for c in line:
421 if c == '\t':
422 count += tabsize - count % tabsize
423 else:
424 count += 1
425
426 return count
427
428class ValidationStats(object):
429 def __init__(self):
430 self.toolong = 0
431 self.toolong80 = 0
432 self.leadtabs = 0
433 self.trailwhite = 0
434 self.badcontrol = 0
435 self.cret = 0
436
437 def dump(self):
438 print '''\
439%d violations of lines over 79 chars. %d of which are 80 chars exactly.
440%d cases of whitespace at the end of a line.
441%d cases of tabs to indent.
442%d bad parens after if/while/for.
443%d carriage returns found.
444''' % (self.toolong, self.toolong80, self.trailwhite, self.leadtabs,
445 self.badcontrol, self.cret)
446
447 def __nonzero__(self):
448 return self.toolong or self.toolong80 or self.leadtabs or \
449 self.trailwhite or self.badcontrol or self.cret
450
451def validate(filename, stats, verbose, exit_code):
452 lang = lang_type(filename)
453 if lang not in format_types:
454 return
455
456 def msg(lineno, line, message):
457 print '%s:%d>' % (filename, lineno + 1), message
458 if verbose > 2:
459 print line
460
461 def bad():
462 if exit_code is not None:
463 sys.exit(exit_code)
464
465 try:
466 f = file(filename, 'r')
467 except OSError:
468 if verbose > 0:
469 print 'could not open file %s' % filename
470 bad()
471 return
472
473 for i,line in enumerate(f):
474 line = line.rstrip('\n')
475
476 # no carriage returns
477 if line.find('\r') != -1:
478 self.cret += 1
479 if verbose > 1:
480 msg(i, line, 'carriage return found')
481 bad()
482
483 # lines max out at 79 chars
484 llen = linelen(line)
485 if llen > 79:
486 stats.toolong += 1
487 if llen == 80:
488 stats.toolong80 += 1
489 if verbose > 1:
490 msg(i, line, 'line too long (%d chars)' % llen)
491 bad()
492
493 # no tabs used to indent
494 match = lead.search(line)
495 if match and match.group(1).find('\t') != -1:
496 stats.leadtabs += 1
497 if verbose > 1:
498 msg(i, line, 'using tabs to indent')
499 bad()
500
501 # no trailing whitespace
502 if trail.search(line):
503 stats.trailwhite +=1
504 if verbose > 1:
505 msg(i, line, 'trailing whitespace')
506 bad()
507
508 # for c++, exactly one space betwen if/while/for and (
509 if lang == 'C++':
510 match = any_control.search(line)
414 if match and not good_control.search(line):
511 if match and match.group(2) != " ":
415 stats.badcontrol += 1
416 if verbose > 1:
417 msg(i, line, 'improper spacing after %s' % match.group(1))
418 bad()
419
420
421def _modified_regions(repo, patterns, **kwargs):
422 opt_all = kwargs.get('all', False)
423 opt_no_ignore = kwargs.get('no_ignore', False)
424
425 # Import the match (repository file name matching helper)
426 # function. Different versions of Mercurial keep it in different
427 # modules and implement them differently.
428 try:
429 from mercurial import scmutil
430 m = scmutil.match(repo[None], patterns, kwargs)
431 except ImportError:
432 from mercurial import cmdutil
433 m = cmdutil.match(repo, patterns, kwargs)
434
435 modified, added, removed, deleted, unknown, ignore, clean = \
436 repo.status(match=m, clean=opt_all)
437
438 if not opt_all:
439 try:
440 wctx = repo.workingctx()
441 except:
442 from mercurial import context
443 wctx = context.workingctx(repo)
444
445 files = [ (fn, all_regions) for fn in added ] + \
446 [ (fn, modregions(wctx, fn)) for fn in modified ]
447 else:
448 files = [ (fn, all_regions) for fn in added + modified + clean ]
449
450 for fname, mod_regions in files:
451 if opt_no_ignore or not check_ignores(fname):
452 yield fname, mod_regions
453
454
455def do_check_style(hgui, repo, *pats, **opts):
456 """check files for proper m5 style guidelines
457
458 Without an argument, checks all modified and added files for gem5
459 coding style violations. A list of files can be specified to limit
460 the checker to a subset of the repository. The style rules are
461 normally applied on a diff of the repository state (i.e., added
462 files are checked in their entirety while only modifications of
463 modified files are checked).
464
465 The --all option can be specified to include clean files and check
466 modified files in their entirety.
512 stats.badcontrol += 1
513 if verbose > 1:
514 msg(i, line, 'improper spacing after %s' % match.group(1))
515 bad()
516
517
518def _modified_regions(repo, patterns, **kwargs):
519 opt_all = kwargs.get('all', False)
520 opt_no_ignore = kwargs.get('no_ignore', False)
521
522 # Import the match (repository file name matching helper)
523 # function. Different versions of Mercurial keep it in different
524 # modules and implement them differently.
525 try:
526 from mercurial import scmutil
527 m = scmutil.match(repo[None], patterns, kwargs)
528 except ImportError:
529 from mercurial import cmdutil
530 m = cmdutil.match(repo, patterns, kwargs)
531
532 modified, added, removed, deleted, unknown, ignore, clean = \
533 repo.status(match=m, clean=opt_all)
534
535 if not opt_all:
536 try:
537 wctx = repo.workingctx()
538 except:
539 from mercurial import context
540 wctx = context.workingctx(repo)
541
542 files = [ (fn, all_regions) for fn in added ] + \
543 [ (fn, modregions(wctx, fn)) for fn in modified ]
544 else:
545 files = [ (fn, all_regions) for fn in added + modified + clean ]
546
547 for fname, mod_regions in files:
548 if opt_no_ignore or not check_ignores(fname):
549 yield fname, mod_regions
550
551
552def do_check_style(hgui, repo, *pats, **opts):
553 """check files for proper m5 style guidelines
554
555 Without an argument, checks all modified and added files for gem5
556 coding style violations. A list of files can be specified to limit
557 the checker to a subset of the repository. The style rules are
558 normally applied on a diff of the repository state (i.e., added
559 files are checked in their entirety while only modifications of
560 modified files are checked).
561
562 The --all option can be specified to include clean files and check
563 modified files in their entirety.
467 """
468 opt_fix_all = opts.get('fix_all', False)
469 if not opt_fix_all:
470 opt_fix_white = opts.get('fix_white', False)
471 opt_fix_include = opts.get('fix_include', False)
472 else:
473 opt_fix_white = True
474 opt_fix_include = True
475
564
476 ui = MercurialUI(hgui, verbose=hgui.verbose)
565 The --fix-<check>, --ignore-<check>, and --skip-<check> options
566 can be used to control individual style checks:
477
567
478 def prompt(name, func, regions=all_regions):
479 result = ui.prompt("(a)bort, (i)gnore, or (f)ix?", 'aif', 'a')
480 if result == 'a':
481 return True
482 elif result == 'f':
483 func(name, regions)
568 --fix-<check> will perform the check and automatically attempt to
569 fix sny style error (printing a warning if unsuccessful)
484
570
485 return False
571 --ignore-<check> will perform the check but ignore any errors
572 found (other than printing a message for each)
486
573
487 def no_prompt(name, func, regions=all_regions):
488 func(name, regions)
489 return False
574 --skip-<check> will skip performing the check entirely
490
575
491 prompt_white = prompt if not opt_fix_white else no_prompt
492 prompt_include = prompt if not opt_fix_include else no_prompt
576 If none of these options are given, all checks will be performed
577 and the user will be prompted on how to handle each error.
493
578
494 whitespace = Whitespace(ui, repo)
495 sorted_includes = SortedIncludes(ui, repo)
496 for fname, mod_regions in _modified_regions(repo, pats, **opts):
497 if whitespace.apply(fname, prompt_white, mod_regions):
498 return True
579 --fix-all, --ignore-all, and --skip-all are equivalent to specifying
580 --fix-<check>, --ignore-<check>, or --skip-<check> for all checks,
581 respectively. However, option settings for specific checks take
582 precedence. Thus --skip-all --fix-white can be used to skip every
583 check other than whitespace errors, which will be checked and
584 automatically fixed.
499
585
500 if sorted_includes.apply(fname, prompt_include, mod_regions):
501 return True
586 The -v/--verbose flag will display the offending line(s) as well
587 as their location.
588 """
502
589
590 ui = MercurialUI(hgui, verbose=hgui.verbose)
591
592 # instantiate varifier objects
593 verifiers = [v(ui, repo, opts) for v in all_verifiers]
594
595 for fname, mod_regions in _modified_regions(repo, pats, **opts):
596 for verifier in verifiers:
597 if verifier.apply(fname, mod_regions):
598 return True
599
503 return False
504
505def do_check_format(hgui, repo, *pats, **opts):
506 """check files for gem5 code formatting violations
507
508 Without an argument, checks all modified and added files for gem5
509 code formatting violations. A list of files can be specified to
510 limit the checker to a subset of the repository. The style rules
511 are normally applied on a diff of the repository state (i.e.,
512 added files are checked in their entirety while only modifications
513 of modified files are checked).
514
515 The --all option can be specified to include clean files and check
516 modified files in their entirety.
517 """
518 ui = MercurialUI(hgui, hgui.verbose)
519
520 verbose = 0
521 for fname, mod_regions in _modified_regions(repo, pats, **opts):
522 stats = ValidationStats()
523 validate(joinpath(repo.root, fname), stats, verbose, None)
524 if stats:
525 print "%s:" % fname
526 stats.dump()
527 result = ui.prompt("invalid formatting\n(i)gnore or (a)bort?",
528 'ai', 'a')
529 if result == 'a':
530 return True
531
532 return False
533
534def check_hook(hooktype):
535 if hooktype not in ('pretxncommit', 'pre-qrefresh'):
536 raise AttributeError, \
537 "This hook is not meant for %s" % hooktype
538
600 return False
601
602def do_check_format(hgui, repo, *pats, **opts):
603 """check files for gem5 code formatting violations
604
605 Without an argument, checks all modified and added files for gem5
606 code formatting violations. A list of files can be specified to
607 limit the checker to a subset of the repository. The style rules
608 are normally applied on a diff of the repository state (i.e.,
609 added files are checked in their entirety while only modifications
610 of modified files are checked).
611
612 The --all option can be specified to include clean files and check
613 modified files in their entirety.
614 """
615 ui = MercurialUI(hgui, hgui.verbose)
616
617 verbose = 0
618 for fname, mod_regions in _modified_regions(repo, pats, **opts):
619 stats = ValidationStats()
620 validate(joinpath(repo.root, fname), stats, verbose, None)
621 if stats:
622 print "%s:" % fname
623 stats.dump()
624 result = ui.prompt("invalid formatting\n(i)gnore or (a)bort?",
625 'ai', 'a')
626 if result == 'a':
627 return True
628
629 return False
630
631def check_hook(hooktype):
632 if hooktype not in ('pretxncommit', 'pre-qrefresh'):
633 raise AttributeError, \
634 "This hook is not meant for %s" % hooktype
635
636# This function provides a hook that is called before transaction
637# commit and on qrefresh
539def check_style(ui, repo, hooktype, **kwargs):
540 check_hook(hooktype)
541 args = {}
542
543 try:
544 return do_check_style(ui, repo, **args)
545 except Exception, e:
546 import traceback
547 traceback.print_exc()
548 return True
549
550def check_format(ui, repo, hooktype, **kwargs):
551 check_hook(hooktype)
552 args = {}
553
554 try:
555 return do_check_format(ui, repo, **args)
556 except Exception, e:
557 import traceback
558 traceback.print_exc()
559 return True
560
561try:
562 from mercurial.i18n import _
563except ImportError:
564 def _(arg):
565 return arg
566
567_common_region_options = [
568 ('a', 'all', False,
569 _("include clean files and unmodified parts of modified files")),
570 ('', 'no-ignore', False, _("ignore the style ignore list")),
571 ]
572
638def check_style(ui, repo, hooktype, **kwargs):
639 check_hook(hooktype)
640 args = {}
641
642 try:
643 return do_check_style(ui, repo, **args)
644 except Exception, e:
645 import traceback
646 traceback.print_exc()
647 return True
648
649def check_format(ui, repo, hooktype, **kwargs):
650 check_hook(hooktype)
651 args = {}
652
653 try:
654 return do_check_format(ui, repo, **args)
655 except Exception, e:
656 import traceback
657 traceback.print_exc()
658 return True
659
660try:
661 from mercurial.i18n import _
662except ImportError:
663 def _(arg):
664 return arg
665
666_common_region_options = [
667 ('a', 'all', False,
668 _("include clean files and unmodified parts of modified files")),
669 ('', 'no-ignore', False, _("ignore the style ignore list")),
670 ]
671
672
673fix_opts = [('f', 'fix-all', False, _("fix all style errors"))] + \
674 [('', 'fix-' + v.opt_name, False,
675 _('fix errors in ' + v.test_name)) for v in all_verifiers]
676ignore_opts = [('', 'ignore-all', False, _("ignore all style errors"))] + \
677 [('', 'ignore-' + v.opt_name, False,
678 _('ignore errors in ' + v.test_name)) for v in all_verifiers]
679skip_opts = [('', 'skip-all', False, _("skip all style error checks"))] + \
680 [('', 'skip-' + v.opt_name, False,
681 _('skip checking for ' + v.test_name)) for v in all_verifiers]
682all_opts = fix_opts + ignore_opts + skip_opts
683
684
573cmdtable = {
574 '^m5style' : (
685cmdtable = {
686 '^m5style' : (
575 do_check_style, [
576 ('f', 'fix-all', False, _("automatically fix style issues")),
577 ('', 'fix-white', False, _("automatically fix white space issues")),
578 ('', 'fix-include', False, _("automatically fix include ordering")),
579 ] + _common_region_options + commands.walkopts,
687 do_check_style, all_opts + _common_region_options + commands.walkopts,
580 _('hg m5style [-a] [FILE]...')),
581 '^m5format' :
582 ( do_check_format, [
583 ] + _common_region_options + commands.walkopts,
584 _('hg m5format [FILE]...')),
585}
586
587if __name__ == '__main__':
588 import getopt
589
590 progname = sys.argv[0]
591 if len(sys.argv) < 2:
592 sys.exit('usage: %s <command> [<command args>]' % progname)
593
594 fixwhite_usage = '%s fixwhite [-t <tabsize> ] <path> [...] \n' % progname
595 chkformat_usage = '%s chkformat <path> [...] \n' % progname
596 chkwhite_usage = '%s chkwhite <path> [...] \n' % progname
597
598 command = sys.argv[1]
599 if command == 'fixwhite':
600 flags = 't:'
601 usage = fixwhite_usage
602 elif command == 'chkwhite':
603 flags = 'nv'
604 usage = chkwhite_usage
605 elif command == 'chkformat':
606 flags = 'nv'
607 usage = chkformat_usage
608 else:
609 sys.exit(fixwhite_usage + chkwhite_usage + chkformat_usage)
610
611 opts, args = getopt.getopt(sys.argv[2:], flags)
612
613 code = 1
614 verbose = 1
615 for opt,arg in opts:
616 if opt == '-n':
617 code = None
618 if opt == '-t':
619 tabsize = int(arg)
620 if opt == '-v':
621 verbose += 1
622
623 if command == 'fixwhite':
624 for filename in args:
625 fixwhite(filename, tabsize)
626 elif command == 'chkwhite':
627 for filename in args:
628 for line,num in checkwhite(filename):
629 print 'invalid whitespace: %s:%d' % (filename, num)
630 if verbose:
631 print '>>%s<<' % line[:-1]
632 elif command == 'chkformat':
633 stats = ValidationStats()
634 for filename in args:
635 validate(filename, stats=stats, verbose=verbose, exit_code=code)
636
637 if verbose > 0:
638 stats.dump()
639 else:
640 sys.exit("command '%s' not found" % command)
688 _('hg m5style [-a] [FILE]...')),
689 '^m5format' :
690 ( do_check_format, [
691 ] + _common_region_options + commands.walkopts,
692 _('hg m5format [FILE]...')),
693}
694
695if __name__ == '__main__':
696 import getopt
697
698 progname = sys.argv[0]
699 if len(sys.argv) < 2:
700 sys.exit('usage: %s <command> [<command args>]' % progname)
701
702 fixwhite_usage = '%s fixwhite [-t <tabsize> ] <path> [...] \n' % progname
703 chkformat_usage = '%s chkformat <path> [...] \n' % progname
704 chkwhite_usage = '%s chkwhite <path> [...] \n' % progname
705
706 command = sys.argv[1]
707 if command == 'fixwhite':
708 flags = 't:'
709 usage = fixwhite_usage
710 elif command == 'chkwhite':
711 flags = 'nv'
712 usage = chkwhite_usage
713 elif command == 'chkformat':
714 flags = 'nv'
715 usage = chkformat_usage
716 else:
717 sys.exit(fixwhite_usage + chkwhite_usage + chkformat_usage)
718
719 opts, args = getopt.getopt(sys.argv[2:], flags)
720
721 code = 1
722 verbose = 1
723 for opt,arg in opts:
724 if opt == '-n':
725 code = None
726 if opt == '-t':
727 tabsize = int(arg)
728 if opt == '-v':
729 verbose += 1
730
731 if command == 'fixwhite':
732 for filename in args:
733 fixwhite(filename, tabsize)
734 elif command == 'chkwhite':
735 for filename in args:
736 for line,num in checkwhite(filename):
737 print 'invalid whitespace: %s:%d' % (filename, num)
738 if verbose:
739 print '>>%s<<' % line[:-1]
740 elif command == 'chkformat':
741 stats = ValidationStats()
742 for filename in args:
743 validate(filename, stats=stats, verbose=verbose, exit_code=code)
744
745 if verbose > 0:
746 stats.dump()
747 else:
748 sys.exit("command '%s' not found" % command)