summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIan Cordasco <graffatcolmingov@gmail.com>2016-07-13 01:07:58 +0000
committerIan Cordasco <graffatcolmingov@gmail.com>2016-07-13 01:07:58 +0000
commit4d6929c8ab45ca8116e36ad63d3fb6a55b1177b5 (patch)
tree3054210bf1b865666c0068e024e2e42dc1e4d4de /src
parent58e67634cd24122e1961ac2e223d38465e2ab4c8 (diff)
parent2ffcf96b4b7a1fbc761796df1336a94408a79ed0 (diff)
downloadflake8-4d6929c8ab45ca8116e36ad63d3fb6a55b1177b5.tar.gz
Merge branch 'add-statistics' into 'master'
Add the statistics module *Description of changes* Start adding support for `--statistics` and legacy `get_statistics` API. *Related to:* (Add bug number here) See merge request !73
Diffstat (limited to 'src')
-rw-r--r--src/flake8/api/legacy.py7
-rw-r--r--src/flake8/statistics.py118
-rw-r--r--src/flake8/style_guide.py3
3 files changed, 127 insertions, 1 deletions
diff --git a/src/flake8/api/legacy.py b/src/flake8/api/legacy.py
index 35c7101..9fc05bb 100644
--- a/src/flake8/api/legacy.py
+++ b/src/flake8/api/legacy.py
@@ -141,6 +141,8 @@ class Report(object):
.. warning:: This should not be instantiated by users.
"""
self._application = application
+ self._style_guide = application.guide
+ self._stats = self._style_guide.stats
@property
def total_errors(self):
@@ -149,4 +151,7 @@ class Report(object):
def get_statistics(self, violation):
"""Get the number of occurences of a violation."""
- raise NotImplementedError('Statistics capturing needs to happen first')
+ return [
+ '{} {} {}'.format(s.count, s.error_code, s.message)
+ for s in self._stats.statistics_for(violation)
+ ]
diff --git a/src/flake8/statistics.py b/src/flake8/statistics.py
new file mode 100644
index 0000000..2512089
--- /dev/null
+++ b/src/flake8/statistics.py
@@ -0,0 +1,118 @@
+"""Statistic collection logic for Flake8."""
+import collections
+
+
+class Statistics(object):
+ """Manager of aggregated statistics for a run of Flake8."""
+
+ def __init__(self):
+ """Initialize the underlying dictionary for our statistics."""
+ self._store = {}
+
+ def record(self, error):
+ """Add the fact that the error was seen in the file.
+
+ :param error:
+ The Error instance containing the information about the violation.
+ :type error:
+ flake8.style_guide.Error
+ """
+ key = Key.create_from(error)
+ if key not in self._store:
+ self._store[key] = Statistic.create_from(error)
+ self._store[key].increment()
+
+ def statistics_for(self, prefix, filename=None):
+ """Generate statistics for the prefix and filename.
+
+ If you have a :class:`Statistics` object that has recorded errors,
+ you can generate the statistics for a prefix (e.g., ``E``, ``E1``,
+ ``W50``, ``W503``) with the optional filter of a filename as well.
+
+ .. code-block:: python
+
+ >>> stats = Statistics()
+ >>> stats.statistics_for('E12',
+ filename='src/flake8/statistics.py')
+ <generator ...>
+ >>> stats.statistics_for('W')
+ <generator ...>
+
+ :param str prefix:
+ The error class or specific error code to find statistics for.
+ :param str filename:
+ (Optional) The filename to further filter results by.
+ :returns:
+ Generator of instances of :class:`Statistic`
+ """
+ matching_errors = sorted(key for key in self._store.keys()
+ if key.matches(prefix, filename))
+ for error_code in matching_errors:
+ yield self._store[error_code]
+
+
+class Key(collections.namedtuple('Key', ['filename', 'code'])):
+ """Simple key structure for the Statistics dictionary.
+
+ To make things clearer, easier to read, and more understandable, we use a
+ namedtuple here for all Keys in the underlying dictionary for the
+ Statistics object.
+ """
+
+ __slots__ = ()
+
+ @classmethod
+ def create_from(cls, error):
+ """Create a Key from :class:`flake8.style_guide.Error`."""
+ return cls(
+ filename=error.filename,
+ code=error.code,
+ )
+
+ def matches(self, prefix, filename):
+ """Determine if this key matches some constraints.
+
+ :param str prefix:
+ The error code prefix that this key's error code should start with.
+ :param str filename:
+ The filename that we potentially want to match on. This can be
+ None to only match on error prefix.
+ :returns:
+ True if the Key's code starts with the prefix and either filename
+ is None, or the Key's filename matches the value passed in.
+ :rtype:
+ bool
+ """
+ return (self.code.startswith(prefix) and
+ (filename is None or
+ self.filename == filename))
+
+
+class Statistic(object):
+ """Simple wrapper around the logic of each statistic.
+
+ Instead of maintaining a simple but potentially hard to reason about
+ tuple, we create a namedtuple which has attributes and a couple
+ convenience methods on it.
+ """
+
+ def __init__(self, error_code, filename, message, count):
+ """Initialize our Statistic."""
+ self.error_code = error_code
+ self.filename = filename
+ self.message = message
+ self.count = count
+
+ @classmethod
+ def create_from(cls, error):
+ """Create a Statistic from a :class:`flake8.style_guide.Error`."""
+ return cls(
+ error_code=error.code,
+ filename=error.filename,
+ message=error.text,
+ count=0,
+ )
+
+ def increment(self):
+ """Increment the number of times we've seen this error in this file."""
+ self.count += 1
diff --git a/src/flake8/style_guide.py b/src/flake8/style_guide.py
index 89890ba..ed1b844 100644
--- a/src/flake8/style_guide.py
+++ b/src/flake8/style_guide.py
@@ -5,6 +5,7 @@ import linecache
import logging
import re
+from flake8 import statistics
from flake8 import utils
__all__ = (
@@ -74,6 +75,7 @@ class StyleGuide(object):
self.options = options
self.listener = listener_trie
self.formatter = formatter
+ self.stats = statistics.Statistics()
self._selected = tuple(options.select)
self._ignored = tuple(options.ignore)
self._decision_cache = {}
@@ -267,6 +269,7 @@ class StyleGuide(object):
if (error_is_selected and is_not_inline_ignored and
is_included_in_diff):
self.formatter.handle(error)
+ self.stats.record(error)
self.listener.notify(error.code, error)
return 1
return 0