Add mccabe (0.2.1)
authorGregory Szorc <gps@mozilla.com>
Mon, 22 Jul 2013 20:55:48 -0700
changeset 22 a72cc7e9d88127b9cf22a79695ac9701adf095d2
parent 21 baf677f16f850924a9a3627e39d0bcde468faebe
child 23 38dd4d4b71cd1f2fce363d04026d006780f27f2f
push id9
push usergszorc@mozilla.com
push dateTue, 23 Jul 2013 03:56:04 +0000
Add mccabe (0.2.1)
mccabe/PKG-INFO
mccabe/README.rst
mccabe/mccabe.egg-info/PKG-INFO
mccabe/mccabe.egg-info/SOURCES.txt
mccabe/mccabe.egg-info/dependency_links.txt
mccabe/mccabe.egg-info/entry_points.txt
mccabe/mccabe.egg-info/not-zip-safe
mccabe/mccabe.egg-info/top_level.txt
mccabe/mccabe.py
mccabe/setup.cfg
mccabe/setup.py
new file mode 100644
--- /dev/null
+++ b/mccabe/PKG-INFO
@@ -0,0 +1,110 @@
+Metadata-Version: 1.1
+Name: mccabe
+Version: 0.2.1
+Summary: McCabe checker, plugin for flake8
+Home-page: https://github.com/flintwork/mccabe
+Author: Florent Xicluna
+Author-email: florent.xicluna@gmail.com
+License: Expat license
+Description: McCabe complexity checker
+        =========================
+        
+        Ned's script to check McCabe complexity.
+        
+        This module provides a plugin for ``flake8``, the Python code checker.
+        
+        
+        Installation
+        ------------
+        
+        You can install, upgrade, uninstall ``mccabe`` with these commands::
+        
+          $ pip install mccabe
+          $ pip install --upgrade mccabe
+          $ pip uninstall mccabe
+        
+        
+        Standalone script
+        -----------------
+        
+        The complexity checker can be used directly::
+        
+          $ python -m mccabe --min 5 mccabe.py
+          ("185:1: 'PathGraphingAstVisitor.visitIf'", 5)
+          ("71:1: 'PathGraph.to_dot'", 5)
+          ("245:1: 'McCabeChecker.run'", 5)
+          ("283:1: 'main'", 7)
+          ("203:1: 'PathGraphingAstVisitor.visitTryExcept'", 5)
+          ("257:1: 'get_code_complexity'", 5)
+        
+        
+        Plugin for Flake8
+        -----------------
+        
+        When both ``flake8 2.0`` and ``mccabe`` are installed, the plugin is
+        available in ``flake8``::
+        
+          $ flake8 --version
+          2.0 (pep8: 1.4.2, pyflakes: 0.6.1, mccabe: 0.2)
+        
+        By default the plugin is disabled.  Use the ``--max-complexity`` switch to
+        enable it.  It will emit a warning if the McCabe complexity of a function is
+        higher that the value::
+        
+            $ flake8 --max-complexity 10 coolproject
+            ...
+            coolproject/mod.py:1204:1: C901 'CoolFactory.prepare' 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.
+        
+        
+        Links
+        -----
+        
+        * Feedback and ideas: http://mail.python.org/mailman/listinfo/code-quality
+        
+        * Cyclomatic complexity: http://en.wikipedia.org/wiki/Cyclomatic_complexity.
+        
+        * Ned Batchelder's script:
+          http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html
+        
+        
+        Changes
+        -------
+        
+        0.2.1 - 2013-04-03
+        ``````````````````
+        
+        * Do not require ``setuptools`` in setup.py.  It works around an issue
+          with ``pip`` and Python 3.
+        
+        
+        0.2 - 2013-02-22
+        ````````````````
+        
+        * Rename project ``flint-mccabe`` to ``mccabe``.
+        
+        * Provide ``flake8.extension`` setuptools entry point.
+        
+        * Read ``max-complexity`` from the configuration file.
+        
+        * Rename argument ``min_complexity`` to ``threshold``.
+        
+        
+        0.1 - 2013-02-11
+        ````````````````
+        * First release
+        
+Keywords: flake8 mccabe
+Platform: UNKNOWN
+Classifier: Development Status :: 3 - Alpha
+Classifier: Environment :: Console
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 3
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Software Development :: Quality Assurance
new file mode 100644
--- /dev/null
+++ b/mccabe/README.rst
@@ -0,0 +1,89 @@
+McCabe complexity checker
+=========================
+
+Ned's script to check McCabe complexity.
+
+This module provides a plugin for ``flake8``, the Python code checker.
+
+
+Installation
+------------
+
+You can install, upgrade, uninstall ``mccabe`` with these commands::
+
+  $ pip install mccabe
+  $ pip install --upgrade mccabe
+  $ pip uninstall mccabe
+
+
+Standalone script
+-----------------
+
+The complexity checker can be used directly::
+
+  $ python -m mccabe --min 5 mccabe.py
+  ("185:1: 'PathGraphingAstVisitor.visitIf'", 5)
+  ("71:1: 'PathGraph.to_dot'", 5)
+  ("245:1: 'McCabeChecker.run'", 5)
+  ("283:1: 'main'", 7)
+  ("203:1: 'PathGraphingAstVisitor.visitTryExcept'", 5)
+  ("257:1: 'get_code_complexity'", 5)
+
+
+Plugin for Flake8
+-----------------
+
+When both ``flake8 2.0`` and ``mccabe`` are installed, the plugin is
+available in ``flake8``::
+
+  $ flake8 --version
+  2.0 (pep8: 1.4.2, pyflakes: 0.6.1, mccabe: 0.2)
+
+By default the plugin is disabled.  Use the ``--max-complexity`` switch to
+enable it.  It will emit a warning if the McCabe complexity of a function is
+higher that the value::
+
+    $ flake8 --max-complexity 10 coolproject
+    ...
+    coolproject/mod.py:1204:1: C901 'CoolFactory.prepare' 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.
+
+
+Links
+-----
+
+* Feedback and ideas: http://mail.python.org/mailman/listinfo/code-quality
+
+* Cyclomatic complexity: http://en.wikipedia.org/wiki/Cyclomatic_complexity.
+
+* Ned Batchelder's script:
+  http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html
+
+
+Changes
+-------
+
+0.2.1 - 2013-04-03
+``````````````````
+
+* Do not require ``setuptools`` in setup.py.  It works around an issue
+  with ``pip`` and Python 3.
+
+
+0.2 - 2013-02-22
+````````````````
+
+* Rename project ``flint-mccabe`` to ``mccabe``.
+
+* Provide ``flake8.extension`` setuptools entry point.
+
+* Read ``max-complexity`` from the configuration file.
+
+* Rename argument ``min_complexity`` to ``threshold``.
+
+
+0.1 - 2013-02-11
+````````````````
+* First release
new file mode 100644
--- /dev/null
+++ b/mccabe/mccabe.egg-info/PKG-INFO
@@ -0,0 +1,110 @@
+Metadata-Version: 1.1
+Name: mccabe
+Version: 0.2.1
+Summary: McCabe checker, plugin for flake8
+Home-page: https://github.com/flintwork/mccabe
+Author: Florent Xicluna
+Author-email: florent.xicluna@gmail.com
+License: Expat license
+Description: McCabe complexity checker
+        =========================
+        
+        Ned's script to check McCabe complexity.
+        
+        This module provides a plugin for ``flake8``, the Python code checker.
+        
+        
+        Installation
+        ------------
+        
+        You can install, upgrade, uninstall ``mccabe`` with these commands::
+        
+          $ pip install mccabe
+          $ pip install --upgrade mccabe
+          $ pip uninstall mccabe
+        
+        
+        Standalone script
+        -----------------
+        
+        The complexity checker can be used directly::
+        
+          $ python -m mccabe --min 5 mccabe.py
+          ("185:1: 'PathGraphingAstVisitor.visitIf'", 5)
+          ("71:1: 'PathGraph.to_dot'", 5)
+          ("245:1: 'McCabeChecker.run'", 5)
+          ("283:1: 'main'", 7)
+          ("203:1: 'PathGraphingAstVisitor.visitTryExcept'", 5)
+          ("257:1: 'get_code_complexity'", 5)
+        
+        
+        Plugin for Flake8
+        -----------------
+        
+        When both ``flake8 2.0`` and ``mccabe`` are installed, the plugin is
+        available in ``flake8``::
+        
+          $ flake8 --version
+          2.0 (pep8: 1.4.2, pyflakes: 0.6.1, mccabe: 0.2)
+        
+        By default the plugin is disabled.  Use the ``--max-complexity`` switch to
+        enable it.  It will emit a warning if the McCabe complexity of a function is
+        higher that the value::
+        
+            $ flake8 --max-complexity 10 coolproject
+            ...
+            coolproject/mod.py:1204:1: C901 'CoolFactory.prepare' 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.
+        
+        
+        Links
+        -----
+        
+        * Feedback and ideas: http://mail.python.org/mailman/listinfo/code-quality
+        
+        * Cyclomatic complexity: http://en.wikipedia.org/wiki/Cyclomatic_complexity.
+        
+        * Ned Batchelder's script:
+          http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html
+        
+        
+        Changes
+        -------
+        
+        0.2.1 - 2013-04-03
+        ``````````````````
+        
+        * Do not require ``setuptools`` in setup.py.  It works around an issue
+          with ``pip`` and Python 3.
+        
+        
+        0.2 - 2013-02-22
+        ````````````````
+        
+        * Rename project ``flint-mccabe`` to ``mccabe``.
+        
+        * Provide ``flake8.extension`` setuptools entry point.
+        
+        * Read ``max-complexity`` from the configuration file.
+        
+        * Rename argument ``min_complexity`` to ``threshold``.
+        
+        
+        0.1 - 2013-02-11
+        ````````````````
+        * First release
+        
+Keywords: flake8 mccabe
+Platform: UNKNOWN
+Classifier: Development Status :: 3 - Alpha
+Classifier: Environment :: Console
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 3
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Software Development :: Quality Assurance
new file mode 100644
--- /dev/null
+++ b/mccabe/mccabe.egg-info/SOURCES.txt
@@ -0,0 +1,9 @@
+README.rst
+mccabe.py
+setup.py
+mccabe.egg-info/PKG-INFO
+mccabe.egg-info/SOURCES.txt
+mccabe.egg-info/dependency_links.txt
+mccabe.egg-info/entry_points.txt
+mccabe.egg-info/not-zip-safe
+mccabe.egg-info/top_level.txt
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mccabe/mccabe.egg-info/dependency_links.txt
@@ -0,0 +1,1 @@
+
new file mode 100644
--- /dev/null
+++ b/mccabe/mccabe.egg-info/entry_points.txt
@@ -0,0 +1,3 @@
+[flake8.extension]
+C90 = mccabe:McCabeChecker
+
new file mode 100644
--- /dev/null
+++ b/mccabe/mccabe.egg-info/not-zip-safe
@@ -0,0 +1,1 @@
+
new file mode 100644
--- /dev/null
+++ b/mccabe/mccabe.egg-info/top_level.txt
@@ -0,0 +1,1 @@
+mccabe
new file mode 100644
--- /dev/null
+++ b/mccabe/mccabe.py
@@ -0,0 +1,312 @@
+""" Meager code path measurement tool.
+    Ned Batchelder
+    http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html
+    MIT License.
+"""
+from __future__ import with_statement
+
+import optparse
+import sys
+from collections import defaultdict
+try:
+    import ast
+    from ast import iter_child_nodes
+except ImportError:   # Python 2.5
+    from flake8.util import ast, iter_child_nodes
+
+__version__ = '0.2.1'
+
+
+class ASTVisitor(object):
+    """Performs a depth-first walk of the AST."""
+
+    def __init__(self):
+        self.node = None
+        self._cache = {}
+
+    def default(self, node, *args):
+        for child in iter_child_nodes(node):
+            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(object):
+    def __init__(self, name, look="circle"):
+        self.name = name
+        self.look = look
+
+    def to_dot(self):
+        print('node [shape=%s,label="%s"] %d;' % (
+            self.look, self.name, self.dot_id()))
+
+    def dot_id(self):
+        return id(self)
+
+
+class PathGraph(object):
+    def __init__(self, name, entity, lineno):
+        self.name = name
+        self.entity = entity
+        self.lineno = lineno
+        self.nodes = defaultdict(list)
+
+    def connect(self, n1, n2):
+        self.nodes[n1].append(n2)
+
+    def to_dot(self):
+        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('}')
+
+    def complexity(self):
+        """ Return the McCabe complexity for the graph.
+            V-E+2
+        """
+        num_edges = sum([len(n) for n in self.nodes.values()])
+        num_nodes = len(self.nodes)
+        return num_edges - num_nodes + 2
+
+
+class PathGraphingAstVisitor(ASTVisitor):
+    """ A visitor for a parsed Abstract Syntax Tree which finds executable
+        statements.
+    """
+
+    def __init__(self):
+        super(PathGraphingAstVisitor, self).__init__()
+        self.classname = ""
+        self.graphs = {}
+        self.reset()
+
+    def reset(self):
+        self.graph = None
+        self.tail = None
+
+    def dispatch_list(self, node_list):
+        for node in node_list:
+            self.dispatch(node)
+
+    def visitFunctionDef(self, node):
+
+        if self.classname:
+            entity = '%s%s' % (self.classname, node.name)
+        else:
+            entity = node.name
+
+        name = '%d:1: %r' % (node.lineno, entity)
+
+        if self.graph is not None:
+            # closure
+            pathnode = self.appendPathNode(name)
+            self.tail = pathnode
+            self.dispatch_list(node.body)
+            bottom = PathNode("", look='point')
+            self.graph.connect(self.tail, bottom)
+            self.graph.connect(pathnode, bottom)
+            self.tail = bottom
+        else:
+            self.graph = PathGraph(name, entity, node.lineno)
+            pathnode = PathNode(name)
+            self.tail = pathnode
+            self.dispatch_list(node.body)
+            self.graphs["%s%s" % (self.classname, node.name)] = self.graph
+            self.reset()
+
+    def visitClassDef(self, node):
+        old_classname = self.classname
+        self.classname += node.name + "."
+        self.dispatch_list(node.body)
+        self.classname = old_classname
+
+    def appendPathNode(self, name):
+        if not self.tail:
+            return
+        pathnode = PathNode(name)
+        self.graph.connect(self.tail, pathnode)
+        self.tail = pathnode
+        return pathnode
+
+    def visitSimpleStatement(self, node):
+        if node.lineno is None:
+            lineno = 0
+        else:
+            lineno = node.lineno
+        name = "Stmt %d" % lineno
+        self.appendPathNode(name)
+
+    visitAssert = visitAssign = visitAugAssign = visitDelete = visitPrint = \
+        visitRaise = visitYield = visitImport = visitCall = visitSubscript = \
+        visitPass = visitContinue = visitBreak = visitGlobal = visitReturn = \
+        visitSimpleStatement
+
+    def visitLoop(self, node):
+        name = "Loop %d" % node.lineno
+
+        if self.graph is None:
+            # global loop
+            self.graph = PathGraph(name, name, node.lineno)
+            pathnode = PathNode(name)
+            self.tail = pathnode
+            self.dispatch_list(node.body)
+            self.graphs["%s%s" % (self.classname, name)] = self.graph
+            self.reset()
+        else:
+            pathnode = self.appendPathNode(name)
+            self.tail = pathnode
+            self.dispatch_list(node.body)
+            bottom = PathNode("", look='point')
+            self.graph.connect(self.tail, bottom)
+            self.graph.connect(pathnode, bottom)
+            self.tail = bottom
+
+        # TODO: else clause in node.orelse
+
+    visitFor = visitWhile = visitLoop
+
+    def visitIf(self, node):
+        name = "If %d" % node.lineno
+        pathnode = self.appendPathNode(name)
+        loose_ends = []
+        self.dispatch_list(node.body)
+        loose_ends.append(self.tail)
+        if node.orelse:
+            self.tail = pathnode
+            self.dispatch_list(node.orelse)
+            loose_ends.append(self.tail)
+        else:
+            loose_ends.append(pathnode)
+        if pathnode:
+            bottom = PathNode("", look='point')
+            for le in loose_ends:
+                self.graph.connect(le, bottom)
+            self.tail = bottom
+
+    def visitTryExcept(self, node):
+        name = "TryExcept %d" % node.lineno
+        pathnode = self.appendPathNode(name)
+        loose_ends = []
+        self.dispatch_list(node.body)
+        loose_ends.append(self.tail)
+        for handler in node.handlers:
+            self.tail = pathnode
+            self.dispatch_list(handler.body)
+            loose_ends.append(self.tail)
+        if pathnode:
+            bottom = PathNode("", look='point')
+            for le in loose_ends:
+                self.graph.connect(le, bottom)
+            self.tail = bottom
+
+    def visitWith(self, node):
+        name = "With %d" % node.lineno
+        self.appendPathNode(name)
+        self.dispatch_list(node.body)
+
+
+class McCabeChecker(object):
+    """McCabe cyclomatic complexity checker."""
+    name = 'mccabe'
+    version = __version__
+    _code = 'C901'
+    _error_tmpl = "C901 %r is too complex (%d)"
+    max_complexity = 0
+
+    def __init__(self, tree, filename):
+        self.tree = tree
+
+    @classmethod
+    def add_options(cls, parser):
+        parser.add_option('--max-complexity', default=-1, action='store',
+                          type='int', help="McCabe complexity threshold")
+        parser.config_options.append('max-complexity')
+
+    @classmethod
+    def parse_options(cls, options):
+        cls.max_complexity = options.max_complexity
+
+    def run(self):
+        if self.max_complexity < 0:
+            return
+        visitor = PathGraphingAstVisitor()
+        visitor.preorder(self.tree, visitor)
+        for graph in visitor.graphs.values():
+            if graph.complexity() >= self.max_complexity:
+                text = self._error_tmpl % (graph.entity, graph.complexity())
+                yield graph.lineno, 0, text, type(self)
+
+
+def get_code_complexity(code, threshold=7, filename='stdin'):
+    try:
+        tree = compile(code, filename, "exec", ast.PyCF_ONLY_AST)
+    except SyntaxError:
+        e = sys.exc_info()[1]
+        sys.stderr.write("Unable to parse %s: %s\n" % (filename, e))
+        return 0
+
+    complx = []
+    McCabeChecker.max_complexity = threshold
+    for lineno, offset, text, check in McCabeChecker(tree, filename).run():
+        complx.append('%s:%d:1: %s' % (filename, lineno, text))
+
+    if len(complx) == 0:
+        return 0
+    print('\n'.join(complx))
+    return len(complx)
+
+
+def get_module_complexity(module_path, threshold=7):
+    """Returns the complexity of a module"""
+    with open(module_path, "rU") as mod:
+        code = mod.read()
+    return get_code_complexity(code, threshold, filename=module_path)
+
+
+def main(argv):
+    opar = optparse.OptionParser()
+    opar.add_option("-d", "--dot", dest="dot",
+                    help="output a graphviz dot file", action="store_true")
+    opar.add_option("-m", "--min", dest="threshold",
+                    help="minimum complexity for output", type="int",
+                    default=2)
+
+    options, args = opar.parse_args(argv)
+
+    with open(args[0], "rU") as mod:
+        code = mod.read()
+    tree = compile(code, args[0], "exec", ast.PyCF_ONLY_AST)
+    visitor = PathGraphingAstVisitor()
+    visitor.preorder(tree, visitor)
+
+    if options.dot:
+        print('graph {')
+        for graph in visitor.graphs.values():
+            if graph.complexity() >= options.threshold:
+                graph.to_dot()
+        print('}')
+    else:
+        for graph in visitor.graphs.values():
+            if graph.complexity() >= options.threshold:
+                print(graph.name, graph.complexity())
+
+
+if __name__ == '__main__':
+    main(sys.argv[1:])
new file mode 100644
--- /dev/null
+++ b/mccabe/setup.cfg
@@ -0,0 +1,5 @@
+[egg_info]
+tag_build = 
+tag_date = 0
+tag_svn_revision = 0
+
new file mode 100644
--- /dev/null
+++ b/mccabe/setup.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+from __future__ import with_statement
+from setuptools import setup
+
+
+def get_version(fname='mccabe.py'):
+    with open(fname) as f:
+        for line in f:
+            if line.startswith('__version__'):
+                return eval(line.split('=')[-1])
+
+
+def get_long_description():
+    descr = []
+    for fname in ('README.rst',):
+        with open(fname) as f:
+            descr.append(f.read())
+    return '\n\n'.join(descr)
+
+
+setup(
+    name='mccabe',
+    version=get_version(),
+    description="McCabe checker, plugin for flake8",
+    long_description=get_long_description(),
+    keywords='flake8 mccabe',
+    author='Tarek Ziade',
+    author_email='tarek@ziade.org',
+    maintainer='Florent Xicluna',
+    maintainer_email='florent.xicluna@gmail.com',
+    url='https://github.com/flintwork/mccabe',
+    license='Expat license',
+    py_modules=['mccabe'],
+    zip_safe=False,
+    entry_points={
+        'flake8.extension': [
+            'C90 = mccabe:McCabeChecker',
+        ],
+    },
+    classifiers=[
+        'Development Status :: 3 - Alpha',
+        'Environment :: Console',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: MIT License',
+        'Operating System :: OS Independent',
+        'Programming Language :: Python',
+        'Programming Language :: Python :: 2',
+        'Programming Language :: Python :: 3',
+        'Topic :: Software Development :: Libraries :: Python Modules',
+        'Topic :: Software Development :: Quality Assurance',
+    ],
+)