style.py revision 7807:15553b536bd6
1#! /usr/bin/env python 2# Copyright (c) 2006 The Regents of The University of Michigan 3# Copyright (c) 2007 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 re 32import os 33import sys 34 35sys.path.insert(0, os.path.dirname(__file__)) 36 37from file_types import lang_type 38 39lead = re.compile(r'^([ \t]+)') 40trail = re.compile(r'([ \t]+)$') 41any_control = re.compile(r'\b(if|while|for)[ \t]*[(]') 42good_control = re.compile(r'\b(if|while|for) [(]') 43 44whitespace_types = set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons')) 45format_types = set(('C', 'C++')) 46 47def checkwhite_line(line): 48 match = lead.search(line) 49 if match and match.group(1).find('\t') != -1: 50 return False 51 52 match = trail.search(line) 53 if match: 54 return False 55 56 return True 57 58def checkwhite(filename): 59 if lang_type(filename) not in whitespace_types: 60 return 61 62 try: 63 f = file(filename, 'r+') 64 except OSError, msg: 65 print 'could not open file %s: %s' % (filename, msg) 66 return 67 68 for num,line in enumerate(f): 69 if not checkwhite_line(line): 70 yield line,num + 1 71 72def fixwhite_line(line, tabsize): 73 if lead.search(line): 74 newline = '' 75 for i,c in enumerate(line): 76 if c == ' ': 77 newline += ' ' 78 elif c == '\t': 79 newline += ' ' * (tabsize - len(newline) % tabsize) 80 else: 81 newline += line[i:] 82 break 83 84 line = newline 85 86 return line.rstrip() + '\n' 87 88def fixwhite(filename, tabsize, fixonly=None): 89 if lang_type(filename) not in whitespace_types: 90 return 91 92 try: 93 f = file(filename, 'r+') 94 except OSError, msg: 95 print 'could not open file %s: %s' % (filename, msg) 96 return 97 98 lines = list(f) 99 100 f.seek(0) 101 f.truncate() 102 103 for i,line in enumerate(lines): 104 if fixonly is None or i in fixonly: 105 line = fixwhite_line(line, tabsize) 106 107 print >>f, line, 108 109def linelen(line): 110 tabs = line.count('\t') 111 if not tabs: 112 return len(line) 113 114 count = 0 115 for c in line: 116 if c == '\t': 117 count += tabsize - count % tabsize 118 else: 119 count += 1 120 121 return count 122 123class ValidationStats(object): 124 def __init__(self): 125 self.toolong = 0 126 self.toolong80 = 0 127 self.leadtabs = 0 128 self.trailwhite = 0 129 self.badcontrol = 0 130 self.cret = 0 131 132 def dump(self): 133 print '''\ 134%d violations of lines over 79 chars. %d of which are 80 chars exactly. 135%d cases of whitespace at the end of a line. 136%d cases of tabs to indent. 137%d bad parens after if/while/for. 138%d carriage returns found. 139''' % (self.toolong, self.toolong80, self.trailwhite, self.leadtabs, 140 self.badcontrol, self.cret) 141 142 def __nonzero__(self): 143 return self.toolong or self.toolong80 or self.leadtabs or \ 144 self.trailwhite or self.badcontrol or self.cret 145 146def validate(filename, stats, verbose, exit_code): 147 if lang_type(filename) not in format_types: 148 return 149 150 def msg(lineno, line, message): 151 print '%s:%d>' % (filename, lineno + 1), message 152 if verbose > 2: 153 print line 154 155 def bad(): 156 if exit_code is not None: 157 sys.exit(exit_code) 158 159 try: 160 f = file(filename, 'r') 161 except OSError: 162 if verbose > 0: 163 print 'could not open file %s' % filename 164 bad() 165 return 166 167 for i,line in enumerate(f): 168 line = line.rstrip('\n') 169 170 # no carriage returns 171 if line.find('\r') != -1: 172 self.cret += 1 173 if verbose > 1: 174 msg(i, line, 'carriage return found') 175 bad() 176 177 # lines max out at 79 chars 178 llen = linelen(line) 179 if llen > 79: 180 stats.toolong += 1 181 if llen == 80: 182 stats.toolong80 += 1 183 if verbose > 1: 184 msg(i, line, 'line too long (%d chars)' % llen) 185 bad() 186 187 # no tabs used to indent 188 match = lead.search(line) 189 if match and match.group(1).find('\t') != -1: 190 stats.leadtabs += 1 191 if verbose > 1: 192 msg(i, line, 'using tabs to indent') 193 bad() 194 195 # no trailing whitespace 196 if trail.search(line): 197 stats.trailwhite +=1 198 if verbose > 1: 199 msg(i, line, 'trailing whitespace') 200 bad() 201 202 # for c++, exactly one space betwen if/while/for and ( 203 if cpp: 204 match = any_control.search(line) 205 if match and not good_control.search(line): 206 stats.badcontrol += 1 207 if verbose > 1: 208 msg(i, line, 'improper spacing after %s' % match.group(1)) 209 bad() 210 211def modified_lines(old_data, new_data, max_lines): 212 from itertools import count 213 from mercurial import bdiff, mdiff 214 215 modified = set() 216 counter = count() 217 for pbeg, pend, fbeg, fend in bdiff.blocks(old_data, new_data): 218 for i in counter: 219 if i < fbeg: 220 modified.add(i) 221 elif i + 1 >= fend: 222 break 223 elif i > max_lines: 224 break 225 return modified 226 227def do_check_whitespace(ui, repo, *files, **args): 228 """check files for proper m5 style guidelines""" 229 from mercurial import mdiff, util 230 231 if files: 232 files = frozenset(files) 233 234 def skip(name): 235 return files and name in files 236 237 def prompt(name, fixonly=None): 238 if args.get('auto', False): 239 result = 'f' 240 else: 241 while True: 242 result = ui.prompt("(a)bort, (i)gnore, or (f)ix?", default='a') 243 if result in 'aif': 244 break 245 246 if result == 'a': 247 return True 248 elif result == 'f': 249 fixwhite(repo.wjoin(name), args['tabsize'], fixonly) 250 251 return False 252 253 modified, added, removed, deleted, unknown, ignore, clean = repo.status() 254 255 for fname in added: 256 if skip(fname): 257 continue 258 259 ok = True 260 for line,num in checkwhite(repo.wjoin(fname)): 261 ui.write("invalid whitespace in %s:%d\n" % (fname, num)) 262 if ui.verbose: 263 ui.write(">>%s<<\n" % line[-1]) 264 ok = False 265 266 if not ok: 267 if prompt(fname): 268 return True 269 270 try: 271 wctx = repo.workingctx() 272 except: 273 from mercurial import context 274 wctx = context.workingctx(repo) 275 276 for fname in modified: 277 if skip(fname): 278 continue 279 280 if lang_type(fname) not in whitespace_types: 281 continue 282 283 fctx = wctx.filectx(fname) 284 pctx = fctx.parents() 285 286 file_data = fctx.data() 287 lines = mdiff.splitnewlines(file_data) 288 if len(pctx) in (1, 2): 289 mod_lines = modified_lines(pctx[0].data(), file_data, len(lines)) 290 if len(pctx) == 2: 291 m2 = modified_lines(pctx[1].data(), file_data, len(lines)) 292 # only the lines that are new in both 293 mod_lines = mod_lines & m2 294 else: 295 mod_lines = xrange(0, len(lines)) 296 297 fixonly = set() 298 for i,line in enumerate(lines): 299 if i not in mod_lines: 300 continue 301 302 if checkwhite_line(line): 303 continue 304 305 ui.write("invalid whitespace: %s:%d\n" % (fname, i+1)) 306 if ui.verbose: 307 ui.write(">>%s<<\n" % line[:-1]) 308 fixonly.add(i) 309 310 if fixonly: 311 if prompt(fname, fixonly): 312 return True 313 314def do_check_format(ui, repo, **args): 315 modified, added, removed, deleted, unknown, ignore, clean = repo.status() 316 317 verbose = 0 318 stats = ValidationStats() 319 for f in modified + added: 320 validate(f, stats, verbose, None) 321 322 if stats: 323 stats.dump() 324 result = ui.prompt("invalid formatting\n(i)gnore or (a)bort?", 325 "^[ia]$", "a") 326 if result.startswith('i'): 327 pass 328 elif result.startswith('a'): 329 return True 330 else: 331 raise util.Abort(_("Invalid response: '%s'") % result) 332 333 return False 334 335def check_hook(hooktype): 336 if hooktype not in ('pretxncommit', 'pre-qrefresh'): 337 raise AttributeError, \ 338 "This hook is not meant for %s" % hooktype 339 340def check_whitespace(ui, repo, hooktype, **kwargs): 341 check_hook(hooktype) 342 args = { 'tabsize' : 8 } 343 return do_check_whitespace(ui, repo, **args) 344 345def check_format(ui, repo, hooktype, **kwargs): 346 check_hook(hooktype) 347 args = {} 348 return do_check_format(ui, repo, **args) 349 350try: 351 from mercurial.i18n import _ 352except ImportError: 353 def _(arg): 354 return arg 355 356cmdtable = { 357 '^m5style' : 358 ( do_check_whitespace, 359 [ ('a', 'auto', False, _("automatically fix whitespace")), 360 ('t', 'tabsize', 8, _("Number of spaces TAB indents")) ], 361 _('hg m5style [-a] [-t <tabsize>] [FILE]...')), 362 '^m5format' : 363 ( do_check_format, 364 [ ], 365 _('hg m5format [FILE]...')), 366} 367 368if __name__ == '__main__': 369 import getopt 370 371 progname = sys.argv[0] 372 if len(sys.argv) < 2: 373 sys.exit('usage: %s <command> [<command args>]' % progname) 374 375 fixwhite_usage = '%s fixwhite [-t <tabsize> ] <path> [...] \n' % progname 376 chkformat_usage = '%s chkformat <path> [...] \n' % progname 377 chkwhite_usage = '%s chkwhite <path> [...] \n' % progname 378 379 command = sys.argv[1] 380 if command == 'fixwhite': 381 flags = 't:' 382 usage = fixwhite_usage 383 elif command == 'chkwhite': 384 flags = 'nv' 385 usage = chkwhite_usage 386 elif command == 'chkformat': 387 flags = 'nv' 388 usage = chkformat_usage 389 else: 390 sys.exit(fixwhite_usage + chkwhite_usage + chkformat_usage) 391 392 opts, args = getopt.getopt(sys.argv[2:], flags) 393 394 code = 1 395 verbose = 1 396 tabsize = 8 397 for opt,arg in opts: 398 if opt == '-n': 399 code = None 400 if opt == '-t': 401 tabsize = int(arg) 402 if opt == '-v': 403 verbose += 1 404 405 if command == 'fixwhite': 406 for filename in args: 407 fixwhite(filename, tabsize) 408 elif command == 'chkwhite': 409 for filename in args: 410 for line,num in checkwhite(filename): 411 print 'invalid whitespace: %s:%d' % (filename, num) 412 if verbose: 413 print '>>%s<<' % line[:-1] 414 elif command == 'chkformat': 415 stats = ValidationStats() 416 for filename in args: 417 validate(filename, stats=stats, verbose=verbose, exit_code=code) 418 419 if verbose > 0: 420 stats.dump() 421 else: 422 sys.exit("command '%s' not found" % command) 423