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