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