diff options
| author | Ian Cordasco <graffatcolmingov@gmail.com> | 2016-07-13 01:07:58 +0000 |
|---|---|---|
| committer | Ian Cordasco <graffatcolmingov@gmail.com> | 2016-07-13 01:07:58 +0000 |
| commit | 4d6929c8ab45ca8116e36ad63d3fb6a55b1177b5 (patch) | |
| tree | 3054210bf1b865666c0068e024e2e42dc1e4d4de /src | |
| parent | 58e67634cd24122e1961ac2e223d38465e2ab4c8 (diff) | |
| parent | 2ffcf96b4b7a1fbc761796df1336a94408a79ed0 (diff) | |
| download | flake8-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.py | 7 | ||||
| -rw-r--r-- | src/flake8/statistics.py | 118 | ||||
| -rw-r--r-- | src/flake8/style_guide.py | 3 |
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 |
