1"""
2Test flake8 errors.
3"""
4from __future__ import absolute_import, print_function, division
5from nose.plugins.skip import SkipTest
6import os
7import sys
8from fnmatch import fnmatch
9import theano
10new_flake8 = True
11try:
12    import flake8.main
13    flake8_available = True
14    try:
15        import flake8.engine
16        new_flake8 = False
17    except ImportError:
18        import flake8.api.legacy
19except ImportError:
20    flake8_available = False
21
22__authors__ = ("Saizheng Zhang")
23__copyright__ = "(c) 2016, Universite de Montreal"
24__contact__ = "Saizheng Zhang <saizhenglisa..at..gmail.com>"
25
26# We ignore:
27# - "line too long"
28#    too complex to do with the C code
29# - "closing bracket does not match indentation of opening bracket's line"
30#    ignored by default by pep8
31# - "'with_statement' missing"
32# - "'unicode_literals' missing"
33# - "'generator_stop' missing"
34# - "'division' present"
35# - "'absolute_import' present"
36# - "'print_function' present"
37# - "expected 2 blank lines after class or function definition"' (E305)
38# - "ambiguous variable name" (E741)
39#   Redundant error code generated by flake8-future-import module
40ignore = ('E501', 'E123', 'E133', 'FI12', 'FI14', 'FI15', 'FI16', 'FI17',
41          'FI18', 'FI50', 'FI51', 'FI53', 'E305', 'E741')
42
43whitelist_flake8 = [
44    "__init__.py",
45    "_version.py",  # This is generated by versioneer
46    "tests/__init__.py",
47    "compile/__init__.py",
48    "compile/sandbox/__init__.py",
49    "compile/tests/__init__.py",
50    "gpuarray/__init__.py",
51    "gpuarray/tests/__init__.py",
52    "typed_list/__init__.py",
53    "typed_list/tests/__init__.py",
54    "tensor/__init__.py",
55    "tensor/tests/__init__.py",
56    "tensor/tests/test_utils.py",
57    "tensor/tests/test_nlinalg.py",
58    "tensor/tests/test_shared_randomstreams.py",
59    "tensor/tests/test_misc.py",
60    "tensor/tests/mlp_test.py",
61    "tensor/tests/test_opt_uncanonicalize.py",
62    "tensor/tests/test_merge.py",
63    "tensor/tests/test_gc.py",
64    "tensor/tests/test_complex.py",
65    "tensor/tests/test_io.py",
66    "tensor/tests/test_sharedvar.py",
67    "tensor/tests/test_fourier.py",
68    "tensor/tests/test_casting.py",
69    "tensor/tests/test_sort.py",
70    "tensor/tests/test_raw_random.py",
71    "tensor/tests/test_xlogx.py",
72    "tensor/tests/test_slinalg.py",
73    "tensor/tests/test_blas_c.py",
74    "tensor/tests/test_blas_scipy.py",
75    "tensor/tests/test_mpi.py",
76    "tensor/nnet/__init__.py",
77    "tensor/signal/__init__.py",
78    "tensor/signal/tests/__init__.py",
79    "scalar/__init__.py",
80    "scalar/tests/__init__.py",
81    "sandbox/__init__.py",
82    "sandbox/cuda/__init__.py",
83    "sandbox/tests/__init__.py",
84    "sandbox/gpuarray/__init__.py",
85    "sandbox/linalg/__init__.py",
86    "sandbox/linalg/tests/__init__.py",
87    "scan_module/scan_utils.py",
88    "scan_module/scan_views.py",
89    "scan_module/scan.py",
90    "scan_module/scan_perform_ext.py",
91    "scan_module/__init__.py",
92    "scan_module/tests/__init__.py",
93    "scan_module/tests/test_scan.py",
94    "scan_module/tests/test_scan_opt.py",
95    "misc/__init__.py",
96    "misc/tests/__init__.py",
97    "misc/hooks/reindent.py",
98    "misc/hooks/check_whitespace.py",
99    "sparse/__init__.py",
100    "sparse/tests/__init__.py",
101    "sparse/tests/test_utils.py",
102    "sparse/tests/test_opt.py",
103    "sparse/tests/test_basic.py",
104    "sparse/tests/test_sp2.py",
105    "sparse/sandbox/__init__.py",
106    "sparse/sandbox/test_sp.py",
107    "sparse/sandbox/sp2.py",
108    "sparse/sandbox/truedot.py",
109    "sparse/sandbox/sp.py",
110    "gof/__init__.py",
111    "d3viz/__init__.py",
112    "d3viz/tests/__init__.py",
113    "gof/tests/__init__.py",
114]
115
116
117def list_files(dir_path=theano.__path__[0], pattern='*.py', no_match=".#"):
118    """
119    List all files under theano's path.
120    """
121    files_list = []
122    for (dir, _, files) in os.walk(dir_path):
123        for f in files:
124            if fnmatch(f, pattern):
125                path = os.path.join(dir, f)
126                if not f.startswith(no_match):
127                    files_list.append(path)
128    return files_list
129
130
131def test_format_flake8():
132    # Test if flake8 is respected.
133    if not flake8_available:
134        raise SkipTest("flake8 is not installed")
135    total_errors = 0
136
137    files_to_checks = []
138    for path in list_files():
139        rel_path = os.path.relpath(path, theano.__path__[0])
140        if sys.platform == 'win32':
141            rel_path = rel_path.replace('\\', '/')
142        if rel_path in whitelist_flake8:
143            continue
144        else:
145            files_to_checks.append(path)
146
147    if new_flake8:
148        guide = flake8.api.legacy.get_style_guide(ignore=ignore)
149        r = guide.check_files(files_to_checks)
150        total_errors = r.total_errors
151    else:
152        for path in files_to_checks:
153            error_num = flake8.main.check_file(path, ignore=ignore)
154            total_errors += error_num
155
156    if total_errors > 0:
157        raise AssertionError("FLAKE8 Format not respected")
158
159
160def print_files_information_flake8(files):
161    """
162    Print the list of files which can be removed from the whitelist and the
163    list of files which do not respect FLAKE8 formatting that aren't in the
164    whitelist.
165    """
166    infracting_files = []
167    non_infracting_files = []
168    if not files:
169        files = list_files()
170    for path in files:
171        rel_path = os.path.relpath(path, theano.__path__[0])
172        number_of_infractions = flake8.main.check_file(path,
173                                                       ignore=ignore)
174        if number_of_infractions > 0:
175            if rel_path not in whitelist_flake8:
176                infracting_files.append(rel_path)
177        else:
178            if rel_path in whitelist_flake8:
179                non_infracting_files.append(rel_path)
180    print("Files that must be corrected or added to whitelist:")
181    for file in infracting_files:
182        print(file)
183    print("Files that can be removed from whitelist:")
184    for file in non_infracting_files:
185        print(file)
186
187
188def check_all_files(dir_path=theano.__path__[0], pattern='*.py'):
189    """
190    List all .py files under dir_path (theano path), check if they follow
191    flake8 format, save all the error-formatted files into
192    theano_filelist.txt. This function is used for generating
193    the "whitelist_flake8" in this file.
194    """
195
196    with open('theano_filelist.txt', 'a') as f_txt:
197        for (dir, _, files) in os.walk(dir_path):
198            for f in files:
199                if fnmatch(f, pattern):
200                    error_num = flake8.main.check_file(os.path.join(dir, f),
201                                                       ignore=ignore)
202                    if error_num > 0:
203                        path = os.path.relpath(os.path.join(dir, f),
204                                               theano.__path__[0])
205                        f_txt.write('"' + path + '",\n')
206
207
208if __name__ == "__main__":
209    print_files_information_flake8(sys.argv[1:])
210