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
|
#!/usr/bin/env python
from __future__ import absolute_import
from __future__ import with_statement
import os
import re
import sys
from collections import defaultdict
from unipath import Path
RE_COMMENT = r'^\s*\#'
RE_NOQA = r'.+?\#\s+noqa+'
RE_MULTILINE_COMMENT_O = r'^\s*(?:\'\'\'|""").+?(?:\'\'\'|""")'
RE_MULTILINE_COMMENT_S = r'^\s*(?:\'\'\'|""")'
RE_MULTILINE_COMMENT_E = r'(?:^|.+?)(?:\'\'\'|""")'
RE_WITH = r'(?:^|\s+)with\s+'
RE_WITH_IMPORT = r'''from\s+ __future__\s+ import\s+ with_statement'''
RE_PRINT = r'''(?:^|\s+)print\((?:"|')(?:\W+?)?[A-Z0-9:]{2,}'''
RE_ABS_IMPORT = r'''from\s+ __future__\s+ import\s+ absolute_import'''
acc = defaultdict(lambda: {"abs": False, "print": False})
def compile(regex):
return re.compile(regex, re.VERBOSE)
class FlakePP(object):
re_comment = compile(RE_COMMENT)
re_ml_comment_o = compile(RE_MULTILINE_COMMENT_O)
re_ml_comment_s = compile(RE_MULTILINE_COMMENT_S)
re_ml_comment_e = compile(RE_MULTILINE_COMMENT_E)
re_abs_import = compile(RE_ABS_IMPORT)
re_print = compile(RE_PRINT)
re_with_import = compile(RE_WITH_IMPORT)
re_with = compile(RE_WITH)
re_noqa = compile(RE_NOQA)
map = {"abs": True, "print": False,
"with": False, "with-used": False}
def __init__(self, verbose=False):
self.verbose = verbose
self.steps = (("abs", self.re_abs_import),
("with", self.re_with_import),
("with-used", self.re_with),
("print", self.re_print))
def analyze_fh(self, fh):
steps = self.steps
filename = fh.name
acc = dict(self.map)
index = 0
errors = [0]
def error(fmt, **kwargs):
errors[0] += 1
self.announce(fmt, **dict(kwargs, filename=filename))
for index, line in enumerate(self.strip_comments(fh)):
for key, pattern in steps:
if pattern.match(line):
acc[key] = True
if index:
if not acc["abs"]:
error("%(filename)s: missing abs import")
if acc["with-used"] and not acc["with"]:
error("%(filename)s: missing with import")
if acc["print"]:
error("%(filename)s: left over print statement")
return filename, errors[0], acc
def analyze_file(self, filename):
with open(filename) as fh:
return self.analyze_fh(fh)
def analyze_tree(self, dir):
for dirpath, _, filenames in os.walk(dir):
for path in (Path(dirpath, f) for f in filenames):
if path.endswith(".py"):
yield self.analyze_file(path)
def analyze(self, *paths):
for path in map(Path, paths):
if path.isdir():
for res in self.analyze_tree(path):
yield res
else:
yield self.analyze_file(path)
def strip_comments(self, fh):
re_comment = self.re_comment
re_ml_comment_o = self.re_ml_comment_o
re_ml_comment_s = self.re_ml_comment_s
re_ml_comment_e = self.re_ml_comment_e
re_noqa = self.re_noqa
in_ml = False
for line in fh.readlines():
if in_ml:
if re_ml_comment_e.match(line):
in_ml = False
else:
if re_noqa.match(line) or re_ml_comment_o.match(line):
pass
elif re_ml_comment_s.match(line):
in_ml = True
elif re_comment.match(line):
pass
else:
yield line
def announce(self, fmt, **kwargs):
sys.stderr.write((fmt + "\n") % kwargs)
def main(argv=sys.argv, exitcode=0):
for _, errors, _ in FlakePP(verbose=True).analyze(*argv[1:]):
if errors:
exitcode = 1
return exitcode
if __name__ == "__main__":
sys.exit(main())
|