13 SRCDIR = sysconfig.get_config_var('srcdir')
16 def n_files_str(count):
17 """Return 'N file(s)' with the proper plurality on 'file'."""
18 return "{} file{}".format(count, "s" if count != 1 else "")
21 def status(message, modal=False, info=None):
22 """Decorator to output status info to stdout."""
23 def decorated_fxn(fxn):
24 def call_fxn(*args, **kwargs):
25 sys.stdout.write(message + ' ... ')
27 result = fxn(*args, **kwargs)
28 if not modal and not info:
33 print "yes" if result else "NO"
39 def mq_patches_applied():
40 """Check if there are any applied MQ patches."""
42 st = subprocess.Popen(cmd.split(),
43 stdout=subprocess.PIPE,
44 stderr=subprocess.PIPE)
46 bstdout, _ = st.communicate()
47 return st.returncode == 0 and bstdout
53 @status("Getting the list of files that have been added/changed",
54 info=lambda x: n_files_str(len(x)))
56 """Get the list of changed or added files from the VCS."""
57 if os.path.isdir(os.path.join(SRCDIR, '.hg')):
59 cmd = 'hg status --added --modified --no-status'
60 if mq_patches_applied():
61 cmd += ' --rev qparent'
62 elif os.path.isdir('.svn'):
64 cmd = 'svn status --quiet --non-interactive --ignore-externals'
66 sys.exit('need a checkout to get modified files')
68 st = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
72 return [x.decode().rstrip() for x in st.stdout]
74 output = (x.decode().rstrip().rsplit(None, 1)[-1]
75 for x in st.stdout if x[0] in 'AM')
76 return set(path for path in output if os.path.isfile(path))
81 def report_modified_files(file_paths):
82 count = len(file_paths)
84 return n_files_str(count)
86 lines = ["{}:".format(n_files_str(count))]
87 for path in file_paths:
88 lines.append(" {}".format(path))
89 return "\n".join(lines)
92 @status("Fixing whitespace", info=report_modified_files)
93 def normalize_whitespace(file_paths):
94 """Make sure that the whitespace for .py files have been normalized."""
95 reindent.makebackup = False # No need to create backups.
97 for path in (x for x in file_paths if x.endswith('.py')):
98 if reindent.check(os.path.join(SRCDIR, path)):
103 @status("Fixing C file whitespace", info=report_modified_files)
104 def normalize_c_whitespace(file_paths):
105 """Report if any C files """
107 for path in file_paths:
108 abspath = os.path.join(SRCDIR, path)
109 with open(abspath, 'r') as f:
110 if '\t' not in f.read():
112 untabify.process(abspath, 8, verbose=False)
117 ws_re = re.compile(br'\s+(\r?\n)$')
119 @status("Fixing docs whitespace", info=report_modified_files)
120 def normalize_docs_whitespace(file_paths):
122 for path in file_paths:
123 abspath = os.path.join(SRCDIR, path)
125 with open(abspath, 'rb') as f:
126 lines = f.readlines()
127 new_lines = [ws_re.sub(br'\1', line) for line in lines]
128 if new_lines != lines:
129 shutil.copyfile(abspath, abspath + '.bak')
130 with open(abspath, 'wb') as f:
131 f.writelines(new_lines)
133 except Exception as err:
134 print 'Cannot fix %s: %s' % (path, err)
138 @status("Docs modified", modal=True)
139 def docs_modified(file_paths):
140 """Report if any file in the Doc directory has been changed."""
141 return bool(file_paths)
144 @status("Misc/ACKS updated", modal=True)
145 def credit_given(file_paths):
146 """Check if Misc/ACKS has been changed."""
147 return 'Misc/ACKS' in file_paths
150 @status("Misc/NEWS updated", modal=True)
151 def reported_news(file_paths):
152 """Check if Misc/NEWS has been changed."""
153 return 'Misc/NEWS' in file_paths
157 file_paths = changed_files()
158 python_files = [fn for fn in file_paths if fn.endswith('.py')]
159 c_files = [fn for fn in file_paths if fn.endswith(('.c', '.h'))]
160 doc_files = [fn for fn in file_paths if fn.startswith('Doc')]
161 special_files = {'Misc/ACKS', 'Misc/NEWS'} & set(file_paths)
162 # PEP 8 whitespace rules enforcement.
163 normalize_whitespace(python_files)
164 # C rules enforcement.
165 normalize_c_whitespace(c_files)
166 # Doc whitespace enforcement.
167 normalize_docs_whitespace(doc_files)
169 docs_modified(doc_files)
171 credit_given(special_files)
173 reported_news(special_files)
175 # Test suite run and passed.
176 if python_files or c_files:
178 print "Did you run the test suite?"
181 if __name__ == '__main__':