11#!/usr/bin/env python3
22"""Check proposed changes for common issues."""
3+ import re
34import sys
5+ import shutil
46import os .path
57import subprocess
68import sysconfig
79
10+ import reindent
11+ import untabify
12+
13+
814def get_python_source_dir ():
915 src_dir = sysconfig .get_config_var ('abs_srcdir' )
1016 if not src_dir :
1117 src_dir = sysconfig .get_config_var ('srcdir' )
1218 return os .path .abspath (src_dir )
19+
20+
21+ # Excluded directories which are copies of external libraries:
22+ # don't check their coding style
23+ EXCLUDE_DIRS = [
24+ os .path .join ('Modules' , '_decimal' , 'libmpdec' ),
25+ os .path .join ('Modules' , 'expat' ),
26+ os .path .join ('Modules' , 'zlib' ),
27+ ]
1328SRCDIR = get_python_source_dir ()
1429
1530
@@ -140,8 +155,62 @@ def changed_files(base_branch=None):
140155 else :
141156 sys .exit ('need a git checkout to get modified files' )
142157
143- # Normalize the path to be able to match using str.startswith()
144- return list (map (os .path .normpath , filenames ))
158+ filenames2 = []
159+ for filename in filenames :
160+ # Normalize the path to be able to match using .startswith()
161+ filename = os .path .normpath (filename )
162+ if any (filename .startswith (path ) for path in EXCLUDE_DIRS ):
163+ # Exclude the file
164+ continue
165+ filenames2 .append (filename )
166+
167+ return filenames2
168+
169+
170+ def report_modified_files (file_paths ):
171+ count = len (file_paths )
172+ if count == 0 :
173+ return n_files_str (count )
174+ else :
175+ lines = [f"{ n_files_str (count )} :" ]
176+ for path in file_paths :
177+ lines .append (f" { path } " )
178+ return "\n " .join (lines )
179+
180+
181+ #: Python files that have tabs by design:
182+ _PYTHON_FILES_WITH_TABS = frozenset ({
183+ 'Tools/c-analyzer/cpython/_parser.py' ,
184+ })
185+
186+
187+ @status ("Fixing Python file whitespace" , info = report_modified_files )
188+ def normalize_whitespace (file_paths ):
189+ """Make sure that the whitespace for .py files have been normalized."""
190+ reindent .makebackup = False # No need to create backups.
191+ fixed = [
192+ path for path in file_paths
193+ if (
194+ path .endswith ('.py' )
195+ and path not in _PYTHON_FILES_WITH_TABS
196+ and reindent .check (os .path .join (SRCDIR , path ))
197+ )
198+ ]
199+ return fixed
200+
201+
202+ @status ("Fixing C file whitespace" , info = report_modified_files )
203+ def normalize_c_whitespace (file_paths ):
204+ """Report if any C files """
205+ fixed = []
206+ for path in file_paths :
207+ abspath = os .path .join (SRCDIR , path )
208+ with open (abspath , 'r' ) as f :
209+ if '\t ' not in f .read ():
210+ continue
211+ untabify .process (abspath , 8 , verbose = False )
212+ fixed .append (path )
213+ return fixed
145214
146215
147216@status ("Docs modified" , modal = True )
@@ -181,12 +250,38 @@ def regenerated_pyconfig_h_in(file_paths):
181250 return "not needed"
182251
183252
253+ def ci (pull_request ):
254+ if pull_request == 'false' :
255+ print ('Not a pull request; skipping' )
256+ return
257+ base_branch = get_base_branch ()
258+ file_paths = changed_files (base_branch )
259+ python_files = [fn for fn in file_paths if fn .endswith ('.py' )]
260+ c_files = [fn for fn in file_paths if fn .endswith (('.c' , '.h' ))]
261+ fixed = []
262+ fixed .extend (normalize_whitespace (python_files ))
263+ fixed .extend (normalize_c_whitespace (c_files ))
264+ if not fixed :
265+ print ('No whitespace issues found' )
266+ else :
267+ count = len (fixed )
268+ print (f'Please fix the { n_files_str (count )} with whitespace issues' )
269+ print ('(on Unix you can run `make patchcheck` to make the fixes)' )
270+ sys .exit (1 )
271+
272+
184273def main ():
185274 base_branch = get_base_branch ()
186275 file_paths = changed_files (base_branch )
276+ python_files = [fn for fn in file_paths if fn .endswith ('.py' )]
277+ c_files = [fn for fn in file_paths if fn .endswith (('.c' , '.h' ))]
187278 doc_files = [fn for fn in file_paths if fn .startswith ('Doc' ) and
188279 fn .endswith (('.rst' , '.inc' ))]
189280 misc_files = {p for p in file_paths if p .startswith ('Misc' )}
281+ # PEP 8 whitespace rules enforcement.
282+ normalize_whitespace (python_files )
283+ # C rules enforcement.
284+ normalize_c_whitespace (c_files )
190285 # Docs updated.
191286 docs_modified (doc_files )
192287 # Misc/ACKS changed.
@@ -199,14 +294,19 @@ def main():
199294 regenerated_pyconfig_h_in (file_paths )
200295
201296 # Test suite run and passed.
202- has_c_files = any (fn for fn in file_paths if fn .endswith (('.c' , '.h' )))
203- has_python_files = any (fn for fn in file_paths if fn .endswith ('.py' ))
204- print ()
205- if has_c_files :
206- print ("Did you run the test suite and check for refleaks?" )
207- elif has_python_files :
208- print ("Did you run the test suite?" )
297+ if python_files or c_files :
298+ end = " and check for refleaks?" if c_files else "?"
299+ print ()
300+ print ("Did you run the test suite" + end )
209301
210302
211303if __name__ == '__main__' :
212- main ()
304+ import argparse
305+ parser = argparse .ArgumentParser (description = __doc__ )
306+ parser .add_argument ('--ci' ,
307+ help = 'Perform pass/fail checks' )
308+ args = parser .parse_args ()
309+ if args .ci :
310+ ci (args .ci )
311+ else :
312+ main ()
0 commit comments