PageRenderTime 58ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/util/style.py

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