Merge lp:~justin-fathomdb/nova/termie-bot into lp:~hudson-openstack/nova/trunk

Proposed by justinsb
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
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

Description of the change

Simple style-checker that can check some of our style rules, and is easy to extend

To post a comment you must log in.
Revision history for this message
Thierry Carrez (ttx) wrote :

I like the idea, but does this need to be shipped in the core code ? It looks a bit developer-only to me.

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
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!

Revision history for this message
Jay Pipes (jaypipes) wrote :

89 +class TwoLinesAfterLog(SimpleRegexChecker):
90 + def _build_regex(self):
91 + self.regex = '\nLOG(\s)*=[^\n]*\n[^\n][^\n]'
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.getLogger(...)
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

Revision history for this message
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.

Revision history for this message
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

Revision history for this message
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_boolean are not. We shouldn't actually have many constants, however, as most actual constants are defined as FLAGS which allows them to be described.

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

Revision history for this message
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.

Revision history for this message
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

Revision history for this message
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_boolean are not. We shouldn't actually have many constants, however, as most actual constants are defined as FLAGS which allows them to be described.
>
> 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

Revision history for this message
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/openstack/_id_translator.py:24:19: E261 at least two spaces before inline comment

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 RethrowExceptionsCarefully needs some adjustment :p

review: Needs Fixing
Revision history for this message
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.

Revision history for this message
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

Revision history for this message
Brian Lamar (blamar) wrote :

Does this validate for itself, out of curiosity?

review: Needs Information
Revision history for this message
justinsb (justin-fathomdb) wrote :

I thought it did, but I can't remember if I ran it on the final version.
Obviously it should :-)

Revision history for this message
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 RethrowExceptionsCarefully rule, because it seems to catch some real issues. We may have to use the trick that the glance guys found to rethrow an exception. We should try not to lose the stack traces if we're going to be able to support this in production.

Revision history for this message
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_BASE_CLASSES does work. Is your concern that the position is fragile?

Revision history for this message
termie (termie) wrote :

we discussed this a bit in irc, any further thoughts on python-vs-java philosophy?

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
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_list('checkers', ['xxx','yyy'], ....)
for checker in checkers:
   obj = utils.import_object(checker) # or equivalent

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.

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
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...

Revision history for this message
Brian Lamar (blamar) :
review: Abstain
Revision history for this message
Vish Ishaya (vishvananda) wrote :

Only 2000+ termie violations!

review: Approve
Revision history for this message
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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()