Tests: replace os.system w/ subprocess.call
[blender.git] / tests / python / pep8.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8-80 compliant>
20
21 import os
22 import subprocess
23 import shutil
24
25 # depends on pep8, frosted, pylint
26 # for Ubuntu
27 #
28 #   sudo apt-get install pylint
29 #
30 #   sudo apt-get install python-setuptools python-pip
31 #   sudo pip install pep8
32 #   sudo pip install frosted
33 #
34 # in Debian install pylint pep8 with apt-get/aptitude/etc
35 #
36 # on *nix run
37 #   python tests/pep8.py > test_pep8.log 2>&1
38
39 # how many lines to read into the file, pep8 comment
40 # should be directly after the license header, ~20 in most cases
41 PEP8_SEEK_COMMENT = 40
42 SKIP_PREFIX = "./tools", "./config", "./extern"
43 SKIP_ADDONS = True
44 FORCE_PEP8_ALL = False
45
46
47 def file_list_py(path):
48     for dirpath, dirnames, filenames in os.walk(path):
49         for filename in filenames:
50             if filename.endswith((".py", ".cfg")):
51                 yield os.path.join(dirpath, filename)
52
53
54 def is_pep8(path):
55     print(path)
56     if open(path, 'rb').read(3) == b'\xef\xbb\xbf':
57         print("\nfile contains BOM, remove first 3 bytes: %r\n" % path)
58
59     # templates don't have a header but should be pep8
60     for d in ("presets", "templates_py", "examples"):
61         if ("%s%s%s" % (os.sep, d, os.sep)) in path:
62             return 1
63
64     f = open(path, 'r', encoding="utf8")
65     for i in range(PEP8_SEEK_COMMENT):
66         line = f.readline()
67         if line.startswith("# <pep8"):
68             if line.startswith("# <pep8 compliant>"):
69                 return 1
70             elif line.startswith("# <pep8-80 compliant>"):
71                 return 2
72     f.close()
73     return 0
74
75
76 def check_files_flake8(files):
77     print("\n\n\n# running flake8...")
78
79     # these are very picky and often hard to follow
80     # while keeping common script formatting.
81     ignore = (
82         "E122",
83         "E123",
84         "E124",
85         "E125",
86         "E126",
87         "E127",
88         "E128",
89         # "imports not at top of file."
90         # prefer to load as needed (lazy load addons etc).
91         "E402",
92         # "do not compare types, use 'isinstance()'"
93         # times types are compared,
94         # I rather keep them specific
95         "E721",
96         )
97
98     for f, pep8_type in files:
99
100         if pep8_type == 1:
101             # E501:80 line length
102             ignore_tmp = ignore + ("E501", )
103         else:
104             ignore_tmp = ignore
105
106         subprocess.call((
107             "flake8",
108             "--isolated",
109             "--ignore=%s" % ",".join(ignore_tmp),
110             f,
111         ))
112
113
114 def check_files_frosted(files):
115     print("\n\n\n# running frosted...")
116     for f, pep8_type in files:
117         subprocess.call(("frosted", f))
118
119
120 def check_files_pylint(files):
121     print("\n\n\n# running pylint...")
122     for f, pep8_type in files:
123         # let pep8 complain about line length
124         subprocess.call((
125             "pylint",
126             "--disable="
127             "C0111,"  # missing doc string
128             "C0103,"  # invalid name
129             "C0413,"  # import should be placed at the top
130             "W0613,"  # unused argument, may add this back
131             # but happens a lot for 'context' for eg.
132                     "W0232,"  # class has no __init__, Operator/Panel/Menu etc
133             "W0142,"  # Used * or ** magic
134             # even needed in some cases
135                     "R0902,"  # Too many instance attributes
136             "R0903,"  # Too many statements
137             "R0911,"  # Too many return statements
138             "R0912,"  # Too many branches
139             "R0913,"  # Too many arguments
140             "R0914,"  # Too many local variables
141             "R0915,",  # Too many statements
142             "--output-format=parseable",
143             "--reports=n",
144             "--max-line-length=1000",
145             f,
146         ))
147
148
149 def main():
150     files = []
151     files_skip = []
152     for f in file_list_py("."):
153         if [None for prefix in SKIP_PREFIX if f.startswith(prefix)]:
154             continue
155
156         if SKIP_ADDONS:
157             if (os.sep + "addons") in f:
158                 continue
159
160         pep8_type = FORCE_PEP8_ALL or is_pep8(f)
161
162         if pep8_type:
163             # so we can batch them for each tool.
164             files.append((os.path.abspath(f), pep8_type))
165         else:
166             files_skip.append(f)
167
168     print("\nSkipping...")
169     for f in files_skip:
170         print("    %s" % f)
171
172     # strict imports
173     print("\n\n\n# checking imports...")
174     import re
175     import_check = re.compile(r"\s*from\s+[A-z\.]+\s+import \*\s*")
176     for f, pep8_type in files:
177         for i, l in enumerate(open(f, 'r', encoding='utf8')):
178             if import_check.match(l):
179                 print("%s:%d:0: global import bad practice" % (f, i + 1))
180     del re, import_check
181
182     print("\n\n\n# checking class definitions...")
183     import re
184     class_check = re.compile(r"\s*class\s+.*\(\):.*")
185     for f, pep8_type in files:
186         for i, l in enumerate(open(f, 'r', encoding='utf8')):
187             if class_check.match(l):
188                 print("%s:%d:0: empty class (), remove" % (f, i + 1))
189     del re, class_check
190
191     if shutil.which("flake8"):
192         check_files_flake8(files)
193     else:
194         print("Skipping flake8 checks (command not found)")
195
196     if shutil.which("frosted"):
197         check_files_frosted(files)
198     else:
199         print("Skipping frosted checks (command not found)")
200
201     if shutil.which("pylint"):
202         check_files_pylint(files)
203     else:
204         print("Skipping pylint checks (command not found)")
205
206
207
208 if __name__ == "__main__":
209     main()