Commits

Chris Adams committed 6c1f74e Merge
  • Participants
  • Parent commits 05546b0, 3a3792f

Comments (0)

Files changed (17)

 lib
 include
 .*\.pyc$
+dist
+bin
+flake8.egg-info
+man
+\.Python
 b04dcad949573b1cef99392cf66caa5c55a0f930 0.8
 6403df46028ffe602f5834743099c5160938aa6b 0.9
 f49f8afba1da337074eb1dbb911ad0ec2a2c6199 1.0
+33104f05ce6c430c27b6414d85e37a88468e6aff 1.1
+575a782a8fb5d42e87954fd0a9253ffae6268023 1.2
+575a782a8fb5d42e87954fd0a9253ffae6268023 1.2
+de690f0eb4029802d6dc67ab7e1760a914d3eb0c 1.2
+0407b6714ca42766edea6f3b17e183cac8fa596b 1.3
+c522d468c5b86329a8b562ca7e392e544a45fffa 1.3.1

File CONTRIBUTORS.txt

 Project created by Tarek Ziadé.
 
-Contributors:
+Contributors (by order of appearence) :
 
 - Tamás Gulácsi
 - Nicolas Dumazet
 - Stefan Scherfke
 - Chris Adams
 - Ben Bass
+- Ask Solem
+- Steven Kryskalla
+- Gustavo Picon
+- Jannis Leidel
+== Flake8 License (MIT) ==
+
+Copyright (C) 2011 Tarek Ziade <tarek@ziade.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
+
+== McCabe License (MIT) ==
+
+Copyright (C) <year> Ned Batchelder
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
+== PyFlakes license (MIT) ==
 
 Copyright (c) 2005 Divmod, Inc., http://www.divmod.com/
 
 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+== pep8 license (expat) ==
+
+Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
 
 - PyFlakes
 - pep8
-- Ned's MacCabe script
+- Ned's McCabe script
 
 Flake8 runs all tools by launching the single 'flake8' script, but ignores pep8
 and PyFlakes extended options and just uses defaults. It displays the warnings
 
 It also adds a few features:
 
-- files that starts with this header are skipped::
+- files that contains with this header are skipped::
 
     # flake8: noqa
 
 The output of PyFlakes *and* pep8 is merged and returned.
 
 flake8 offers an extra option: --max-complexity, which will emit a warning if the
-McCabe complexityu of a function is higher that the value. By default it's 
+McCabe complexityu of a function is higher that the value. By default it's
 deactivated::
 
     $ bin/flake8 --max-complexity 12 flake8
     coolproject/mod.py:1204:1: 'selftest' is too complex (14)
 
 This feature is quite useful to detect over-complex code. According to McCabe, anything
-that goes beyond 10 is too complex. 
+that goes beyond 10 is too complex.
 See https://en.wikipedia.org/wiki/Cyclomatic_complexity.
 
 
 If *strict* option is set to **1**, any warning will block the commit. When
 *strict* is set to **0**, warnings are just displayed in the standard output.
 
-*complexity* defines the maximum McCabe complexity allowed before a warning 
+*complexity* defines the maximum McCabe complexity allowed before a warning
 is emited. If you don't specify it it's just ignored. If specified, must
 be a positive value. 12 is usually a good value.
 
+Git hook
+========
+
+To use the Git hook on any *commit*, add a **pre-commit** file in the
+*.git/hooks* directory containing::
+
+    #!/usr/bin/python
+    import sys
+    from flake8.run import git_hook
+
+    COMPLEXITY = 10
+    STRICT = False
+
+    if __name__ == '__main__':
+        sys.exit(git_hook(complexity=COMPLEXITY, strict=STRICT))
+
+
+If *strict* option is set to **True**, any warning will block the commit. When
+*strict* is set to **False** or omited, warnings are just displayed in the
+standard output.
+
+*complexity* defines the maximum McCabe complexity allowed before a warning
+is emited. If you don't specify it or set it to **-1**, it's just ignored.
+If specified, it must be a positive value. 12 is usually a good value.
+
+Also, make sure the file is executable and adapt the shebang line so it
+point to your python interpreter.
+
+
+Buildout integration
+=====================
+
+In order to use Flake8 inside a buildout, edit your buildout.cfg and add this::
+
+    [buildout]
+
+    parts +=
+        ...
+        flake8
+
+    [flake8]
+    recipe = zc.recipe.egg
+    eggs = flake8
+           ${buildout:eggs}
+    entry-points =
+        flake8=flake8.run:main
 
 Original projects
 =================
 - PyFlakes: http://divmod.org/trac/wiki/DivmodPyflakes
 - McCabe: http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html
 
+Warning / Error codes
+=====================
+
+Below are lists of all warning and error codes flake8 will generate, broken
+out by component.
+
+pep8:
+
+- E101: indentation contains mixed spaces and tabs
+- E111: indentation is not a multiple of four
+- E112: expected an indented block
+- E113: unexpected indentation
+- E201: whitespace after char
+- E202: whitespace before char
+- E203: whitespace before char
+- E211: whitespace before text
+- E223: tab / multiple spaces before operator
+- E224: tab / multiple spaces after operator
+- E225: missing whitespace around operator
+- E225: missing whitespace around operator
+- E231: missing whitespace after char
+- E241: multiple spaces after separator
+- E242: tab after separator
+- E251: no spaces around keyword / parameter equals
+- E262: inline comment should start with '# '
+- E301: expected 1 blank line, found 0
+- E302: expected 2 blank lines, found <n>
+- E303: too many blank lines (<n>)
+- E304: blank lines found after function decorator
+- E401: multiple imports on one line
+- E501: line too long (<n> characters)
+- E701: multiple statements on one line (colon)
+- E702: multiple statements on one line (semicolon)
+- W191: indentation contains tabs
+- W291: trailing whitespace
+- W292: no newline at end of file
+- W293: blank line contains whitespace
+- W391: blank line at end of file
+- W601: .has_key() is deprecated, use 'in'
+- W602: deprecated form of raising exception
+- W603: '<>' is deprecated, use '!='
+- W604: backticks are deprecated, use 'repr()'
+
+pyflakes:
+
+- W402: <module> imported but unused
+- W403: import <module> from line <n> shadowed by loop variable
+- W404: 'from <module> import ``*``' used; unable to detect undefined names
+- W405: future import(s) <name> after other statements
+- W801: redefinition of unused <name> from line <n>
+- W802: undefined name <name>
+- W803: undefined name <name> in __all__
+- W804: local variable <name> (defined in enclosing scope on line <n>) referenced before assignment
+- W805: duplicate argument <name> in function definition
+- W806: redefinition of function <name> from line <n>
+- W806: local variable <name> is assigned to but never used
+
+McCabe:
+
+- W901: '<function_name>' is too complex ('<complexity_level>')
 
 CHANGES
 =======
 
-1.1 - ?
+1.4 - ?
 -------
 
 ?
 
+1.3.1 - 2012-05-19
+------------------
+
+- fixed support for Python 2.5
+
+
+1.3 - 2012-03-12
+----------------
+
+- fixed false W402 warning on exception blocks.
+
+
+1.2 - 2012-02-12
+----------------
+
+- added a git hook
+- now python 3 compatible 
+- mccabe and pyflakes have warning codes like pep8 now
+
+
+1.1 - 2012-02-14
+----------------
+
+- fixed the value returned by --version
+- allow the flake8: header to be more generic
+- fixed the "hg hook raises 'physical lines'" bug
+- allow three argument form of raise
+- now uses setuptools if available, for 'develop' command
+
 1.0 - 2011-11-29
 ----------------
 

File bin/flake8

-#!/home/tarek/dev/bitbucket.org/flake8-clean/bin/python
-from flake8.run import main
-
-if __name__ == '__main__':
-    main()

File flake8/__init__.py

 #
+
+__version__ = '1.4'

File flake8/flake8

+#!/home/tarek/dev/bitbucket.org/flake8-clean/bin/python
+from flake8.run import main
+
+if __name__ == '__main__':
+    main()

File flake8/mccabe.py

     http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html
     MIT License.
 """
-import compiler
+try:
+    from compiler import parse      # NOQA
+    iter_child_nodes = None         # NOQA
+except ImportError:
+    from ast import parse, iter_child_nodes     # NOQA
+
 import optparse
 import sys
+from collections import defaultdict
+
+WARNING_CODE = "W901"
+
+
+class ASTVisitor:
+
+    VERBOSE = 0
+
+    def __init__(self):
+        self.node = None
+        self._cache = {}
+
+    def default(self, node, *args):
+        if hasattr(node, 'getChildNodes'):
+            children = node.getChildNodes()
+        else:
+            children = iter_child_nodes(node)
+
+        for child in children:
+            self.dispatch(child, *args)
+
+    def dispatch(self, node, *args):
+        self.node = node
+        klass = node.__class__
+        meth = self._cache.get(klass)
+        if meth is None:
+            className = klass.__name__
+            meth = getattr(self.visitor, 'visit' + className, self.default)
+            self._cache[klass] = meth
+
+        return meth(node, *args)
+
+    def preorder(self, tree, visitor, *args):
+        """Do preorder walk of tree using visitor"""
+        self.visitor = visitor
+        visitor.visit = self.dispatch
+        self.dispatch(tree, *args)  # XXX *args make sense?
 
 
 class PathNode:
         self.look = look
 
     def to_dot(self):
-        print 'node [shape=%s,label="%s"] %d;' % \
-                (self.look, self.name, self.dot_id())
+        print('node [shape=%s,label="%s"] %d;' % \
+                (self.look, self.name, self.dot_id()))
 
     def dot_id(self):
         return id(self)
 
 
 class PathGraph:
-    def __init__(self, name):
+    def __init__(self, name, entity, lineno):
         self.name = name
-        self.nodes = {}
-
-    def add_node(self, n):
-        assert n
-        self.nodes.setdefault(n, [])
+        self.entity = entity
+        self.lineno = lineno
+        self.nodes = defaultdict(list)
 
     def connect(self, n1, n2):
-        assert n1
-        assert n2
-        self.nodes.setdefault(n1, []).append(n2)
+        self.nodes[n1].append(n2)
 
     def to_dot(self):
-        print 'subgraph {'
+        print('subgraph {')
         for node in self.nodes:
             node.to_dot()
         for node, nexts in self.nodes.items():
             for next in nexts:
-                print '%s -- %s;' % (node.dot_id(), next.dot_id())
-        print '}'
+                print('%s -- %s;' % (node.dot_id(), next.dot_id()))
+        print('}')
 
     def complexity(self):
         """ Return the McCabe complexity for the graph.
         return num_edges - num_nodes + 2
 
 
-class PathGraphingAstVisitor(compiler.visitor.ASTVisitor):
+class PathGraphingAstVisitor(ASTVisitor):
     """ A visitor for a parsed Abstract Syntax Tree which finds executable
         statements.
     """
 
     def __init__(self):
-        compiler.visitor.ASTVisitor.__init__(self)
+        ASTVisitor.__init__(self)
         self.classname = ""
         self.graphs = {}
         self.reset()
             self.graph.connect(pathnode, bottom)
             self.tail = bottom
         else:
-            self.graph = PathGraph(name)
+            self.graph = PathGraph(name, entity, node.lineno)
             pathnode = PathNode(name)
             self.tail = pathnode
             self.default(node)
             self.graphs["%s%s" % (self.classname, node.name)] = self.graph
             self.reset()
 
+    visitFunctionDef = visitFunction
+
     def visitClass(self, node):
         old_classname = self.classname
         self.classname += node.name + "."
         if not self.tail:
             return
         pathnode = PathNode(name)
-        self.graph.add_node(pathnode)
         self.graph.connect(self.tail, pathnode)
         self.tail = pathnode
         return pathnode
 
         if self.graph is None:
             # global loop
-            self.graph = PathGraph(name)
+            self.graph = PathGraph(name, name, node.lineno)
             pathnode = PathNode(name)
             self.tail = pathnode
             self.default(node)
     # TODO: visitTryFinally
     # TODO: visitWith
 
+    # XXX todo: determine which ones can add to the complexity
+    # py2
+    # TODO: visitStmt
+    # TODO: visitAssName
+    # TODO: visitCallFunc
+    # TODO: visitConst
+
+    # py3
+    # TODO: visitStore
+    # TODO: visitCall
+    # TODO: visitLoad
+    # TODO: visitNum
+    # TODO: visitarguments
+    # TODO: visitExpr
+
 
 def get_code_complexity(code, min=7, filename='stdin'):
     complex = []
     try:
-        ast = compiler.parse(code)
-    except AttributeError as e:
-        print >> sys.stderr, "Unable to parse %s: %s" % (filename, e)
+        ast = parse(code)
+    except AttributeError:
+        e = sys.exc_info()[1]
+        sys.stderr.write("Unable to parse %s: %s\n" % (filename, e))
         return 0
 
     visitor = PathGraphingAstVisitor()
             # ?
             continue
         if graph.complexity() >= min:
-            msg = '%s:%s is too complex (%d)' % (filename,
-                    graph.name, graph.complexity())
+            msg = '%s:%d:1: %s %r is too complex (%d)' % (
+                filename,
+                graph.lineno,
+                WARNING_CODE,
+                graph.entity,
+                graph.complexity(),
+            )
             complex.append(msg)
 
     if len(complex) == 0:
     options, args = opar.parse_args(argv)
 
     text = open(args[0], "rU").read() + '\n\n'
-    ast = compiler.parse(text)
+    ast = parse(text)
     visitor = PathGraphingAstVisitor()
     visitor.preorder(ast, visitor)
 
     if options.dot:
-        print 'graph {'
+        print('graph {')
         for graph in visitor.graphs.values():
             if graph.complexity() >= options.min:
                 graph.to_dot()
-        print '}'
+        print('}')
     else:
         for graph in visitor.graphs.values():
             if graph.complexity() >= options.min:
-                print graph.name, graph.complexity()
+                print(graph.name, graph.complexity())
 
 
 if __name__ == '__main__':

File flake8/messages.py

         return '%s:%s: %s' % (self.filename, self.lineno,
                               self.message % self.message_args)
 
+    def __lt__(self, other):
+        if self.filename != other.filename:
+            return self.filename < other.filename
+        return self.lineno < other.lineno
+
 
 class UnusedImport(Message):
-    message = '%r imported but unused'
+    message = 'W402 %r imported but unused'
 
     def __init__(self, filename, lineno, name):
         Message.__init__(self, filename, lineno)
 
 
 class RedefinedWhileUnused(Message):
-    message = 'redefinition of unused %r from line %r'
+    message = 'W801 redefinition of unused %r from line %r'
 
     def __init__(self, filename, lineno, name, orig_lineno):
         Message.__init__(self, filename, lineno)
 
 
 class ImportShadowedByLoopVar(Message):
-    message = 'import %r from line %r shadowed by loop variable'
+    message = 'W403 import %r from line %r shadowed by loop variable'
 
     def __init__(self, filename, lineno, name, orig_lineno):
         Message.__init__(self, filename, lineno)
 
 
 class ImportStarUsed(Message):
-    message = "'from %s import *' used; unable to detect undefined names"
+    message = "W404 'from %s import *' used; unable to detect undefined names"
 
     def __init__(self, filename, lineno, modname):
         Message.__init__(self, filename, lineno)
 
 
 class UndefinedName(Message):
-    message = 'undefined name %r'
+    message = 'W802 undefined name %r'
 
     def __init__(self, filename, lineno, name):
         Message.__init__(self, filename, lineno)
 
 
 class UndefinedExport(Message):
-    message = 'undefined name %r in __all__'
+    message = 'W803 undefined name %r in __all__'
 
     def __init__(self, filename, lineno, name):
         Message.__init__(self, filename, lineno)
 
 
 class UndefinedLocal(Message):
-    message = "local variable %r (defined in enclosing scope on line %r) " \
-            "referenced before assignment"
+    message = "W804 local variable %r (defined in enclosing scope on line " \
+            "%r) referenced before assignment"
 
     def __init__(self, filename, lineno, name, orig_lineno):
         Message.__init__(self, filename, lineno)
 
 
 class DuplicateArgument(Message):
-    message = 'duplicate argument %r in function definition'
+    message = 'W805 duplicate argument %r in function definition'
 
     def __init__(self, filename, lineno, name):
         Message.__init__(self, filename, lineno)
 
 
 class RedefinedFunction(Message):
-    message = 'redefinition of function %r from line %r'
+    message = 'W806 redefinition of function %r from line %r'
 
     def __init__(self, filename, lineno, name, orig_lineno):
         Message.__init__(self, filename, lineno)
 
 
 class LateFutureImport(Message):
-    message = 'future import(s) %r after other statements'
+    message = 'W405 future import(s) %r after other statements'
 
     def __init__(self, filename, lineno, names):
         Message.__init__(self, filename, lineno)
     used.
     """
 
-    message = 'local variable %r is assigned to but never used'
+    message = 'W806 local variable %r is assigned to but never used'
 
     def __init__(self, filename, lineno, names):
         Message.__init__(self, filename, lineno)

File flake8/pep8.py

 for space.
 
 """
+from flake8 import __version__ as flake8_version
+from flake8.pyflakes import __version__ as pep8_version
 
 __version__ = '0.6.0'
 
 
 INDENT_REGEX = re.compile(r'([ \t]*)')
 RAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*(,)')
+QUOTED_REGEX = re.compile(r"""([""'])(?:(?=(\\?))\2.)*?\1""")
 SELFTEST_REGEX = re.compile(r'(Okay|[EW]\d{3}):\s(.*)')
 ERRORCODE_REGEX = re.compile(r'[EW]\d{3}')
 DOCSTRING_REGEX = re.compile(r'u?r?["\']')
     """
     match = RAISE_COMMA_REGEX.match(logical_line)
     if match:
-        return match.start(1), "W602 deprecated form of raising exception"
+        rest = QUOTED_REGEX.sub("", logical_line)
+        # but allow three argument form of raise
+        if rest.count(",") == 1:
+            return match.start(1), "W602 deprecated form of raising exception"
 
 
 def python_3000_not_equal(logical_line):
     Process options passed either via arglist or via command line args.
     """
     global options, args
-    parser = OptionParser(version=__version__,
+    version = '%s (pyflakes: %s, pep8: %s)' % (flake8_version, pep8_version,
+            __version__)
+    parser = OptionParser(version=version,
                           usage="%prog [options] input ...")
+    parser.add_option('--builtins', default=[], action="append",
+                      help="append builtin function (pyflakes "
+                           "_MAGIC_GLOBALS)")
     parser.add_option('--max-complexity', default=-1, action='store',
                       type='int', help="McCabe complexity treshold")
     parser.add_option('-v', '--verbose', default=0, action='count',
                       help="exclude files or directories which match these "
                         "comma separated patterns (default: %s)" %
                         DEFAULT_EXCLUDE)
+    parser.add_option('--exit-zero', action='store_true',
+                      help="use exit code 0 (success), even if there are "
+                        "warnings")
     parser.add_option('--filename', metavar='patterns', default='*.py',
                       help="when parsing directories, only check filenames "
                         "matching these comma separated patterns (default: "

File flake8/pyflakes.py

 # (c) 2005-2010 Divmod, Inc.
 # See LICENSE file for details
 
-import __builtin__
+try:
+    import __builtin__      # NOQA
+except ImportError:
+    import builtins as __builtin__  # NOQA
+
 import os.path
 import _ast
 import sys
 from flake8 import messages
 from flake8.util import skip_warning
 
+__version__ = '0.5.0'
+
 # utility function to iterate over an AST node's children, adapted
 # from Python 2.6's standard ast module
 try:
                 all = []
 
             # Look for imported names that aren't used.
-            for importation in scope.itervalues():
+            for importation in scope.values():
                 if isinstance(importation, Importation):
                     if not importation.used and importation.name not in all:
                         self.report(
     def handleNode(self, node, parent):
         node.parent = parent
         if self.traceTree:
-            print '  ' * self.nodeDepth + node.__class__.__name__
+            print('  ' * self.nodeDepth + node.__class__.__name__)
         self.nodeDepth += 1
         if self.futuresAllowed and not \
                (isinstance(node, _ast.ImportFrom) or self.isDocstring(node)):
         finally:
             self.nodeDepth -= 1
         if self.traceTree:
-            print '  ' * self.nodeDepth + 'end ' + node.__class__.__name__
+            print('  ' * self.nodeDepth + 'end ' + node.__class__.__name__)
 
     def ignore(self, node):
         pass
     EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = ignore
 
     # additional node types
-    COMPREHENSION = EXCEPTHANDLER = KEYWORD = handleChildren
+    COMPREHENSION = KEYWORD = handleChildren
+
+    def EXCEPTHANDLER(self, node):
+
+        if node.name is not None:
+            if isinstance(node.name, str):
+                name = node.name
+            else:
+                name = node.name.id
+            self.addBinding(node.lineno, Assignment(name, node))
+
+        def runException():
+            for stmt in iter_child_nodes(node):
+                self.handleNode(stmt, node)
+
+        self.deferFunction(runException)
 
     def addBinding(self, lineno, value, reportRedef=True):
         '''Called when a binding is altered.
                         and (not isinstance(value, Importation) \
                              or value.fullName == existing.fullName)
                         and reportRedef):
-
                     self.report(messages.RedefinedWhileUnused,
                                 lineno, value.name,
                                 scope[value.name].source.lineno)
                     if isinstance(arg, _ast.Tuple):
                         addArgs(arg.elts)
                     else:
-                        if arg.id in args:
+                        try:
+                            id_ = arg.id
+                        except AttributeError:
+                            id_ = arg.arg
+
+                        if id_ in args:
                             self.report(messages.DuplicateArgument,
-                                        node.lineno, arg.id)
-                        args.append(arg.id)
+                                        node.lineno, id_)
+                        args.append(id_)
 
             self.pushFunctionScope()
             addArgs(node.args.args)
                 """
                 Check to see if any assignments have not been used.
                 """
-                for name, binding in self.scope.iteritems():
+                for name, binding in self.scope.items():
                     if (not binding.used and not name in self.scope.globals
                         and isinstance(binding, Assignment)):
                         self.report(messages.UnusedVariable,
-                                    binding.source.lineno, name)
+                                binding.source.lineno, name)
+
             self.deferAssignment(checkUnusedAssignments)
             self.popScope()
 
     @return: the number of warnings printed
     """
     try:
-        return check(file(filename, 'U').read() + '\n', filename)
-    except IOError, msg:
-        print >> sys.stderr, "%s: %s" % (filename, msg.args[1])
+        return check(open(filename, 'U').read() + '\n', filename)
+    except IOError:
+        msg = sys.exc_info()[1]
+        sys.stderr.write("%s: %s\n" % (filename, msg.args[1]))
         return 1
 
 
-def check(codeString, filename):
+def check(codeString, filename='(code)'):
     """
     Check the Python source given by C{codeString} for flakes.
 
     # First, compile into an AST and handle syntax errors.
     try:
         tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST)
-    except SyntaxError, value:
+    except SyntaxError:
+        value = sys.exc_info()[1]
         msg = value.args[0]
 
         (lineno, offset, text) = value.lineno, value.offset, value.text
             # Avoid using msg, since for the only known case, it contains a
             # bogus message that claims the encoding the file declared was
             # unknown.
-            print >> sys.stderr, "%s: problem decoding source" % (filename, )
+            sys.stderr.write("%s: problem decoding source\n" % (filename))
         else:
             line = text.splitlines()[-1]
 
             if offset is not None:
                 offset = offset - (len(text) - len(line))
 
-            print >> sys.stderr, '%s:%d: %s' % (filename, lineno, msg)
-            print >> sys.stderr, line
+            sys.stderr.write('%s:%d: %s\n' % (filename, lineno, msg))
+            sys.stderr.write(line + '\n')
 
             if offset is not None:
-                print >> sys.stderr, " " * offset, "^"
+                sys.stderr.write(" " * offset + "^\n")
 
         return 1
     else:
         # Okay, it's syntactically valid.  Now check it.
         w = Checker(tree, filename)
-        w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno))
+        sorting = [(msg.lineno, msg) for msg in w.messages]
+        sorting.sort()
+        w.messages = [msg for index, msg in sorting]
         valid_warnings = 0
 
         for warning in w.messages:
             if skip_warning(warning):
                 continue
-            print warning
+            print(warning)
             valid_warnings += 1
 
         return valid_warnings

File flake8/run.py

 import sys
 import os
 import os.path
+from subprocess import PIPE, Popen
 
 from flake8.util import skip_file
 from flake8 import pep8
 def main():
     options, args = pep8.process_options()
     complexity = options.max_complexity
+    builtins = set(options.builtins)
     warnings = 0
+
+    if builtins:
+        orig_builtins = set(pyflakes._MAGIC_GLOBALS)
+        pyflakes._MAGIC_GLOBALS = orig_builtins | builtins
     if args:
         for path in _get_python_files(args):
             warnings += check_file(path, complexity)
         stdin = sys.stdin.read()
         warnings += check_code(stdin, complexity)
 
+    if options.exit_zero:
+        raise SystemExit(0)
     raise SystemExit(warnings > 0)
 
 
 def _get_files(repo, **kwargs):
     seen = set()
-    for rev in xrange(repo[kwargs['node']], len(repo)):
+    for rev in range(repo[kwargs['node']], len(repo)):
         for file_ in repo[rev].files():
             file_ = os.path.join(repo.root, file_)
             if file_ in seen or not os.path.exists(file_):
 
 
 class _PEP8Options(object):
-    exclude = select = []
-    show_pep8 = show_source = quiet = verbose = testsuite = False
+    # Default options taken from pep8.process_options()
+    max_complexity = -1
+    verbose = False
+    quiet = False
     no_repeat = False
-    counters = {}
-    messages = {}
-    ignore = pep8.DEFAULT_IGNORE
+    exclude = [exc.rstrip('/') for exc in pep8.DEFAULT_EXCLUDE.split(',')]
+    filename = ['*.py']
+    select = []
+    ignore = pep8.DEFAULT_IGNORE.split(',')  # or []?
+    show_source = False
+    show_pep8 = False
+    statistics = False
+    count = False
+    benchmark = False
+    testsuite = ''
+    doctest = False
 
 
-def hg_hook(ui, repo, **kwargs):
+def _initpep8():
     # default pep8 setup
     pep8.options = _PEP8Options()
     pep8.options.physical_checks = pep8.find_checks('physical_line')
     pep8.options.logical_checks = pep8.find_checks('logical_line')
+    pep8.options.counters = dict.fromkeys(pep8.BENCHMARK_KEYS, 0)
+    pep8.options.messages = {}
     pep8.args = []
+
+
+def run(command):
+    p = Popen(command.split(), stdout=PIPE, stderr=PIPE)
+    p.wait()
+    return (p.returncode, [line.strip() for line in p.stdout.readlines()],
+            [line.strip() for line in p.stderr.readlines()])
+
+
+def git_hook(complexity=-1, strict=False):
+    _initpep8()
+    warnings = 0
+
+    _, files_modified, _ = run("git diff-index --name-only HEAD")
+    for filename in files_modified:
+        ext = os.path.splitext(filename)[-1]
+        if ext != '.py':
+            continue
+        if not os.path.exists(filename):
+            continue
+        warnings += check_file(filename, complexity)
+
+    if strict:
+        return warnings
+
+    return 0
+
+
+def hg_hook(ui, repo, **kwargs):
+    _initpep8()
     complexity = ui.configint('flake8', 'complexity', default=-1)
     warnings = 0
 

File flake8/tests/test_flakes.py

+from unittest import TestCase
+from flake8.pyflakes import check
+
+
+code = """
+try:
+    pass
+except ValueError as err:
+    print(err)
+"""
+
+code2 = """
+try:
+    pass
+except ValueError:
+    print("err")
+
+try:
+    pass
+except ValueError:
+    print("err")
+"""
+
+code3 = """
+try:
+    pass
+except (ImportError, ValueError):
+    print("err")
+"""
+
+code_from_import_exception = """
+from foo import SomeException
+try:
+    pass
+except SomeException:
+    print("err")
+"""
+
+code_import_exception = """
+import foo.SomeException
+try:
+    pass
+except foo.SomeException:
+    print("err")
+"""
+
+
+class TestFlake(TestCase):
+
+    def test_exception(self):
+        for c in (code, code2, code3):
+            warnings = check(code)
+            self.assertEqual(warnings, 0, code)
+
+    def test_from_import_exception_in_scope(self):
+        self.assertEqual(check(code_from_import_exception), 0)
+
+    def test_import_exception_in_scope(self):
+        self.assertEqual(check(code_import_exception), 0)

File flake8/tests/test_mccabe.py

 import unittest
 import sys
-from StringIO import StringIO
+try:
+    from StringIO import StringIO
+except ImportError:
+    from io import StringIO     # NOQA
 
 from flake8.mccabe import get_code_complexity
 
         self.assertEqual(get_code_complexity(_GLOBAL, 1), 2)
         self.out.seek(0)
         res = self.out.read().strip().split('\n')
-        wanted = ["stdin:5:1: 'a' is too complex (3)",
-                  'stdin:Loop 2 is too complex (1)']
+        wanted = ["stdin:5:1: W901 'a' is too complex (4)",
+                  "stdin:2:1: W901 'Loop 2' is too complex (2)"]
         self.assertEqual(res, wanted)

File flake8/util.py

+from __future__ import with_statement
 import re
+import os
 
 
 def skip_warning(warning):
     # XXX quick dirty hack, just need to keep the line in the warning
-    line = open(warning.filename).readlines()[warning.lineno - 1]
+    if not os.path.isfile(warning.filename):
+        return False
+
+    # XXX should cache the file in memory
+    with open(warning.filename) as f:
+        line = f.readlines()[warning.lineno - 1]
+
     return skip_line(line)
 
 
     return line.strip().lower().endswith('# noqa')
 
 
-_NOQA = re.compile(r'^# flake8: noqa', re.I | re.M)
+_NOQA = re.compile(r'flake8[:=]\s*noqa', re.I | re.M)
 
 
 def skip_file(path):
         content = f.read()
     finally:
         f.close()
-    return _NOQA.match(content) is not None
+    return _NOQA.search(content) is not None
-from distutils.core import setup
+import sys
+
+ispy3 = sys.version_info[0] == 3
+
+if ispy3:
+    from distutils.core import setup
+else:
+    try:
+        from setuptools import setup    # NOQA
+    except ImportError:
+        from distutils.core import setup   # NOQA
+
+from flake8 import __version__
 
 README = open('README').read()
 
 setup(
     name="flake8",
     license="MIT",
-    version="1.1",
+    version=__version__,
     description="code checking using pep8 and pyflakes",
     author="Tarek Ziade",
     author_email="tarek@ziade.org",
     url="http://bitbucket.org/tarek/flake8",
     packages=["flake8", "flake8.tests"],
-    scripts=["bin/flake8"],
+    scripts=["flake8/flake8"],
     long_description=README,
     classifiers=[
         "Environment :: Console",