##// END OF EJS Templates
check-code: catch "echo > $HGRCPATH" too...
Martin Geisler -
r13524:121c89dd default
parent child Browse files
Show More
@@ -1,308 +1,308
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # check-code - a style and portability checker for Mercurial
3 # check-code - a style and portability checker for Mercurial
4 #
4 #
5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
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.
8 # GNU General Public License version 2 or any later version.
9
9
10 import re, glob, os, sys
10 import re, glob, os, sys
11 import keyword
11 import keyword
12 import optparse
12 import optparse
13
13
14 def repquote(m):
14 def repquote(m):
15 t = re.sub(r"\w", "x", m.group('text'))
15 t = re.sub(r"\w", "x", m.group('text'))
16 t = re.sub(r"[^\sx]", "o", t)
16 t = re.sub(r"[^\sx]", "o", t)
17 return m.group('quote') + t + m.group('quote')
17 return m.group('quote') + t + m.group('quote')
18
18
19 def reppython(m):
19 def reppython(m):
20 comment = m.group('comment')
20 comment = m.group('comment')
21 if comment:
21 if comment:
22 return "#" * len(comment)
22 return "#" * len(comment)
23 return repquote(m)
23 return repquote(m)
24
24
25 def repcomment(m):
25 def repcomment(m):
26 return m.group(1) + "#" * len(m.group(2))
26 return m.group(1) + "#" * len(m.group(2))
27
27
28 def repccomment(m):
28 def repccomment(m):
29 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
29 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
30 return m.group(1) + t + "*/"
30 return m.group(1) + t + "*/"
31
31
32 def repcallspaces(m):
32 def repcallspaces(m):
33 t = re.sub(r"\n\s+", "\n", m.group(2))
33 t = re.sub(r"\n\s+", "\n", m.group(2))
34 return m.group(1) + t
34 return m.group(1) + t
35
35
36 def repinclude(m):
36 def repinclude(m):
37 return m.group(1) + "<foo>"
37 return m.group(1) + "<foo>"
38
38
39 def rephere(m):
39 def rephere(m):
40 t = re.sub(r"\S", "x", m.group(2))
40 t = re.sub(r"\S", "x", m.group(2))
41 return m.group(1) + t
41 return m.group(1) + t
42
42
43
43
44 testpats = [
44 testpats = [
45 (r'(pushd|popd)', "don't use 'pushd' or 'popd', use 'cd'"),
45 (r'(pushd|popd)', "don't use 'pushd' or 'popd', use 'cd'"),
46 (r'\W\$?\(\([^\)]*\)\)', "don't use (()) or $(()), use 'expr'"),
46 (r'\W\$?\(\([^\)]*\)\)', "don't use (()) or $(()), use 'expr'"),
47 (r'^function', "don't use 'function', use old style"),
47 (r'^function', "don't use 'function', use old style"),
48 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
48 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
49 (r'echo.*\\n', "don't use 'echo \\n', use printf"),
49 (r'echo.*\\n', "don't use 'echo \\n', use printf"),
50 (r'echo -n', "don't use 'echo -n', use printf"),
50 (r'echo -n', "don't use 'echo -n', use printf"),
51 (r'^diff.*-\w*N', "don't use 'diff -N'"),
51 (r'^diff.*-\w*N', "don't use 'diff -N'"),
52 (r'(^| )wc[^|]*$', "filter wc output"),
52 (r'(^| )wc[^|]*$', "filter wc output"),
53 (r'head -c', "don't use 'head -c', use 'dd'"),
53 (r'head -c', "don't use 'head -c', use 'dd'"),
54 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
54 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
55 (r'printf.*\\\d\d\d', "don't use 'printf \NNN', use Python"),
55 (r'printf.*\\\d\d\d', "don't use 'printf \NNN', use Python"),
56 (r'printf.*\\x', "don't use printf \\x, use Python"),
56 (r'printf.*\\x', "don't use printf \\x, use Python"),
57 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
57 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
58 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
58 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
59 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
59 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
60 "use egrep for extended grep syntax"),
60 "use egrep for extended grep syntax"),
61 (r'/bin/', "don't use explicit paths for tools"),
61 (r'/bin/', "don't use explicit paths for tools"),
62 (r'\$PWD', "don't use $PWD, use `pwd`"),
62 (r'\$PWD', "don't use $PWD, use `pwd`"),
63 (r'[^\n]\Z', "no trailing newline"),
63 (r'[^\n]\Z', "no trailing newline"),
64 (r'export.*=', "don't export and assign at once"),
64 (r'export.*=', "don't export and assign at once"),
65 ('^([^"\']|("[^"]*")|(\'[^\']*\'))*\\^', "^ must be quoted"),
65 ('^([^"\']|("[^"]*")|(\'[^\']*\'))*\\^', "^ must be quoted"),
66 (r'^source\b', "don't use 'source', use '.'"),
66 (r'^source\b', "don't use 'source', use '.'"),
67 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
67 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
68 (r'ls\s+[^|-]+\s+-', "options to 'ls' must come before filenames"),
68 (r'ls\s+[^|-]+\s+-', "options to 'ls' must come before filenames"),
69 (r'[^>]>\s*\$HGRCPATH <<EOF', "append to $HGRCPATH, do not overwrite it"),
69 (r'[^>]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
70 ]
70 ]
71
71
72 testfilters = [
72 testfilters = [
73 (r"( *)(#([^\n]*\S)?)", repcomment),
73 (r"( *)(#([^\n]*\S)?)", repcomment),
74 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
74 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
75 ]
75 ]
76
76
77 uprefix = r"^ \$ "
77 uprefix = r"^ \$ "
78 uprefixc = r"^ > "
78 uprefixc = r"^ > "
79 utestpats = [
79 utestpats = [
80 (r'^(\S| $ ).*(\S\s+|^\s+)\n', "trailing whitespace on non-output"),
80 (r'^(\S| $ ).*(\S\s+|^\s+)\n', "trailing whitespace on non-output"),
81 (uprefix + r'.*\|\s*sed', "use regex test output patterns instead of sed"),
81 (uprefix + r'.*\|\s*sed', "use regex test output patterns instead of sed"),
82 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
82 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
83 (uprefix + r'.*\$\?', "explicit exit code checks unnecessary"),
83 (uprefix + r'.*\$\?', "explicit exit code checks unnecessary"),
84 (uprefix + r'.*\|\| echo.*(fail|error)',
84 (uprefix + r'.*\|\| echo.*(fail|error)',
85 "explicit exit code checks unnecessary"),
85 "explicit exit code checks unnecessary"),
86 (uprefix + r'set -e', "don't use set -e"),
86 (uprefix + r'set -e', "don't use set -e"),
87 (uprefixc + r'( *)\t', "don't use tabs to indent"),
87 (uprefixc + r'( *)\t', "don't use tabs to indent"),
88 ]
88 ]
89
89
90 for p, m in testpats:
90 for p, m in testpats:
91 if p.startswith('^'):
91 if p.startswith('^'):
92 p = uprefix + p[1:]
92 p = uprefix + p[1:]
93 else:
93 else:
94 p = uprefix + p
94 p = uprefix + p
95 utestpats.append((p, m))
95 utestpats.append((p, m))
96
96
97 utestfilters = [
97 utestfilters = [
98 (r"( *)(#([^\n]*\S)?)", repcomment),
98 (r"( *)(#([^\n]*\S)?)", repcomment),
99 ]
99 ]
100
100
101 pypats = [
101 pypats = [
102 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
102 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
103 "tuple parameter unpacking not available in Python 3+"),
103 "tuple parameter unpacking not available in Python 3+"),
104 (r'lambda\s*\(.*,.*\)',
104 (r'lambda\s*\(.*,.*\)',
105 "tuple parameter unpacking not available in Python 3+"),
105 "tuple parameter unpacking not available in Python 3+"),
106 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
106 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
107 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
107 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
108 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
108 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
109 (r'^\s*\t', "don't use tabs"),
109 (r'^\s*\t', "don't use tabs"),
110 (r'\S;\s*\n', "semicolon"),
110 (r'\S;\s*\n', "semicolon"),
111 (r'\w,\w', "missing whitespace after ,"),
111 (r'\w,\w', "missing whitespace after ,"),
112 (r'\w[+/*\-<>]\w', "missing whitespace in expression"),
112 (r'\w[+/*\-<>]\w', "missing whitespace in expression"),
113 (r'^\s+\w+=\w+[^,)]$', "missing whitespace in assignment"),
113 (r'^\s+\w+=\w+[^,)]$', "missing whitespace in assignment"),
114 (r'.{85}', "line too long"),
114 (r'.{85}', "line too long"),
115 (r'.{81}', "warning: line over 80 characters"),
115 (r'.{81}', "warning: line over 80 characters"),
116 (r'[^\n]\Z', "no trailing newline"),
116 (r'[^\n]\Z', "no trailing newline"),
117 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
117 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
118 # (r'^\s+[^_ ][^_. ]+_[^_]+\s*=', "don't use underbars in identifiers"),
118 # (r'^\s+[^_ ][^_. ]+_[^_]+\s*=', "don't use underbars in identifiers"),
119 # (r'\w*[a-z][A-Z]\w*\s*=', "don't use camelcase in identifiers"),
119 # (r'\w*[a-z][A-Z]\w*\s*=', "don't use camelcase in identifiers"),
120 (r'^\s*(if|while|def|class|except|try)\s[^[]*:\s*[^\]#\s]+',
120 (r'^\s*(if|while|def|class|except|try)\s[^[]*:\s*[^\]#\s]+',
121 "linebreak after :"),
121 "linebreak after :"),
122 (r'class\s[^(]:', "old-style class, use class foo(object)"),
122 (r'class\s[^(]:', "old-style class, use class foo(object)"),
123 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
123 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
124 "Python keyword is not a function"),
124 "Python keyword is not a function"),
125 (r',]', "unneeded trailing ',' in list"),
125 (r',]', "unneeded trailing ',' in list"),
126 # (r'class\s[A-Z][^\(]*\((?!Exception)',
126 # (r'class\s[A-Z][^\(]*\((?!Exception)',
127 # "don't capitalize non-exception classes"),
127 # "don't capitalize non-exception classes"),
128 # (r'in range\(', "use xrange"),
128 # (r'in range\(', "use xrange"),
129 # (r'^\s*print\s+', "avoid using print in core and extensions"),
129 # (r'^\s*print\s+', "avoid using print in core and extensions"),
130 (r'[\x80-\xff]', "non-ASCII character literal"),
130 (r'[\x80-\xff]', "non-ASCII character literal"),
131 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
131 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
132 (r'^\s*with\s+', "with not available in Python 2.4"),
132 (r'^\s*with\s+', "with not available in Python 2.4"),
133 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
133 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
134 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
134 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
135 (r'(?<!def)\s+(any|all|format)\(',
135 (r'(?<!def)\s+(any|all|format)\(',
136 "any/all/format not available in Python 2.4"),
136 "any/all/format not available in Python 2.4"),
137 (r'(?<!def)\s+(callable)\(',
137 (r'(?<!def)\s+(callable)\(',
138 "callable not available in Python 3, use hasattr(f, '__call__')"),
138 "callable not available in Python 3, use hasattr(f, '__call__')"),
139 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
139 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
140 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
140 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
141 "gratuitous whitespace after Python keyword"),
141 "gratuitous whitespace after Python keyword"),
142 (r'([\(\[]\s\S)|(\S\s[\)\]])', "gratuitous whitespace in () or []"),
142 (r'([\(\[]\s\S)|(\S\s[\)\]])', "gratuitous whitespace in () or []"),
143 # (r'\s\s=', "gratuitous whitespace before ="),
143 # (r'\s\s=', "gratuitous whitespace before ="),
144 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
144 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
145 "missing whitespace around operator"),
145 "missing whitespace around operator"),
146 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\s',
146 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\s',
147 "missing whitespace around operator"),
147 "missing whitespace around operator"),
148 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
148 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
149 "missing whitespace around operator"),
149 "missing whitespace around operator"),
150 (r'[^+=*!<>&| -](\s=|=\s)[^= ]',
150 (r'[^+=*!<>&| -](\s=|=\s)[^= ]',
151 "wrong whitespace around ="),
151 "wrong whitespace around ="),
152 (r'raise Exception', "don't raise generic exceptions"),
152 (r'raise Exception', "don't raise generic exceptions"),
153 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
153 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
154 "warning: unwrapped ui message"),
154 "warning: unwrapped ui message"),
155 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
155 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
156 (r' [=!]=\s+(True|False|None)',
156 (r' [=!]=\s+(True|False|None)',
157 "comparison with singleton, use 'is' or 'is not' instead"),
157 "comparison with singleton, use 'is' or 'is not' instead"),
158 ]
158 ]
159
159
160 pyfilters = [
160 pyfilters = [
161 (r"""(?msx)(?P<comment>\#.*?$)|
161 (r"""(?msx)(?P<comment>\#.*?$)|
162 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
162 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
163 (?P<text>(([^\\]|\\.)*?))
163 (?P<text>(([^\\]|\\.)*?))
164 (?P=quote))""", reppython),
164 (?P=quote))""", reppython),
165 ]
165 ]
166
166
167 cpats = [
167 cpats = [
168 (r'//', "don't use //-style comments"),
168 (r'//', "don't use //-style comments"),
169 (r'^ ', "don't use spaces to indent"),
169 (r'^ ', "don't use spaces to indent"),
170 (r'\S\t', "don't use tabs except for indent"),
170 (r'\S\t', "don't use tabs except for indent"),
171 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
171 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
172 (r'.{85}', "line too long"),
172 (r'.{85}', "line too long"),
173 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
173 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
174 (r'return\(', "return is not a function"),
174 (r'return\(', "return is not a function"),
175 (r' ;', "no space before ;"),
175 (r' ;', "no space before ;"),
176 (r'\w+\* \w+', "use int *foo, not int* foo"),
176 (r'\w+\* \w+', "use int *foo, not int* foo"),
177 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
177 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
178 (r'\S+ (\+\+|--)', "use foo++, not foo ++"),
178 (r'\S+ (\+\+|--)', "use foo++, not foo ++"),
179 (r'\w,\w', "missing whitespace after ,"),
179 (r'\w,\w', "missing whitespace after ,"),
180 (r'\w[+/*]\w', "missing whitespace in expression"),
180 (r'\w[+/*]\w', "missing whitespace in expression"),
181 (r'^#\s+\w', "use #foo, not # foo"),
181 (r'^#\s+\w', "use #foo, not # foo"),
182 (r'[^\n]\Z', "no trailing newline"),
182 (r'[^\n]\Z', "no trailing newline"),
183 ]
183 ]
184
184
185 cfilters = [
185 cfilters = [
186 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
186 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
187 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
187 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
188 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
188 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
189 (r'(\()([^)]+\))', repcallspaces),
189 (r'(\()([^)]+\))', repcallspaces),
190 ]
190 ]
191
191
192 checks = [
192 checks = [
193 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
193 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
194 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
194 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
195 ('c', r'.*\.c$', cfilters, cpats),
195 ('c', r'.*\.c$', cfilters, cpats),
196 ('unified test', r'.*\.t$', utestfilters, utestpats),
196 ('unified test', r'.*\.t$', utestfilters, utestpats),
197 ]
197 ]
198
198
199 class norepeatlogger(object):
199 class norepeatlogger(object):
200 def __init__(self):
200 def __init__(self):
201 self._lastseen = None
201 self._lastseen = None
202
202
203 def log(self, fname, lineno, line, msg, blame):
203 def log(self, fname, lineno, line, msg, blame):
204 """print error related a to given line of a given file.
204 """print error related a to given line of a given file.
205
205
206 The faulty line will also be printed but only once in the case
206 The faulty line will also be printed but only once in the case
207 of multiple errors.
207 of multiple errors.
208
208
209 :fname: filename
209 :fname: filename
210 :lineno: line number
210 :lineno: line number
211 :line: actual content of the line
211 :line: actual content of the line
212 :msg: error message
212 :msg: error message
213 """
213 """
214 msgid = fname, lineno, line
214 msgid = fname, lineno, line
215 if msgid != self._lastseen:
215 if msgid != self._lastseen:
216 if blame:
216 if blame:
217 print "%s:%d (%s):" % (fname, lineno, blame)
217 print "%s:%d (%s):" % (fname, lineno, blame)
218 else:
218 else:
219 print "%s:%d:" % (fname, lineno)
219 print "%s:%d:" % (fname, lineno)
220 print " > %s" % line
220 print " > %s" % line
221 self._lastseen = msgid
221 self._lastseen = msgid
222 print " " + msg
222 print " " + msg
223
223
224 _defaultlogger = norepeatlogger()
224 _defaultlogger = norepeatlogger()
225
225
226 def getblame(f):
226 def getblame(f):
227 lines = []
227 lines = []
228 for l in os.popen('hg annotate -un %s' % f):
228 for l in os.popen('hg annotate -un %s' % f):
229 start, line = l.split(':', 1)
229 start, line = l.split(':', 1)
230 user, rev = start.split()
230 user, rev = start.split()
231 lines.append((line[1:-1], user, rev))
231 lines.append((line[1:-1], user, rev))
232 return lines
232 return lines
233
233
234 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
234 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
235 blame=False):
235 blame=False):
236 """checks style and portability of a given file
236 """checks style and portability of a given file
237
237
238 :f: filepath
238 :f: filepath
239 :logfunc: function used to report error
239 :logfunc: function used to report error
240 logfunc(filename, linenumber, linecontent, errormessage)
240 logfunc(filename, linenumber, linecontent, errormessage)
241 :maxerr: number of error to display before arborting.
241 :maxerr: number of error to display before arborting.
242 Set to None (default) to report all errors
242 Set to None (default) to report all errors
243
243
244 return True if no error is found, False otherwise.
244 return True if no error is found, False otherwise.
245 """
245 """
246 blamecache = None
246 blamecache = None
247 result = True
247 result = True
248 for name, match, filters, pats in checks:
248 for name, match, filters, pats in checks:
249 fc = 0
249 fc = 0
250 if not re.match(match, f):
250 if not re.match(match, f):
251 continue
251 continue
252 fp = open(f)
252 fp = open(f)
253 pre = post = fp.read()
253 pre = post = fp.read()
254 fp.close()
254 fp.close()
255 if "no-" + "check-code" in pre:
255 if "no-" + "check-code" in pre:
256 break
256 break
257 for p, r in filters:
257 for p, r in filters:
258 post = re.sub(p, r, post)
258 post = re.sub(p, r, post)
259 # print post # uncomment to show filtered version
259 # print post # uncomment to show filtered version
260 z = enumerate(zip(pre.splitlines(), post.splitlines(True)))
260 z = enumerate(zip(pre.splitlines(), post.splitlines(True)))
261 for n, l in z:
261 for n, l in z:
262 if "check-code" + "-ignore" in l[0]:
262 if "check-code" + "-ignore" in l[0]:
263 continue
263 continue
264 for p, msg in pats:
264 for p, msg in pats:
265 if not warnings and msg.startswith("warning"):
265 if not warnings and msg.startswith("warning"):
266 continue
266 continue
267 if re.search(p, l[1]):
267 if re.search(p, l[1]):
268 bd = ""
268 bd = ""
269 if blame:
269 if blame:
270 bd = 'working directory'
270 bd = 'working directory'
271 if not blamecache:
271 if not blamecache:
272 blamecache = getblame(f)
272 blamecache = getblame(f)
273 if n < len(blamecache):
273 if n < len(blamecache):
274 bl, bu, br = blamecache[n]
274 bl, bu, br = blamecache[n]
275 if bl == l[0]:
275 if bl == l[0]:
276 bd = '%s@%s' % (bu, br)
276 bd = '%s@%s' % (bu, br)
277 logfunc(f, n + 1, l[0], msg, bd)
277 logfunc(f, n + 1, l[0], msg, bd)
278 fc += 1
278 fc += 1
279 result = False
279 result = False
280 if maxerr is not None and fc >= maxerr:
280 if maxerr is not None and fc >= maxerr:
281 print " (too many errors, giving up)"
281 print " (too many errors, giving up)"
282 break
282 break
283 break
283 break
284 return result
284 return result
285
285
286 if __name__ == "__main__":
286 if __name__ == "__main__":
287 parser = optparse.OptionParser("%prog [options] [files]")
287 parser = optparse.OptionParser("%prog [options] [files]")
288 parser.add_option("-w", "--warnings", action="store_true",
288 parser.add_option("-w", "--warnings", action="store_true",
289 help="include warning-level checks")
289 help="include warning-level checks")
290 parser.add_option("-p", "--per-file", type="int",
290 parser.add_option("-p", "--per-file", type="int",
291 help="max warnings per file")
291 help="max warnings per file")
292 parser.add_option("-b", "--blame", action="store_true",
292 parser.add_option("-b", "--blame", action="store_true",
293 help="use annotate to generate blame info")
293 help="use annotate to generate blame info")
294
294
295 parser.set_defaults(per_file=15, warnings=False, blame=False)
295 parser.set_defaults(per_file=15, warnings=False, blame=False)
296 (options, args) = parser.parse_args()
296 (options, args) = parser.parse_args()
297
297
298 if len(args) == 0:
298 if len(args) == 0:
299 check = glob.glob("*")
299 check = glob.glob("*")
300 else:
300 else:
301 check = args
301 check = args
302
302
303 for f in check:
303 for f in check:
304 ret = 0
304 ret = 0
305 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
305 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
306 blame=options.blame):
306 blame=options.blame):
307 ret = 1
307 ret = 1
308 sys.exit(ret)
308 sys.exit(ret)
General Comments 0
You need to be logged in to leave comments. Login now