/contrib/check-code.py

https://bitbucket.org/mirror/mercurial/ · Python · 575 lines · 471 code · 48 blank · 56 comment · 63 complexity · 3474fce93d7cbb93f30233ff8d964c01 MD5 · raw file

  1. #!/usr/bin/env python
  2. #
  3. # check-code - a style and portability checker for Mercurial
  4. #
  5. # Copyright 2010 Matt Mackall <mpm@selenic.com>
  6. #
  7. # This software may be used and distributed according to the terms of the
  8. # GNU General Public License version 2 or any later version.
  9. """style and portability checker for Mercurial
  10. when a rule triggers wrong, do one of the following (prefer one from top):
  11. * do the work-around the rule suggests
  12. * doublecheck that it is a false match
  13. * improve the rule pattern
  14. * add an ignore pattern to the rule (3rd arg) which matches your good line
  15. (you can append a short comment and match this, like: #re-raises, # no-py24)
  16. * change the pattern to a warning and list the exception in test-check-code-hg
  17. * ONLY use no--check-code for skipping entire files from external sources
  18. """
  19. import re, glob, os, sys
  20. import keyword
  21. import optparse
  22. try:
  23. import re2
  24. except ImportError:
  25. re2 = None
  26. def compilere(pat, multiline=False):
  27. if multiline:
  28. pat = '(?m)' + pat
  29. if re2:
  30. try:
  31. return re2.compile(pat)
  32. except re2.error:
  33. pass
  34. return re.compile(pat)
  35. def repquote(m):
  36. fromc = '.:'
  37. tochr = 'pq'
  38. def encodechr(i):
  39. if i > 255:
  40. return 'u'
  41. c = chr(i)
  42. if c in ' \n':
  43. return c
  44. if c.isalpha():
  45. return 'x'
  46. if c.isdigit():
  47. return 'n'
  48. try:
  49. return tochr[fromc.find(c)]
  50. except (ValueError, IndexError):
  51. return 'o'
  52. t = m.group('text')
  53. tt = ''.join(encodechr(i) for i in xrange(256))
  54. t = t.translate(tt)
  55. return m.group('quote') + t + m.group('quote')
  56. def reppython(m):
  57. comment = m.group('comment')
  58. if comment:
  59. l = len(comment.rstrip())
  60. return "#" * l + comment[l:]
  61. return repquote(m)
  62. def repcomment(m):
  63. return m.group(1) + "#" * len(m.group(2))
  64. def repccomment(m):
  65. t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
  66. return m.group(1) + t + "*/"
  67. def repcallspaces(m):
  68. t = re.sub(r"\n\s+", "\n", m.group(2))
  69. return m.group(1) + t
  70. def repinclude(m):
  71. return m.group(1) + "<foo>"
  72. def rephere(m):
  73. t = re.sub(r"\S", "x", m.group(2))
  74. return m.group(1) + t
  75. testpats = [
  76. [
  77. (r'pushd|popd', "don't use 'pushd' or 'popd', use 'cd'"),
  78. (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
  79. (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
  80. (r'(?<!hg )grep.*-a', "don't use 'grep -a', use in-line python"),
  81. (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
  82. (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
  83. (r'echo -n', "don't use 'echo -n', use printf"),
  84. (r'(^| )wc[^|]*$\n(?!.*\(re\))', "filter wc output"),
  85. (r'head -c', "don't use 'head -c', use 'dd'"),
  86. (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
  87. (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
  88. (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
  89. (r'printf.*[^\\]\\([1-9]|0\d)', "don't use 'printf \NNN', use Python"),
  90. (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
  91. (r'\$\(.*\)', "don't use $(expr), use `expr`"),
  92. (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
  93. (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
  94. "use egrep for extended grep syntax"),
  95. (r'/bin/', "don't use explicit paths for tools"),
  96. (r'[^\n]\Z', "no trailing newline"),
  97. (r'export.*=', "don't export and assign at once"),
  98. (r'^source\b', "don't use 'source', use '.'"),
  99. (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
  100. (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
  101. (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
  102. (r'^stop\(\)', "don't use 'stop' as a shell function name"),
  103. (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
  104. (r'^alias\b.*=', "don't use alias, use a function"),
  105. (r'if\s*!', "don't use '!' to negate exit status"),
  106. (r'/dev/u?random', "don't use entropy, use /dev/zero"),
  107. (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
  108. (r'^( *)\t', "don't use tabs to indent"),
  109. (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
  110. "put a backslash-escaped newline after sed 'i' command"),
  111. (r'^diff *-\w*u.*$\n(^ \$ |^$)', "prefix diff -u with cmp"),
  112. ],
  113. # warnings
  114. [
  115. (r'^function', "don't use 'function', use old style"),
  116. (r'^diff.*-\w*N', "don't use 'diff -N'"),
  117. (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
  118. (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
  119. (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
  120. ]
  121. ]
  122. testfilters = [
  123. (r"( *)(#([^\n]*\S)?)", repcomment),
  124. (r"<<(\S+)((.|\n)*?\n\1)", rephere),
  125. ]
  126. winglobmsg = "use (glob) to match Windows paths too"
  127. uprefix = r"^ \$ "
  128. utestpats = [
  129. [
  130. (r'^(\S.*|| [$>] .*)[ \t]\n', "trailing whitespace on non-output"),
  131. (uprefix + r'.*\|\s*sed[^|>\n]*\n',
  132. "use regex test output patterns instead of sed"),
  133. (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
  134. (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
  135. (uprefix + r'.*\|\| echo.*(fail|error)',
  136. "explicit exit code checks unnecessary"),
  137. (uprefix + r'set -e', "don't use set -e"),
  138. (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
  139. (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite "
  140. "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx
  141. 'hg pull -q file:../test'), # in test-pull.t which is skipped on windows
  142. (r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
  143. (r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
  144. winglobmsg),
  145. (r'^ pulling from \$TESTTMP/.*[^)]$', winglobmsg,
  146. '\$TESTTMP/unix-repo$'), # in test-issue1802.t which skipped on windows
  147. (r'^ reverting .*/.*[^)]$', winglobmsg),
  148. (r'^ cloning subrepo \S+/.*[^)]$', winglobmsg),
  149. (r'^ pushing to \$TESTTMP/.*[^)]$', winglobmsg),
  150. (r'^ pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg),
  151. (r'^ moving \S+/.*[^)]$', winglobmsg),
  152. (r'^ no changes made to subrepo since.*/.*[^)]$', winglobmsg),
  153. (r'^ .*: largefile \S+ not available from file:.*/.*[^)]$', winglobmsg),
  154. (r'^ .*file://\$TESTTMP',
  155. 'write "file:/*/$TESTTMP" + (glob) to match on windows too'),
  156. ],
  157. # warnings
  158. [
  159. (r'^ [^*?/\n]* \(glob\)$',
  160. "glob match with no glob character (?*/)"),
  161. ]
  162. ]
  163. for i in [0, 1]:
  164. for p, m in testpats[i]:
  165. if p.startswith(r'^'):
  166. p = r"^ [$>] (%s)" % p[1:]
  167. else:
  168. p = r"^ [$>] .*(%s)" % p
  169. utestpats[i].append((p, m))
  170. utestfilters = [
  171. (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
  172. (r"( *)(#([^\n]*\S)?)", repcomment),
  173. ]
  174. pypats = [
  175. [
  176. (r'\([^)]*\*\w[^()]+\w+=', "can't pass varargs with keyword in Py2.5"),
  177. (r'^\s*def\s*\w+\s*\(.*,\s*\(',
  178. "tuple parameter unpacking not available in Python 3+"),
  179. (r'lambda\s*\(.*,.*\)',
  180. "tuple parameter unpacking not available in Python 3+"),
  181. (r'import (.+,[^.]+\.[^.]+|[^.]+\.[^.]+,)',
  182. '2to3 can\'t always rewrite "import qux, foo.bar", '
  183. 'use "import foo.bar" on its own line instead.'),
  184. (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
  185. (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
  186. (r'dict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}',
  187. 'dict-from-generator'),
  188. (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
  189. (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
  190. (r'^\s*\t', "don't use tabs"),
  191. (r'\S;\s*\n', "semicolon"),
  192. (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
  193. (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
  194. (r'(\w|\)),\w', "missing whitespace after ,"),
  195. (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
  196. (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
  197. (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)\1except.*?:\n'
  198. r'((?:\n|\1\s.*\n)+?)\1finally:', 'no try/except/finally in Python 2.4'),
  199. (r'(?<!def)(\s+|^|\()next\(.+\)',
  200. 'no next(foo) in Python 2.4 and 2.5, use foo.next() instead'),
  201. (r'(\s+)try:\n((?:\n|\1\s.*\n)*?)\1\s*yield\b.*?'
  202. r'((?:\n|\1\s.*\n)+?)\1finally:',
  203. 'no yield inside try/finally in Python 2.4'),
  204. (r'.{81}', "line too long"),
  205. (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
  206. (r'[^\n]\Z', "no trailing newline"),
  207. (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
  208. # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
  209. # "don't use underbars in identifiers"),
  210. (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
  211. "don't use camelcase in identifiers"),
  212. (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
  213. "linebreak after :"),
  214. (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
  215. (r'class\s[^( \n]+\(\):',
  216. "class foo() not available in Python 2.4, use class foo(object)"),
  217. (r'\b(%s)\(' % '|'.join(keyword.kwlist),
  218. "Python keyword is not a function"),
  219. (r',]', "unneeded trailing ',' in list"),
  220. # (r'class\s[A-Z][^\(]*\((?!Exception)',
  221. # "don't capitalize non-exception classes"),
  222. # (r'in range\(', "use xrange"),
  223. # (r'^\s*print\s+', "avoid using print in core and extensions"),
  224. (r'[\x80-\xff]', "non-ASCII character literal"),
  225. (r'("\')\.format\(', "str.format() not available in Python 2.4"),
  226. (r'^\s*with\s+', "with not available in Python 2.4"),
  227. (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
  228. (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
  229. (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
  230. (r'(?<!def)\s+(any|all|format)\(',
  231. "any/all/format not available in Python 2.4", 'no-py24'),
  232. (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
  233. (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
  234. "gratuitous whitespace after Python keyword"),
  235. (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
  236. # (r'\s\s=', "gratuitous whitespace before ="),
  237. (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
  238. "missing whitespace around operator"),
  239. (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
  240. "missing whitespace around operator"),
  241. (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
  242. "missing whitespace around operator"),
  243. (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
  244. "wrong whitespace around ="),
  245. (r'\([^()]*( =[^=]|[^<>!=]= )',
  246. "no whitespace around = for named parameters"),
  247. (r'raise Exception', "don't raise generic exceptions"),
  248. (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
  249. "don't use old-style two-argument raise, use Exception(message)"),
  250. (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
  251. (r' [=!]=\s+(True|False|None)',
  252. "comparison with singleton, use 'is' or 'is not' instead"),
  253. (r'^\s*(while|if) [01]:',
  254. "use True/False for constant Boolean expression"),
  255. (r'(?:(?<!def)\s+|\()hasattr',
  256. 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
  257. (r'opener\([^)]*\).read\(',
  258. "use opener.read() instead"),
  259. (r'BaseException', 'not in Python 2.4, use Exception'),
  260. (r'os\.path\.relpath', 'os.path.relpath is not in Python 2.5'),
  261. (r'opener\([^)]*\).write\(',
  262. "use opener.write() instead"),
  263. (r'[\s\(](open|file)\([^)]*\)\.read\(',
  264. "use util.readfile() instead"),
  265. (r'[\s\(](open|file)\([^)]*\)\.write\(',
  266. "use util.writefile() instead"),
  267. (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
  268. "always assign an opened file to a variable, and close it afterwards"),
  269. (r'[\s\(](open|file)\([^)]*\)\.',
  270. "always assign an opened file to a variable, and close it afterwards"),
  271. (r'(?i)descendent', "the proper spelling is descendAnt"),
  272. (r'\.debug\(\_', "don't mark debug messages for translation"),
  273. (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
  274. (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
  275. (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
  276. (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
  277. "missing _() in ui message (use () to hide false-positives)"),
  278. (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
  279. ],
  280. # warnings
  281. [
  282. (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
  283. ]
  284. ]
  285. pyfilters = [
  286. (r"""(?msx)(?P<comment>\#.*?$)|
  287. ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
  288. (?P<text>(([^\\]|\\.)*?))
  289. (?P=quote))""", reppython),
  290. ]
  291. txtfilters = []
  292. txtpats = [
  293. [
  294. ('\s$', 'trailing whitespace'),
  295. ('.. note::[ \n][^\n]', 'add two newlines after note::')
  296. ],
  297. []
  298. ]
  299. cpats = [
  300. [
  301. (r'//', "don't use //-style comments"),
  302. (r'^ ', "don't use spaces to indent"),
  303. (r'\S\t', "don't use tabs except for indent"),
  304. (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
  305. (r'.{81}', "line too long"),
  306. (r'(while|if|do|for)\(', "use space after while/if/do/for"),
  307. (r'return\(', "return is not a function"),
  308. (r' ;', "no space before ;"),
  309. (r'[)][{]', "space between ) and {"),
  310. (r'\w+\* \w+', "use int *foo, not int* foo"),
  311. (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
  312. (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
  313. (r'\w,\w', "missing whitespace after ,"),
  314. (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
  315. (r'^#\s+\w', "use #foo, not # foo"),
  316. (r'[^\n]\Z', "no trailing newline"),
  317. (r'^\s*#import\b', "use only #include in standard C code"),
  318. ],
  319. # warnings
  320. []
  321. ]
  322. cfilters = [
  323. (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
  324. (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
  325. (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
  326. (r'(\()([^)]+\))', repcallspaces),
  327. ]
  328. inutilpats = [
  329. [
  330. (r'\bui\.', "don't use ui in util"),
  331. ],
  332. # warnings
  333. []
  334. ]
  335. inrevlogpats = [
  336. [
  337. (r'\brepo\.', "don't use repo in revlog"),
  338. ],
  339. # warnings
  340. []
  341. ]
  342. webtemplatefilters = []
  343. webtemplatepats = [
  344. [],
  345. [
  346. (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
  347. 'follow desc keyword with either firstline or websub'),
  348. ]
  349. ]
  350. checks = [
  351. ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
  352. ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
  353. ('c', r'.*\.[ch]$', '', cfilters, cpats),
  354. ('unified test', r'.*\.t$', '', utestfilters, utestpats),
  355. ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
  356. pyfilters, inrevlogpats),
  357. ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
  358. inutilpats),
  359. ('txt', r'.*\.txt$', '', txtfilters, txtpats),
  360. ('web template', r'mercurial/templates/.*\.tmpl', '',
  361. webtemplatefilters, webtemplatepats),
  362. ]
  363. def _preparepats():
  364. for c in checks:
  365. failandwarn = c[-1]
  366. for pats in failandwarn:
  367. for i, pseq in enumerate(pats):
  368. # fix-up regexes for multi-line searches
  369. p = pseq[0]
  370. # \s doesn't match \n
  371. p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
  372. # [^...] doesn't match newline
  373. p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
  374. pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
  375. filters = c[3]
  376. for i, flt in enumerate(filters):
  377. filters[i] = re.compile(flt[0]), flt[1]
  378. _preparepats()
  379. class norepeatlogger(object):
  380. def __init__(self):
  381. self._lastseen = None
  382. def log(self, fname, lineno, line, msg, blame):
  383. """print error related a to given line of a given file.
  384. The faulty line will also be printed but only once in the case
  385. of multiple errors.
  386. :fname: filename
  387. :lineno: line number
  388. :line: actual content of the line
  389. :msg: error message
  390. """
  391. msgid = fname, lineno, line
  392. if msgid != self._lastseen:
  393. if blame:
  394. print "%s:%d (%s):" % (fname, lineno, blame)
  395. else:
  396. print "%s:%d:" % (fname, lineno)
  397. print " > %s" % line
  398. self._lastseen = msgid
  399. print " " + msg
  400. _defaultlogger = norepeatlogger()
  401. def getblame(f):
  402. lines = []
  403. for l in os.popen('hg annotate -un %s' % f):
  404. start, line = l.split(':', 1)
  405. user, rev = start.split()
  406. lines.append((line[1:-1], user, rev))
  407. return lines
  408. def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
  409. blame=False, debug=False, lineno=True):
  410. """checks style and portability of a given file
  411. :f: filepath
  412. :logfunc: function used to report error
  413. logfunc(filename, linenumber, linecontent, errormessage)
  414. :maxerr: number of error to display before aborting.
  415. Set to false (default) to report all errors
  416. return True if no error is found, False otherwise.
  417. """
  418. blamecache = None
  419. result = True
  420. try:
  421. fp = open(f)
  422. except IOError, e:
  423. print "Skipping %s, %s" % (f, str(e).split(':', 1)[0])
  424. return result
  425. pre = post = fp.read()
  426. fp.close()
  427. for name, match, magic, filters, pats in checks:
  428. if debug:
  429. print name, f
  430. fc = 0
  431. if not (re.match(match, f) or (magic and re.search(magic, f))):
  432. if debug:
  433. print "Skipping %s for %s it doesn't match %s" % (
  434. name, match, f)
  435. continue
  436. if "no-" "check-code" in pre:
  437. print "Skipping %s it has no-" "check-code" % f
  438. return "Skip" # skip checking this file
  439. for p, r in filters:
  440. post = re.sub(p, r, post)
  441. nerrs = len(pats[0]) # nerr elements are errors
  442. if warnings:
  443. pats = pats[0] + pats[1]
  444. else:
  445. pats = pats[0]
  446. # print post # uncomment to show filtered version
  447. if debug:
  448. print "Checking %s for %s" % (name, f)
  449. prelines = None
  450. errors = []
  451. for i, pat in enumerate(pats):
  452. if len(pat) == 3:
  453. p, msg, ignore = pat
  454. else:
  455. p, msg = pat
  456. ignore = None
  457. if i >= nerrs:
  458. msg = "warning: " + msg
  459. pos = 0
  460. n = 0
  461. for m in p.finditer(post):
  462. if prelines is None:
  463. prelines = pre.splitlines()
  464. postlines = post.splitlines(True)
  465. start = m.start()
  466. while n < len(postlines):
  467. step = len(postlines[n])
  468. if pos + step > start:
  469. break
  470. pos += step
  471. n += 1
  472. l = prelines[n]
  473. if ignore and re.search(ignore, l, re.MULTILINE):
  474. if debug:
  475. print "Skipping %s for %s:%s (ignore pattern)" % (
  476. name, f, n)
  477. continue
  478. bd = ""
  479. if blame:
  480. bd = 'working directory'
  481. if not blamecache:
  482. blamecache = getblame(f)
  483. if n < len(blamecache):
  484. bl, bu, br = blamecache[n]
  485. if bl == l:
  486. bd = '%s@%s' % (bu, br)
  487. errors.append((f, lineno and n + 1, l, msg, bd))
  488. result = False
  489. errors.sort()
  490. for e in errors:
  491. logfunc(*e)
  492. fc += 1
  493. if maxerr and fc >= maxerr:
  494. print " (too many errors, giving up)"
  495. break
  496. return result
  497. if __name__ == "__main__":
  498. parser = optparse.OptionParser("%prog [options] [files]")
  499. parser.add_option("-w", "--warnings", action="store_true",
  500. help="include warning-level checks")
  501. parser.add_option("-p", "--per-file", type="int",
  502. help="max warnings per file")
  503. parser.add_option("-b", "--blame", action="store_true",
  504. help="use annotate to generate blame info")
  505. parser.add_option("", "--debug", action="store_true",
  506. help="show debug information")
  507. parser.add_option("", "--nolineno", action="store_false",
  508. dest='lineno', help="don't show line numbers")
  509. parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
  510. lineno=True)
  511. (options, args) = parser.parse_args()
  512. if len(args) == 0:
  513. check = glob.glob("*")
  514. else:
  515. check = args
  516. ret = 0
  517. for f in check:
  518. if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
  519. blame=options.blame, debug=options.debug,
  520. lineno=options.lineno):
  521. ret = 1
  522. sys.exit(ret)