style.py (9081:bbb0132f0369) style.py (9135:64d4c9d8a384)
1#! /usr/bin/env python
2# Copyright (c) 2006 The Regents of The University of Michigan
3# Copyright (c) 2007,2011 The Hewlett-Packard Development Company
4# All rights reserved.
5#
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.
16#
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.
28#
29# Authors: Nathan Binkert
30
31import heapq
32import os
33import re
34import sys
35
36from os.path import dirname, join as joinpath
37from itertools import count
38from mercurial import bdiff, mdiff
39
40current_dir = dirname(__file__)
41sys.path.insert(0, current_dir)
42sys.path.insert(1, joinpath(dirname(current_dir), 'src', 'python'))
43
44from m5.util import neg_inf, pos_inf, Region, Regions
45import sort_includes
46from file_types import lang_type
47
48all_regions = Regions(Region(neg_inf, pos_inf))
49
50tabsize = 8
51lead = re.compile(r'^([ \t]+)')
52trail = re.compile(r'([ \t]+)$')
53any_control = re.compile(r'\b(if|while|for)[ \t]*[(]')
54good_control = re.compile(r'\b(if|while|for) [(]')
55
56format_types = set(('C', 'C++'))
57
58def modified_regions(old_data, new_data):
59 regions = Regions()
60 beg = None
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)
64 beg = fend
65 return regions
66
67def modregions(wctx, fname):
68 fctx = wctx.filectx(fname)
69 pctx = fctx.parents()
70
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)
75 if len(pctx) == 2:
76 m2 = modified_regions(pctx[1].data(), file_data)
77 # only the lines that are new in both
78 mod_regions &= m2
79 else:
80 mod_regions = Regions()
1#! /usr/bin/env python
2# Copyright (c) 2006 The Regents of The University of Michigan
3# Copyright (c) 2007,2011 The Hewlett-Packard Development Company
4# All rights reserved.
5#
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.
16#
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.
28#
29# Authors: Nathan Binkert
30
31import heapq
32import os
33import re
34import sys
35
36from os.path import dirname, join as joinpath
37from itertools import count
38from mercurial import bdiff, mdiff
39
40current_dir = dirname(__file__)
41sys.path.insert(0, current_dir)
42sys.path.insert(1, joinpath(dirname(current_dir), 'src', 'python'))
43
44from m5.util import neg_inf, pos_inf, Region, Regions
45import sort_includes
46from file_types import lang_type
47
48all_regions = Regions(Region(neg_inf, pos_inf))
49
50tabsize = 8
51lead = re.compile(r'^([ \t]+)')
52trail = re.compile(r'([ \t]+)$')
53any_control = re.compile(r'\b(if|while|for)[ \t]*[(]')
54good_control = re.compile(r'\b(if|while|for) [(]')
55
56format_types = set(('C', 'C++'))
57
58def modified_regions(old_data, new_data):
59 regions = Regions()
60 beg = None
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)
64 beg = fend
65 return regions
66
67def modregions(wctx, fname):
68 fctx = wctx.filectx(fname)
69 pctx = fctx.parents()
70
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)
75 if len(pctx) == 2:
76 m2 = modified_regions(pctx[1].data(), file_data)
77 # only the lines that are new in both
78 mod_regions &= m2
79 else:
80 mod_regions = Regions()
81 mod_regions.add(0, len(lines))
81 mod_regions.append(0, len(lines))
82
83 return mod_regions
84
85class UserInterface(object):
86 def __init__(self, verbose=False, auto=False):
87 self.auto = auto
88 self.verbose = verbose
89
90 def prompt(self, prompt, results, default):
91 if self.auto:
92 return self.auto
93
94 while True:
95 result = self.do_prompt(prompt, results, default)
96 if result in results:
97 return result
98
99class MercurialUI(UserInterface):
100 def __init__(self, ui, *args, **kwargs):
101 super(MercurialUI, self).__init__(*args, **kwargs)
102 self.ui = ui
103
104 def do_prompt(self, prompt, results, default):
105 return self.ui.prompt(prompt, default=default)
106
107 def write(self, string):
108 self.ui.write(string)
109
110class StdioUI(UserInterface):
111 def do_prompt(self, prompt, results, default):
112 return raw_input(prompt) or default
113
114 def write(self, string):
115 sys.stdout.write(string)
116
117class Verifier(object):
118 def __init__(self, ui, repo=None):
119 self.ui = ui
120 self.repo = repo
121 if repo is None:
122 self.wctx = None
123
124 def __getattr__(self, attr):
125 if attr in ('prompt', 'write'):
126 return getattr(self.ui, attr)
127
128 if attr == 'wctx':
129 try:
130 wctx = repo.workingctx()
131 except:
132 from mercurial import context
133 wctx = context.workingctx(repo)
134 self.wctx = wctx
135 return wctx
136
137 raise AttributeError
138
139 def open(self, filename, mode):
140 if self.repo:
141 filename = self.repo.wjoin(filename)
142
143 try:
144 f = file(filename, mode)
145 except OSError, msg:
146 print 'could not open file %s: %s' % (filename, msg)
147 return None
148
149 return f
150
151 def skip(self, filename):
152 return lang_type(filename) not in self.languages
153
154 def check(self, filename, regions=all_regions):
155 f = self.open(filename, 'r')
156
157 errors = 0
158 for num,line in enumerate(f):
159 if num not in regions:
160 continue
161 if not self.check_line(line):
162 self.write("invalid %s in %s:%d\n" % \
163 (self.test_name, filename, num + 1))
164 if self.ui.verbose:
165 self.write(">>%s<<\n" % line[-1])
166 errors += 1
167 return errors
168
169 def fix(self, filename, regions=all_regions):
170 f = self.open(filename, 'r+')
171
172 lines = list(f)
173
174 f.seek(0)
175 f.truncate()
176
177 for i,line in enumerate(lines):
178 if i in regions:
179 line = self.fix_line(line)
180
181 f.write(line)
182 f.close()
183
184 def apply(self, filename, prompt, regions=all_regions):
185 if not self.skip(filename):
186 errors = self.check(filename, regions)
187 if errors:
188 if prompt(filename, self.fix, regions):
189 return True
190 return False
191
192
193class 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:
199 return False
200
201 match = trail.search(line)
202 if match:
203 return False
204
205 return True
206
207 def fix_line(self, line):
208 if lead.search(line):
209 newline = ''
210 for i,c in enumerate(line):
211 if c == ' ':
212 newline += ' '
213 elif c == '\t':
214 newline += ' ' * (tabsize - len(newline) % tabsize)
215 else:
216 newline += line[i:]
217 break
218
219 line = newline
220
221 return line.rstrip() + '\n'
222
223class 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()
228
229 def check(self, filename, regions=all_regions):
230 f = self.open(filename, 'r')
231
232 lines = [ l.rstrip('\n') for l in f.xreadlines() ]
233 old = ''.join(line + '\n' for line in lines)
234 f.close()
235
236 if len(lines) == 0:
237 return 0
238
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)
242
243 mod = modified_regions(old, new)
244 modified = mod & regions
245
246 if modified:
247 self.write("invalid sorting of includes in %s\n" % (filename))
248 if self.ui.verbose:
249 for start, end in modified.regions:
250 self.write("bad region [%d, %d)\n" % (start, end))
251 return 1
252
253 return 0
254
255 def fix(self, filename, regions=all_regions):
256 f = self.open(filename, 'r+')
257
258 old = f.readlines()
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)
263
264 f.seek(0)
265 f.truncate()
266
267 for i,line in enumerate(sort_lines):
268 f.write(line)
269 f.write('\n')
270 f.close()
271
272def linelen(line):
273 tabs = line.count('\t')
274 if not tabs:
275 return len(line)
276
277 count = 0
278 for c in line:
279 if c == '\t':
280 count += tabsize - count % tabsize
281 else:
282 count += 1
283
284 return count
285
286class ValidationStats(object):
287 def __init__(self):
288 self.toolong = 0
289 self.toolong80 = 0
290 self.leadtabs = 0
291 self.trailwhite = 0
292 self.badcontrol = 0
293 self.cret = 0
294
295 def dump(self):
296 print '''\
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)
304
305 def __nonzero__(self):
306 return self.toolong or self.toolong80 or self.leadtabs or \
307 self.trailwhite or self.badcontrol or self.cret
308
309def validate(filename, stats, verbose, exit_code):
310 if lang_type(filename) not in format_types:
311 return
312
313 def msg(lineno, line, message):
314 print '%s:%d>' % (filename, lineno + 1), message
315 if verbose > 2:
316 print line
317
318 def bad():
319 if exit_code is not None:
320 sys.exit(exit_code)
321
322 try:
323 f = file(filename, 'r')
324 except OSError:
325 if verbose > 0:
326 print 'could not open file %s' % filename
327 bad()
328 return
329
330 for i,line in enumerate(f):
331 line = line.rstrip('\n')
332
333 # no carriage returns
334 if line.find('\r') != -1:
335 self.cret += 1
336 if verbose > 1:
337 msg(i, line, 'carriage return found')
338 bad()
339
340 # lines max out at 79 chars
341 llen = linelen(line)
342 if llen > 79:
343 stats.toolong += 1
344 if llen == 80:
345 stats.toolong80 += 1
346 if verbose > 1:
347 msg(i, line, 'line too long (%d chars)' % llen)
348 bad()
349
350 # no tabs used to indent
351 match = lead.search(line)
352 if match and match.group(1).find('\t') != -1:
353 stats.leadtabs += 1
354 if verbose > 1:
355 msg(i, line, 'using tabs to indent')
356 bad()
357
358 # no trailing whitespace
359 if trail.search(line):
360 stats.trailwhite +=1
361 if verbose > 1:
362 msg(i, line, 'trailing whitespace')
363 bad()
364
365 # for c++, exactly one space betwen if/while/for and (
366 if cpp:
367 match = any_control.search(line)
368 if match and not good_control.search(line):
369 stats.badcontrol += 1
370 if verbose > 1:
371 msg(i, line, 'improper spacing after %s' % match.group(1))
372 bad()
373
374def do_check_style(hgui, repo, *files, **args):
375 """check files for proper m5 style guidelines"""
376 from mercurial import mdiff, util
377
378 auto = args.get('auto', False)
379 if auto:
380 auto = 'f'
381 ui = MercurialUI(hgui, hgui.verbose, auto)
382
383 if files:
384 files = frozenset(files)
385
386 def skip(name):
387 return files and name in files
388
389 def prompt(name, func, regions=all_regions):
390 result = ui.prompt("(a)bort, (i)gnore, or (f)ix?", 'aif', 'a')
391 if result == 'a':
392 return True
393 elif result == 'f':
394 func(repo.wjoin(name), regions)
395
396 return False
397
398 modified, added, removed, deleted, unknown, ignore, clean = repo.status()
399
400 whitespace = Whitespace(ui)
401 sorted_includes = SortedIncludes(ui)
402 for fname in added:
403 if skip(fname):
404 continue
405
406 fpath = joinpath(repo.root, fname)
407
408 if whitespace.apply(fpath, prompt):
409 return True
410
411 if sorted_includes.apply(fpath, prompt):
412 return True
413
414 try:
415 wctx = repo.workingctx()
416 except:
417 from mercurial import context
418 wctx = context.workingctx(repo)
419
420 for fname in modified:
421 if skip(fname):
422 continue
423
424 fpath = joinpath(repo.root, fname)
425 regions = modregions(wctx, fname)
426
427 if whitespace.apply(fpath, prompt, regions):
428 return True
429
430 if sorted_includes.apply(fpath, prompt, regions):
431 return True
432
433 return False
434
435def do_check_format(hgui, repo, **args):
436 ui = MercurialUI(hgui, hgui.verbose, auto)
437
438 modified, added, removed, deleted, unknown, ignore, clean = repo.status()
439
440 verbose = 0
441 stats = ValidationStats()
442 for f in modified + added:
443 validate(joinpath(repo.root, f), stats, verbose, None)
444
445 if stats:
446 stats.dump()
447 result = ui.prompt("invalid formatting\n(i)gnore or (a)bort?",
448 'ai', 'a')
449 if result == 'a':
450 return True
451
452 return False
453
454def check_hook(hooktype):
455 if hooktype not in ('pretxncommit', 'pre-qrefresh'):
456 raise AttributeError, \
457 "This hook is not meant for %s" % hooktype
458
459def check_style(ui, repo, hooktype, **kwargs):
460 check_hook(hooktype)
461 args = {}
462
463 try:
464 return do_check_style(ui, repo, **args)
465 except Exception, e:
466 import traceback
467 traceback.print_exc()
468 return True
469
470def check_format(ui, repo, hooktype, **kwargs):
471 check_hook(hooktype)
472 args = {}
473
474 try:
475 return do_check_format(ui, repo, **args)
476 except Exception, e:
477 import traceback
478 traceback.print_exc()
479 return True
480
481try:
482 from mercurial.i18n import _
483except ImportError:
484 def _(arg):
485 return arg
486
487cmdtable = {
488 '^m5style' :
489 ( do_check_style,
490 [ ('a', 'auto', False, _("automatically fix whitespace")) ],
491 _('hg m5style [-a] [FILE]...')),
492 '^m5format' :
493 ( do_check_format,
494 [ ],
495 _('hg m5format [FILE]...')),
496}
497
498if __name__ == '__main__':
499 import getopt
500
501 progname = sys.argv[0]
502 if len(sys.argv) < 2:
503 sys.exit('usage: %s <command> [<command args>]' % progname)
504
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
508
509 command = sys.argv[1]
510 if command == 'fixwhite':
511 flags = 't:'
512 usage = fixwhite_usage
513 elif command == 'chkwhite':
514 flags = 'nv'
515 usage = chkwhite_usage
516 elif command == 'chkformat':
517 flags = 'nv'
518 usage = chkformat_usage
519 else:
520 sys.exit(fixwhite_usage + chkwhite_usage + chkformat_usage)
521
522 opts, args = getopt.getopt(sys.argv[2:], flags)
523
524 code = 1
525 verbose = 1
526 for opt,arg in opts:
527 if opt == '-n':
528 code = None
529 if opt == '-t':
530 tabsize = int(arg)
531 if opt == '-v':
532 verbose += 1
533
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)
541 if verbose:
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)
547
548 if verbose > 0:
549 stats.dump()
550 else:
551 sys.exit("command '%s' not found" % command)
82
83 return mod_regions
84
85class UserInterface(object):
86 def __init__(self, verbose=False, auto=False):
87 self.auto = auto
88 self.verbose = verbose
89
90 def prompt(self, prompt, results, default):
91 if self.auto:
92 return self.auto
93
94 while True:
95 result = self.do_prompt(prompt, results, default)
96 if result in results:
97 return result
98
99class MercurialUI(UserInterface):
100 def __init__(self, ui, *args, **kwargs):
101 super(MercurialUI, self).__init__(*args, **kwargs)
102 self.ui = ui
103
104 def do_prompt(self, prompt, results, default):
105 return self.ui.prompt(prompt, default=default)
106
107 def write(self, string):
108 self.ui.write(string)
109
110class StdioUI(UserInterface):
111 def do_prompt(self, prompt, results, default):
112 return raw_input(prompt) or default
113
114 def write(self, string):
115 sys.stdout.write(string)
116
117class Verifier(object):
118 def __init__(self, ui, repo=None):
119 self.ui = ui
120 self.repo = repo
121 if repo is None:
122 self.wctx = None
123
124 def __getattr__(self, attr):
125 if attr in ('prompt', 'write'):
126 return getattr(self.ui, attr)
127
128 if attr == 'wctx':
129 try:
130 wctx = repo.workingctx()
131 except:
132 from mercurial import context
133 wctx = context.workingctx(repo)
134 self.wctx = wctx
135 return wctx
136
137 raise AttributeError
138
139 def open(self, filename, mode):
140 if self.repo:
141 filename = self.repo.wjoin(filename)
142
143 try:
144 f = file(filename, mode)
145 except OSError, msg:
146 print 'could not open file %s: %s' % (filename, msg)
147 return None
148
149 return f
150
151 def skip(self, filename):
152 return lang_type(filename) not in self.languages
153
154 def check(self, filename, regions=all_regions):
155 f = self.open(filename, 'r')
156
157 errors = 0
158 for num,line in enumerate(f):
159 if num not in regions:
160 continue
161 if not self.check_line(line):
162 self.write("invalid %s in %s:%d\n" % \
163 (self.test_name, filename, num + 1))
164 if self.ui.verbose:
165 self.write(">>%s<<\n" % line[-1])
166 errors += 1
167 return errors
168
169 def fix(self, filename, regions=all_regions):
170 f = self.open(filename, 'r+')
171
172 lines = list(f)
173
174 f.seek(0)
175 f.truncate()
176
177 for i,line in enumerate(lines):
178 if i in regions:
179 line = self.fix_line(line)
180
181 f.write(line)
182 f.close()
183
184 def apply(self, filename, prompt, regions=all_regions):
185 if not self.skip(filename):
186 errors = self.check(filename, regions)
187 if errors:
188 if prompt(filename, self.fix, regions):
189 return True
190 return False
191
192
193class 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:
199 return False
200
201 match = trail.search(line)
202 if match:
203 return False
204
205 return True
206
207 def fix_line(self, line):
208 if lead.search(line):
209 newline = ''
210 for i,c in enumerate(line):
211 if c == ' ':
212 newline += ' '
213 elif c == '\t':
214 newline += ' ' * (tabsize - len(newline) % tabsize)
215 else:
216 newline += line[i:]
217 break
218
219 line = newline
220
221 return line.rstrip() + '\n'
222
223class 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()
228
229 def check(self, filename, regions=all_regions):
230 f = self.open(filename, 'r')
231
232 lines = [ l.rstrip('\n') for l in f.xreadlines() ]
233 old = ''.join(line + '\n' for line in lines)
234 f.close()
235
236 if len(lines) == 0:
237 return 0
238
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)
242
243 mod = modified_regions(old, new)
244 modified = mod & regions
245
246 if modified:
247 self.write("invalid sorting of includes in %s\n" % (filename))
248 if self.ui.verbose:
249 for start, end in modified.regions:
250 self.write("bad region [%d, %d)\n" % (start, end))
251 return 1
252
253 return 0
254
255 def fix(self, filename, regions=all_regions):
256 f = self.open(filename, 'r+')
257
258 old = f.readlines()
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)
263
264 f.seek(0)
265 f.truncate()
266
267 for i,line in enumerate(sort_lines):
268 f.write(line)
269 f.write('\n')
270 f.close()
271
272def linelen(line):
273 tabs = line.count('\t')
274 if not tabs:
275 return len(line)
276
277 count = 0
278 for c in line:
279 if c == '\t':
280 count += tabsize - count % tabsize
281 else:
282 count += 1
283
284 return count
285
286class ValidationStats(object):
287 def __init__(self):
288 self.toolong = 0
289 self.toolong80 = 0
290 self.leadtabs = 0
291 self.trailwhite = 0
292 self.badcontrol = 0
293 self.cret = 0
294
295 def dump(self):
296 print '''\
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)
304
305 def __nonzero__(self):
306 return self.toolong or self.toolong80 or self.leadtabs or \
307 self.trailwhite or self.badcontrol or self.cret
308
309def validate(filename, stats, verbose, exit_code):
310 if lang_type(filename) not in format_types:
311 return
312
313 def msg(lineno, line, message):
314 print '%s:%d>' % (filename, lineno + 1), message
315 if verbose > 2:
316 print line
317
318 def bad():
319 if exit_code is not None:
320 sys.exit(exit_code)
321
322 try:
323 f = file(filename, 'r')
324 except OSError:
325 if verbose > 0:
326 print 'could not open file %s' % filename
327 bad()
328 return
329
330 for i,line in enumerate(f):
331 line = line.rstrip('\n')
332
333 # no carriage returns
334 if line.find('\r') != -1:
335 self.cret += 1
336 if verbose > 1:
337 msg(i, line, 'carriage return found')
338 bad()
339
340 # lines max out at 79 chars
341 llen = linelen(line)
342 if llen > 79:
343 stats.toolong += 1
344 if llen == 80:
345 stats.toolong80 += 1
346 if verbose > 1:
347 msg(i, line, 'line too long (%d chars)' % llen)
348 bad()
349
350 # no tabs used to indent
351 match = lead.search(line)
352 if match and match.group(1).find('\t') != -1:
353 stats.leadtabs += 1
354 if verbose > 1:
355 msg(i, line, 'using tabs to indent')
356 bad()
357
358 # no trailing whitespace
359 if trail.search(line):
360 stats.trailwhite +=1
361 if verbose > 1:
362 msg(i, line, 'trailing whitespace')
363 bad()
364
365 # for c++, exactly one space betwen if/while/for and (
366 if cpp:
367 match = any_control.search(line)
368 if match and not good_control.search(line):
369 stats.badcontrol += 1
370 if verbose > 1:
371 msg(i, line, 'improper spacing after %s' % match.group(1))
372 bad()
373
374def do_check_style(hgui, repo, *files, **args):
375 """check files for proper m5 style guidelines"""
376 from mercurial import mdiff, util
377
378 auto = args.get('auto', False)
379 if auto:
380 auto = 'f'
381 ui = MercurialUI(hgui, hgui.verbose, auto)
382
383 if files:
384 files = frozenset(files)
385
386 def skip(name):
387 return files and name in files
388
389 def prompt(name, func, regions=all_regions):
390 result = ui.prompt("(a)bort, (i)gnore, or (f)ix?", 'aif', 'a')
391 if result == 'a':
392 return True
393 elif result == 'f':
394 func(repo.wjoin(name), regions)
395
396 return False
397
398 modified, added, removed, deleted, unknown, ignore, clean = repo.status()
399
400 whitespace = Whitespace(ui)
401 sorted_includes = SortedIncludes(ui)
402 for fname in added:
403 if skip(fname):
404 continue
405
406 fpath = joinpath(repo.root, fname)
407
408 if whitespace.apply(fpath, prompt):
409 return True
410
411 if sorted_includes.apply(fpath, prompt):
412 return True
413
414 try:
415 wctx = repo.workingctx()
416 except:
417 from mercurial import context
418 wctx = context.workingctx(repo)
419
420 for fname in modified:
421 if skip(fname):
422 continue
423
424 fpath = joinpath(repo.root, fname)
425 regions = modregions(wctx, fname)
426
427 if whitespace.apply(fpath, prompt, regions):
428 return True
429
430 if sorted_includes.apply(fpath, prompt, regions):
431 return True
432
433 return False
434
435def do_check_format(hgui, repo, **args):
436 ui = MercurialUI(hgui, hgui.verbose, auto)
437
438 modified, added, removed, deleted, unknown, ignore, clean = repo.status()
439
440 verbose = 0
441 stats = ValidationStats()
442 for f in modified + added:
443 validate(joinpath(repo.root, f), stats, verbose, None)
444
445 if stats:
446 stats.dump()
447 result = ui.prompt("invalid formatting\n(i)gnore or (a)bort?",
448 'ai', 'a')
449 if result == 'a':
450 return True
451
452 return False
453
454def check_hook(hooktype):
455 if hooktype not in ('pretxncommit', 'pre-qrefresh'):
456 raise AttributeError, \
457 "This hook is not meant for %s" % hooktype
458
459def check_style(ui, repo, hooktype, **kwargs):
460 check_hook(hooktype)
461 args = {}
462
463 try:
464 return do_check_style(ui, repo, **args)
465 except Exception, e:
466 import traceback
467 traceback.print_exc()
468 return True
469
470def check_format(ui, repo, hooktype, **kwargs):
471 check_hook(hooktype)
472 args = {}
473
474 try:
475 return do_check_format(ui, repo, **args)
476 except Exception, e:
477 import traceback
478 traceback.print_exc()
479 return True
480
481try:
482 from mercurial.i18n import _
483except ImportError:
484 def _(arg):
485 return arg
486
487cmdtable = {
488 '^m5style' :
489 ( do_check_style,
490 [ ('a', 'auto', False, _("automatically fix whitespace")) ],
491 _('hg m5style [-a] [FILE]...')),
492 '^m5format' :
493 ( do_check_format,
494 [ ],
495 _('hg m5format [FILE]...')),
496}
497
498if __name__ == '__main__':
499 import getopt
500
501 progname = sys.argv[0]
502 if len(sys.argv) < 2:
503 sys.exit('usage: %s <command> [<command args>]' % progname)
504
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
508
509 command = sys.argv[1]
510 if command == 'fixwhite':
511 flags = 't:'
512 usage = fixwhite_usage
513 elif command == 'chkwhite':
514 flags = 'nv'
515 usage = chkwhite_usage
516 elif command == 'chkformat':
517 flags = 'nv'
518 usage = chkformat_usage
519 else:
520 sys.exit(fixwhite_usage + chkwhite_usage + chkformat_usage)
521
522 opts, args = getopt.getopt(sys.argv[2:], flags)
523
524 code = 1
525 verbose = 1
526 for opt,arg in opts:
527 if opt == '-n':
528 code = None
529 if opt == '-t':
530 tabsize = int(arg)
531 if opt == '-v':
532 verbose += 1
533
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)
541 if verbose:
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)
547
548 if verbose > 0:
549 stats.dump()
550 else:
551 sys.exit("command '%s' not found" % command)