Merge lp:~justin-fathomdb/nova/termie-bot into lp:~hudson-openstack/nova/trunk
- termie-bot
- Merge into trunk
Status: | Work in progress |
---|---|
Proposed branch: | lp:~justin-fathomdb/nova/termie-bot |
Merge into: | lp:~hudson-openstack/nova/trunk |
Diff against target: |
334 lines (+329/-0) 1 file modified
contrib/rules/do_checks.py (+329/-0) |
To merge this branch: | bzr merge lp:~justin-fathomdb/nova/termie-bot |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Vish Ishaya (community) | Approve | ||
Brian Lamar (community) | Abstain | ||
termie (community) | Needs Fixing | ||
Nova Core security contacts | Pending | ||
Review via email: mp+55031@code.launchpad.net |
Commit message
Description of the change
Simple style-checker that can check some of our style rules, and is easy to extend
justinsb (justin-fathomdb) wrote : | # |
Sorry Thierry, I don't really understand. Are you saying that contrib is
shipped, and so it's in the wrong place? Where should it be if so?
Otherwise, we have lots of code for developers-only - this is like pylintrc
or the unit tests. Eventually, we might be able to make this part of the
pre-merge checks.
Thierry Carrez (ttx) wrote : | # |
Maybe I'm overzealous in trying to keep the nova core code focused :)
My point is that the termie-bot is generally useful for openstack projects and is not nova-specific, so maybe it could live outside the nova source tree. Like pep8.
That said, I don't really care, so if the nova-core consensus is that it should live in core code, i'm fine with it.
justinsb (justin-fathomdb) wrote : | # |
I think we have different style rules in nova than in some of the other
projects; let's start this here and see if other projects want to grab and
adapt it to their own styles. We can pull it out in future!
Jay Pipes (jaypipes) wrote : | # |
89 +class TwoLinesAfterLo
90 + def _build_regex(self):
91 + self.regex = '\nLOG(
92 + # We matched a leading \n
93 + self.skip_chars = 1
I know Andy has stated this on your reviews, but this isn't actually part of pep8 nor the HACKING style guide. This:
LOG = logging.
FLAGS = flags.FLAGS
class Blah():
is perfectly acceptable according to both pep8 and HACKING, as far as I read it. In fact, a brief search through many Nova files shows tons of examples like the above.
-jay
justinsb (justin-fathomdb) wrote : | # |
Jay - I'm guessing termie would say it comes under this:
"- thou shalt put two newlines twixt toplevel code (funcs, classes, etc)"
But half the point of this is to get consistency by codifying these rules, as well as to let code reviewers focus on higher level concerns (like the pep8 checker does).
And, yes, this checker does indeed flag a large number of issues in the current code.
Jay Pipes (jaypipes) wrote : | # |
On Mon, Mar 28, 2011 at 12:50 PM, justinsb <email address hidden> wrote:
> Jay - I'm guessing termie would say it comes under this:
>
> "- thou shalt put two newlines twixt toplevel code (funcs, classes, etc)"
>
> But half the point of this is to get consistency by codifying these rules, as well as to let code reviewers focus on higher level concerns (like the pep8 checker does).
If that were what termie meant, I don't think he would have written
half the code in Nova to group top-level class constants together...
Do you REALLY want to see every module-level constant separated by 2 newlines?
-jay
termie (termie) wrote : | # |
> On Mon, Mar 28, 2011 at 12:50 PM, justinsb <email address hidden> wrote:
> > Jay - I'm guessing termie would say it comes under this:
> >
> > "- thou shalt put two newlines twixt toplevel code (funcs, classes, etc)"
> >
> > But half the point of this is to get consistency by codifying these rules,
> as well as to let code reviewers focus on higher level concerns (like the pep8
> checker does).
>
> If that were what termie meant, I don't think he would have written
> half the code in Nova to group top-level class constants together...
>
> Do you REALLY want to see every module-level constant separated by 2 newlines?
>
Yes, though it is more specifically different types of objects, LOG and FLAGS are pretty different where as FLAGS and flags.DEFINE_
I can go either way on LOG specifically, I don't actually like how it is used to begin with, but my interpretation would put two lines between it and FLAGS.
> -jay
termie (termie) wrote : | # |
> Maybe I'm overzealous in trying to keep the nova core code focused :)
>
> My point is that the termie-bot is generally useful for openstack projects and
> is not nova-specific, so maybe it could live outside the nova source tree.
> Like pep8.
>
> That said, I don't really care, so if the nova-core consensus is that it
> should live in core code, i'm fine with it.
I think the definition of what is "core" will shift greatly after the next summit, so I think contrib or tools is a fine place for this at the moment.
Jay Pipes (jaypipes) wrote : | # |
On Mon, Mar 28, 2011 at 1:16 PM, termie <email address hidden> wrote:
>> Maybe I'm overzealous in trying to keep the nova core code focused :)
>>
>> My point is that the termie-bot is generally useful for openstack projects and
>> is not nova-specific, so maybe it could live outside the nova source tree.
>> Like pep8.
>>
>> That said, I don't really care, so if the nova-core consensus is that it
>> should live in core code, i'm fine with it.
>
> I think the definition of what is "core" will shift greatly after the next summit, so I think contrib or tools is a fine place for this at the moment.
Sorry, could you explain this? Statements like the above make it seem
like things have been decided by people, and I'm not familiar with
these decisions.
-jay
Jay Pipes (jaypipes) wrote : | # |
On Mon, Mar 28, 2011 at 1:16 PM, termie <email address hidden> wrote:
>> On Mon, Mar 28, 2011 at 12:50 PM, justinsb <email address hidden> wrote:
>> > Jay - I'm guessing termie would say it comes under this:
>> >
>> > "- thou shalt put two newlines twixt toplevel code (funcs, classes, etc)"
>> >
>> > But half the point of this is to get consistency by codifying these rules,
>> as well as to let code reviewers focus on higher level concerns (like the pep8
>> checker does).
>>
>> If that were what termie meant, I don't think he would have written
>> half the code in Nova to group top-level class constants together...
>>
>> Do you REALLY want to see every module-level constant separated by 2 newlines?
>>
>
> Yes, though it is more specifically different types of objects, LOG and FLAGS are pretty different where as FLAGS and flags.DEFINE_
>
> I can go either way on LOG specifically, I don't actually like how it is used to begin with, but my interpretation would put two lines between it and FLAGS.
I agree that separating different "types of objects" is beneficial.
But LOG and FLAGS are both merely aliases for objects somewhere else.
So their pretty much the same thing in my book.
Something to discuss at the summit, I guess, since it's clear from
looking at the Nova code that the HACKING text is interpreted
(ignored?) differently by different devs.
-jay
termie (termie) wrote : | # |
- copyright and license declarations
- i'd put the CHECKER stuff at the bottom of the file if it needs to be built after stuff. Alternatively you can make a small decorator for the class that automatically adds the class and define the CHECKER global at the top
- you need an if __name__ == '__main__': at the bottom so that people can import this without executing it
- should we make the output look more like pylint?
nova/api/
and it also has the description underneath, though that is an easy extension to make after the fact (just add a method that prints the appropriate rule)
- i can imagine some issues with the 'raise' vs 'raise e' check, while it is usually the case we have some explicit cases right now where we need to 'raise e'
- the docstring for RethrowExceptio
justinsb (justin-fathomdb) wrote : | # |
>
> Something to discuss at the summit, I guess, since it's clear from
> looking at the Nova code that the HACKING text is interpreted
> (ignored?) differently by different devs.
Absolutely... If pep8 finds a violation, it takes me < 1 minute to fix it.
With our rules that pep8 doesn't fix, if I have to go through the push /
propose / "needs fixing' review / move to WIP / fix / push / comment / move
to 'Needs Review' process, it probably takes 10 minutes, and that's ignoring
the reviewers time (unnecessary email to nova-core), and the fact that some
of these violations are likely overlooked. The more we can get into the
automated category, the better in my book - that's what I'm trying to
achieve here.
There's a secondary issue which is that we don't yet agree on all the rules.
When do we get our PTLs? - hopefully they can just make these decisions by
dictat.
termie (termie) wrote : | # |
> On Mon, Mar 28, 2011 at 1:16 PM, termie <email address hidden> wrote:
> >> Maybe I'm overzealous in trying to keep the nova core code focused :)
> >>
> >> My point is that the termie-bot is generally useful for openstack projects
> and
> >> is not nova-specific, so maybe it could live outside the nova source tree.
> >> Like pep8.
> >>
> >> That said, I don't really care, so if the nova-core consensus is that it
> >> should live in core code, i'm fine with it.
> >
> > I think the definition of what is "core" will shift greatly after the next
> summit, so I think contrib or tools is a fine place for this at the moment.
>
> Sorry, could you explain this? Statements like the above make it seem
> like things have been decided by people, and I'm not familiar with
> these decisions.
>
Just talking about a push for openstack-common, which will probably start separating out what is considered nova core, what is openstack common (not required to be used by all but available to all) and what are things built on top of those libraries. Where this stuff ends up after that discussion is up for grabs, either it will remain here or move somewhere else, but here is a good enough place until the discussion occurs.
> -jay
Brian Lamar (blamar) wrote : | # |
Does this validate for itself, out of curiosity?
justinsb (justin-fathomdb) wrote : | # |
I thought it did, but I can't remember if I ran it on the final version.
Obviously it should :-)
justinsb (justin-fathomdb) wrote : | # |
I think this is looking pretty good for a v1:
I added a docstring checker to check some of our rules on docstrings
I added a format flag so that we can output in 'pretty', 'pep8', or 'pylint' style. Pylint should be parseable by the Violations Jenkins plugin (hopefully!).
I left in the RethrowExceptio
justinsb (justin-fathomdb) wrote : | # |
termie: Sorry missed one of your review points. I don't think I really understand this one?
> - i'd put the CHECKER stuff at the bottom of the file if it needs to be built after stuff. Alternatively you can make a small decorator for the class that automatically adds the class and define the CHECKER global at the top
I though Python defined things top-to-bottom, so I think CHECKER_
termie (termie) wrote : | # |
we discussed this a bit in irc, any further thoughts on python-vs-java philosophy?
justinsb (justin-fathomdb) wrote : | # |
I think the Java philosophy is better in this case, and you think the
opposite! Let's ask the PTL mediate this one. If we want it before the
result, let's just get Vish to make the call.
justinsb (justin-fathomdb) wrote : | # |
Vish - I think we need your help as PTL. The barrier is a difference of opinion between termie and myself over how to discover classes that perform checks.
I want to find checks the "Java way", using typing. We create a checker by deriving from Checker, perhaps indirectly. If we want to know if a class is a checker, we check the type.
Termie wants to be more "Pythonic". A Checker simply implements the correct method e.g. run_check. We catch the missing-method exception and assume that the class isn't a checker.
I feel that the Pythonic way doesn't get us very much, but we have to give up help from our compiler & toolset. For example, if we were to rename the 'run_check' method, we'd need to change every Checker. Or a Checker implementation might fat-finger the method name. In both cases, the Java approach gives us lots of help - runtime & static analysis checkers - while the Pythonic approach offers us no help at all. I just don't see what we get in return.
In general, this is what we see throughout the code base, where we don't have a well-defined contract, so different implementations don't work homogeneously and we have lots of bugs that we wouldn't have if we allowed our toolset to help us.
Vish Ishaya (vishvananda) wrote : | # |
I have to say that I find all the logic around BASE_CLASSES a bit ugly. I don't really see the disadvantage of switching to pythonic style here. You can still make all of them subclasses, but it seems a lot easier to go:
try:
obj.do_check()
except NotImplemented, AttributeError:
pass
then doing all of the type checking. Getting a list of candidate classes i am less concerned with, but the global variable with skipped classes is a little odd. Since classes could be defined in other files, one possibility:
flags.DEFINE_
for checker in checkers:
obj = utils.import_
I'm a big fan of using inheritance for polymorphism and shared code, but basing runtime checks on class information should be avoided where possible.
justinsb (justin-fathomdb) wrote : | # |
OK, your call as PTL. I hate any code that hides exceptions, but it's
not me that has to support it...
I'll make it Pythonic.
Brian Lamar (blamar) wrote : | # |
I agree that hiding errors are bad, but we can just as easily replace that 'pass' with an informative log statement or relevant raise if needed. I'm pretty sure Vish was just trying to show the general form.
justinsb (justin-fathomdb) wrote : | # |
The whole point is that we can't do anything other than swallow the
exception, because we don't know if the class is supposed to be a
Checker or not...
Vish Ishaya (vishvananda) wrote : | # |
Only 2000+ termie violations!
Todd Willey (xtoddx) wrote : | # |
I'd like to have it bitch at me if I don't give it paths. Other than that I think it is good to go.
Unmerged revisions
- 911. By justinsb
-
Made discovery of checkers Pythonic
- 910. By justinsb
-
Merged with trunk
- 909. By justinsb
-
Remove regex for finding docstrings, replace with "my first parser" level code.
The regex was hanging on certain inputs, and also got confused by embedded quotes in docstrings
- 908. By justinsb
-
Fixed the checker to be checker-compliant
- 907. By justinsb
-
Code style cleanups
- 906. By justinsb
-
pep8 fixes
- 905. By justinsb
-
Check that multi-line docstrings end in a blank line
- 904. By justinsb
-
Fix pep8
- 903. By justinsb
-
Basic docstring checking
- 902. By justinsb
-
Added pep8, pylint, pretty output formats
Preview Diff
1 | === added directory 'contrib/rules' |
2 | === added file 'contrib/rules/do_checks.py' |
3 | --- contrib/rules/do_checks.py 1970-01-01 00:00:00 +0000 |
4 | +++ contrib/rules/do_checks.py 2011-05-20 05:15:12 +0000 |
5 | @@ -0,0 +1,329 @@ |
6 | +#!/usr/bin/env python |
7 | +# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
8 | + |
9 | +# Copyright 2011 Justin Santa Barbara |
10 | +# All Rights Reserved. |
11 | +# |
12 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
13 | +# not use this file except in compliance with the License. You may obtain |
14 | +# a copy of the License at |
15 | +# |
16 | +# http://www.apache.org/licenses/LICENSE-2.0 |
17 | +# |
18 | +# Unless required by applicable law or agreed to in writing, software |
19 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
20 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
21 | +# License for the specific language governing permissions and limitations |
22 | +# under the License. |
23 | + |
24 | + |
25 | +import gflags |
26 | +import imp |
27 | +import os |
28 | +import re |
29 | +import sys |
30 | + |
31 | +import __main__ |
32 | + |
33 | + |
34 | +FLAGS = gflags.FLAGS |
35 | +gflags.DEFINE_enum('format', 'pretty', ['pretty', 'pylint', 'pep8'], |
36 | + 'Output format to use') |
37 | + |
38 | + |
39 | +class SourceFile(object): |
40 | + """A source code file.""" |
41 | + |
42 | + def __init__(self, path): |
43 | + self.path = path |
44 | + |
45 | + f = open(path, 'r') |
46 | + code = f.read() |
47 | + code = code.replace('\r\n', '\n') |
48 | + code = code.replace('\n\r', '\n') |
49 | + self.code = code |
50 | + self.lines = code.splitlines() |
51 | + f.close() |
52 | + self.module = None |
53 | + |
54 | + def get_module(self): |
55 | + # NOTE(justinsb): This would be nice, but causes errors because the |
56 | + # code is actually executed (e.g. flags) |
57 | + if self.module is None: |
58 | + mod_name, _ext = os.path.splitext(os.path.split(self.path)[-1]) |
59 | + self.module = imp.load_source(mod_name, self.path) |
60 | + return self.module |
61 | + |
62 | + def map_to_line_index(self, index): |
63 | + line_index = 0 |
64 | + for i in xrange(index): |
65 | + if self.code[i] == '\n': |
66 | + line_index = line_index + 1 |
67 | + return line_index |
68 | + |
69 | + |
70 | +class Violation(object): |
71 | + """Records a rule infraction.""" |
72 | + |
73 | + def __init__(self, src, index, tag, message): |
74 | + self.src = src |
75 | + self.index = index |
76 | + self.tag = tag |
77 | + self.message = message |
78 | + |
79 | + def print_warning(self): |
80 | + if FLAGS.format == 'pylint': |
81 | + self.print_pylint() |
82 | + elif FLAGS.format == 'pep8': |
83 | + self.print_pep8() |
84 | + else: |
85 | + self.print_pretty() |
86 | + |
87 | + def print_pylint(self): |
88 | + line = self.src.map_to_line_index(self.index) |
89 | + level = 'C' |
90 | + description = self.tag + ' ' + self.message |
91 | + print "%s:%s: [%s] %s" % (self.src.path, line + 1, level, description) |
92 | + |
93 | + def print_pep8(self): |
94 | + line = self.src.map_to_line_index(self.index) |
95 | + level = 'E' |
96 | + column = 1 |
97 | + errnum = self.tag |
98 | + description = self.message |
99 | + print ("%s:%s:%s: %s%s %s" % |
100 | + (self.src.path, line + 1, column, level, errnum, description)) |
101 | + |
102 | + def print_pretty(self): |
103 | + line = self.src.map_to_line_index(self.index) |
104 | + print "%s %s %s" % (self.tag, self.src.path, line + 1) |
105 | + if self.message != self.tag: |
106 | + print "\t%s" % (self.message) |
107 | + for i in xrange(line - 2, line + 3): |
108 | + if i < 0 or i >= len(self.src.lines): |
109 | + continue |
110 | + if i == line: |
111 | + prefix = "* " |
112 | + else: |
113 | + prefix = " " |
114 | + print prefix + self.src.lines[i] |
115 | + |
116 | + |
117 | +class Checker(object): |
118 | + """Base class for all rule-checkers.""" |
119 | + |
120 | + def __init__(self, src): |
121 | + self.src = src |
122 | + |
123 | + def do_check(self): |
124 | + raise NotImplementedError() |
125 | + |
126 | + def _build_error(self, index, message=None): |
127 | + tag = self.__class__.__name__ |
128 | + |
129 | + if message is None: |
130 | + message = self.__class__.__doc__ |
131 | + if message is None: |
132 | + message = tag |
133 | + return Violation(self.src, index, tag, message) |
134 | + |
135 | + |
136 | +class DocStringChecker(Checker): |
137 | + """Checks docstrings against our style.""" |
138 | + |
139 | + def do_check(self): |
140 | + errors = [] |
141 | + |
142 | + # NOTE(justinsb): We hit a bug in re module: the below regex hangs |
143 | + # on some inputs. In addition, it gets confused by quotes in docs. |
144 | + #regex = ':(\s)*"""((\s|[^"])*?)"""' |
145 | + #matches = re.finditer(regex, self.src.code) |
146 | + |
147 | + code = self.src.code |
148 | + start_pos = 0 |
149 | + |
150 | + while True: |
151 | + docstring_start = code.find('"""', start_pos) |
152 | + if docstring_start == -1: |
153 | + break |
154 | + |
155 | + docstring_end = code.find('"""', docstring_start + 3) |
156 | + if docstring_end == -1: |
157 | + break |
158 | + docstring_end += 3 |
159 | + docstring = code[docstring_start:docstring_end] |
160 | + |
161 | + errors += self._check_docstring(docstring_start, docstring) |
162 | + |
163 | + start_pos = docstring_end + 3 |
164 | + |
165 | + return errors |
166 | + |
167 | + def _check_docstring(self, index, text): |
168 | + errors = [] |
169 | + assert text.startswith('"""') |
170 | + assert text.endswith('"""') |
171 | + text = text[3:] |
172 | + text = text[:-3] |
173 | + |
174 | + lines = text.splitlines() |
175 | + |
176 | + if len(lines) == 0: |
177 | + message = "Docstrings should not be empty" |
178 | + errors.append(self._build_error(index, message=message)) |
179 | + |
180 | + else: |
181 | + if lines[0].startswith(' '): |
182 | + message = "Docstrings do not start with whitespace" |
183 | + errors.append(self._build_error(index, message=message)) |
184 | + |
185 | + if lines[0].endswith(' '): |
186 | + message = "Docstrings should not end with whitespace" |
187 | + errors.append(self._build_error(index, message=message)) |
188 | + |
189 | + if len(lines) == 1: |
190 | + # Single line docstring |
191 | + if not lines[0].endswith('.'): |
192 | + message = "A single line docstring ends in a full stop (.)" |
193 | + errors.append(self._build_error(index, message=message)) |
194 | + |
195 | + if len(lines) > 1: |
196 | + # Multi-line docstring |
197 | + if not lines[0].endswith('.'): |
198 | + message = ("The first line of a multi-line docstring ends in " |
199 | + "a full stop (.)") |
200 | + errors.append(self._build_error(index, message=message)) |
201 | + |
202 | + if lines[1]: |
203 | + message = ("The second line of a multi-line docstring must " |
204 | + "be blank") |
205 | + errors.append(self._build_error(index, message=message)) |
206 | + |
207 | + if lines[-2] or lines[-1].strip(): |
208 | + message = ("Leave a blank line at the end of a multi-line " |
209 | + "docstring") |
210 | + errors.append(self._build_error(index, message=message)) |
211 | + |
212 | + # TODO(justinsb): On a class docstring, there should be a blank line |
213 | + # after the closing """ |
214 | + |
215 | + return errors |
216 | + |
217 | + |
218 | +class SimpleRegexChecker(Checker): |
219 | + """Base class for checkers that find violations based on regex.""" |
220 | + |
221 | + def __init__(self, src, regex=None, skip_chars=0): |
222 | + super(SimpleRegexChecker, self).__init__(src) |
223 | + self.regex = regex |
224 | + self.skip_chars = skip_chars |
225 | + |
226 | + def _build_regex(self): |
227 | + raise NotImplementedError() |
228 | + |
229 | + def do_check(self): |
230 | + if not self.regex: |
231 | + self._build_regex() |
232 | + |
233 | + errors = [] |
234 | + matches = re.finditer(self.regex, self.src.code) |
235 | + for match in matches: |
236 | + index = match.start() |
237 | + |
238 | + index = index + self.skip_chars |
239 | + |
240 | + errors.append(self._build_error(index)) |
241 | + |
242 | + return errors |
243 | + |
244 | + |
245 | +class TwoLinesAfterLog(SimpleRegexChecker): |
246 | + """After a LOG statement, leave two blank lines before the FLAGS/code.""" |
247 | + |
248 | + def _build_regex(self): |
249 | + self.regex = '\nLOG(\s)*=[^\n]*\n[^\n][^\n]' |
250 | + # We matched a leading \n |
251 | + self.skip_chars = 1 |
252 | + |
253 | + |
254 | +class RethrowExceptionsCarefully(SimpleRegexChecker): |
255 | + """When rethrowing an exception, do 'raise', not 'raise e'. |
256 | + |
257 | + This preserves the stack trace. Note that there are some complexities |
258 | + with eventlet, because eventlet can wipe the 'current' exception. |
259 | + |
260 | + """ |
261 | + |
262 | + def _build_regex(self): |
263 | + self.regex = '\n[ ]*raise([ ]+)([a-z]+)\n' |
264 | + self.skip_chars = 1 |
265 | + |
266 | + |
267 | +class NoWhiteSpaceLeadingDocString(SimpleRegexChecker): |
268 | + """Don't start your docstrings with whitespace.""" |
269 | + |
270 | + def _build_regex(self): |
271 | + self.regex = '\):\n(\s)*"""\s' |
272 | + self.skip_chars = 3 |
273 | + |
274 | + |
275 | +class AvoidAggressivePunctuation(SimpleRegexChecker): |
276 | + """Punctuation that includes repeated question marks can upset people.""" |
277 | + |
278 | + def _build_regex(self): |
279 | + self.regex = '\?\?' |
280 | + |
281 | + |
282 | +class OneSpaceInTodoOrNotes(SimpleRegexChecker): |
283 | + """Use one space in your TODO and NOTE comments.""" |
284 | + |
285 | + def _build_regex(self): |
286 | + self.regex = '#(TODO|NOTE)' |
287 | + |
288 | + |
289 | +class ClaimYourTodoAndNotes(SimpleRegexChecker): |
290 | + """Put your name in brackets after a TODO/NOTE.""" |
291 | + |
292 | + def _build_regex(self): |
293 | + self.regex = '(TODO|NOTE)(\s*):' |
294 | + |
295 | + |
296 | +def check_file(src_path): |
297 | + src = SourceFile(src_path) |
298 | + |
299 | + for x in dir(__main__): |
300 | + s = getattr(__main__, x) |
301 | + if not hasattr(s, 'do_check'): |
302 | + continue |
303 | + |
304 | + try: |
305 | + checker = s(src) |
306 | + errors = checker.do_check() |
307 | + for error in errors: |
308 | + error.print_warning() |
309 | + except NotImplementedError, AttributeError: |
310 | + pass |
311 | + |
312 | + |
313 | +def check_dir(dir_path): |
314 | + for child_name in os.listdir(dir_path): |
315 | + child_path = os.path.join(dir_path, child_name) |
316 | + if os.path.isfile(child_path): |
317 | + if child_path.endswith(".py"): |
318 | + check_file(child_path) |
319 | + else: |
320 | + check_dir(child_path) |
321 | + |
322 | + |
323 | +def do_main(): |
324 | + args = FLAGS(sys.argv) |
325 | + |
326 | + for path in args[1:]: |
327 | + if os.path.isdir(path): |
328 | + check_dir(path) |
329 | + else: |
330 | + check_file(path) |
331 | + |
332 | + |
333 | +if __name__ == '__main__': |
334 | + do_main() |
I like the idea, but does this need to be shipped in the core code ? It looks a bit developer-only to me.