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