forked from Robpol86/flake8-pydocstyle
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathflake8_pep257.py
129 lines (104 loc) · 4.85 KB
/
flake8_pep257.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
"""A simple flake8 plugin for the pep257 Python utility for validating docstrings.
https://github.com/Robpol86/flake8-pep257
https://pypi.python.org/pypi/flake8-pep257
"""
import codecs
import gc
import optparse
import os
import pep257
import pep8
import pkg_resources
def load_file(filename):
"""Read file to memory.
For stdin sourced files, this function does something super duper incredibly hacky and shameful. So so shameful. I'm
obtaining the original source code of the target module from the only instance of pep8.Checker through the Python
garbage collector. Flake8's API doesn't give me the original source code of the module we are checking. Instead it
has pep8 give me an AST object of the module (already parsed). This unfortunately loses valuable information like
the kind of quotes used for strings (no way to know if a docstring was surrounded by triple double quotes or just
one single quote, thereby rendering pep257's D300 error as unusable).
This will break one day. I'm sure of it. For now it fixes https://github.com/Robpol86/flake8-pep257/issues/2
:param str filename: File path or 'stdin'. From Main().filename.
:return: First item is the filename or 'stdin', second are the contents of the file.
:rtype: tuple
"""
if filename in ('stdin', '-', None):
instances = [i for i in gc.get_objects() if isinstance(i, pep8.Checker)]
if len(instances) != 1:
raise ValueError('Expected only 1 instance of pep8.Checker, got {0} instead.'.format(len(instances)))
return 'stdin', ''.join(instances[0].lines)
with codecs.open(filename, encoding='utf-8') as handle:
return filename, handle.read()
def ignore(code):
"""Should this code be ignored.
:param str code: Error code (e.g. D201).
:return: True if code should be ignored, False otherwise.
:rtype: bool
"""
if code in Main.options['ignore']:
return True
if any(c in code for c in Main.options['ignore']):
return True
return False
class Main(object):
"""pep257 flake8 plugin."""
name = 'flake8-pep257'
options = dict()
version = getattr(pkg_resources, 'require')('flake8-pep257')[0].version
def __init__(self, tree, filename):
"""Constructor.
:param tree: Tokenized source code, not used.
:param str filename: Single filename to analyze or 'stdin'.
"""
self.tree = tree
self.filename = filename
@classmethod
def add_options(cls, parser):
"""Add options to flake8.
:param parser: optparse.OptionParser from pep8.
"""
try:
# flake8 3.0.0 and later
parser.add_option('--show-pep257', action='store_true', parse_from_config=True,
help='show explanation of each PEP 257 error')
except optparse.OptionError:
# flake8 < 3.0.0
parser.add_option('--show-pep257', action='store_true', help='show explanation of each PEP 257 error')
parser.config_options.append('show-pep257')
@classmethod
def parse_options(cls, options):
"""Read parsed options from flake8.
:param options: Options to add to flake8's command line options.
"""
# Handle flake8 options.
cls.options['explain'] = bool(options.show_pep257)
cls.options['ignore'] = options.ignore
# Handle pep257 options.
config = pep257.RawConfigParser()
for file_name in pep257.ConfigurationParser.PROJECT_CONFIG_FILES:
if config.read(os.path.join(os.path.abspath('.'), file_name)):
break
if not config.has_section('pep257'):
return
native_options = dict()
for option in config.options('pep257'):
if option == 'ignore':
native_options['ignore'] = config.get('pep257', option)
if option in ('explain', 'source'):
native_options[option] = config.getboolean('pep257', option)
native_options['show-source'] = native_options.pop('source', None)
if native_options.get('ignore'):
native_options['ignore'] = native_options['ignore'].split(',')
cls.options.update(dict((k, v) for k, v in native_options.items() if v))
def run(self):
"""Run analysis on a single file."""
pep257.Error.explain = self.options['explain']
filename, source = load_file(self.filename)
for error in pep257.PEP257Checker().check_source(source, filename):
if not hasattr(error, 'code') or ignore(error.code):
continue
lineno = error.line
offset = 0 # Column number starting from 0.
explanation = error.explanation if pep257.Error.explain else ''
text = '{0} {1}{2}'.format(error.code, error.message.split(': ', 1)[1], explanation)
yield lineno, offset, text, Main