summaryrefslogtreecommitdiff
path: root/src/flake8/statistics.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/flake8/statistics.py')
-rw-r--r--src/flake8/statistics.py118
1 files changed, 118 insertions, 0 deletions
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