blob: 58b081a9c53ff5f17c9bb321299b5e1a376b1850 [file] [log] [blame]
Éric Araujoa0e92a82011-07-26 18:01:08 +02001#!/usr/bin/env python3
Georg Brandla9afb682010-10-21 12:49:28 +00002import re
3import sys
4import shutil
Christian Heimesada8c3b2008-03-18 18:26:33 +00005import os.path
6import subprocess
Éric Araujoa3e072b2011-07-30 21:34:04 +02007import sysconfig
Christian Heimesada8c3b2008-03-18 18:26:33 +00008
9import reindent
Georg Brandla9afb682010-10-21 12:49:28 +000010import untabify
Christian Heimesada8c3b2008-03-18 18:26:33 +000011
12
Éric Araujoa3e072b2011-07-30 21:34:04 +020013SRCDIR = sysconfig.get_config_var('srcdir')
14
15
Brett Cannon058173e2010-07-04 22:05:34 +000016def 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 "")
19
Florent Xiclunae4a33802010-08-09 12:24:20 +000020
Christian Heimesada8c3b2008-03-18 18:26:33 +000021def 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 + ' ... ')
26 sys.stdout.flush()
27 result = fxn(*args, **kwargs)
28 if not modal and not info:
29 print("done")
30 elif info:
31 print(info(result))
32 else:
Florent Xiclunae4a33802010-08-09 12:24:20 +000033 print("yes" if result else "NO")
Christian Heimesada8c3b2008-03-18 18:26:33 +000034 return result
35 return call_fxn
36 return decorated_fxn
37
Florent Xiclunae4a33802010-08-09 12:24:20 +000038
Nadeem Vawda9f64f732012-02-22 11:46:41 +020039def mq_patches_applied():
40 """Check if there are any applied MQ patches."""
41 cmd = 'hg qapplied'
42 with subprocess.Popen(cmd.split(),
43 stdout=subprocess.PIPE,
44 stderr=subprocess.PIPE) as st:
45 bstdout, _ = st.communicate()
46 return st.returncode == 0 and bstdout
47
48
Christian Heimesada8c3b2008-03-18 18:26:33 +000049@status("Getting the list of files that have been added/changed",
Georg Brandla9afb682010-10-21 12:49:28 +000050 info=lambda x: n_files_str(len(x)))
Christian Heimesada8c3b2008-03-18 18:26:33 +000051def changed_files():
Christian Heimesd98c6772015-04-23 11:24:14 +020052 """Get the list of changed or added files from Mercurial or git."""
53 if os.path.isdir(os.path.join(SRCDIR, '.hg')):
54 cmd = 'hg status --added --modified --no-status'
55 if mq_patches_applied():
56 cmd += ' --rev qparent'
57 with subprocess.Popen(cmd.split(), stdout=subprocess.PIPE) as st:
58 return [x.decode().rstrip() for x in st.stdout]
59 elif os.path.isdir(os.path.join(SRCDIR, '.git')):
60 cmd = 'git status --porcelain'
61 filenames = []
62 with subprocess.Popen(cmd.split(), stdout=subprocess.PIPE) as st:
63 for line in st.stdout:
64 line = line.decode().rstrip()
65 status = set(line[:2])
66 # modified, added or unmerged files
67 if not status.intersection('MAU'):
68 continue
69 filename = line[3:]
70 if ' -> ' in filename:
71 # file is renamed
72 filename = filename.split(' -> ', 2)[1].strip()
73 filenames.append(filename)
74 return filenames
75 else:
76 sys.exit('need a Mercurial or git checkout to get modified files')
Florent Xiclunae4a33802010-08-09 12:24:20 +000077
Christian Heimesada8c3b2008-03-18 18:26:33 +000078
Brett Cannon058173e2010-07-04 22:05:34 +000079def report_modified_files(file_paths):
80 count = len(file_paths)
81 if count == 0:
82 return n_files_str(count)
83 else:
84 lines = ["{}:".format(n_files_str(count))]
85 for path in file_paths:
86 lines.append(" {}".format(path))
87 return "\n".join(lines)
88
Florent Xiclunae4a33802010-08-09 12:24:20 +000089
Brett Cannon058173e2010-07-04 22:05:34 +000090@status("Fixing whitespace", info=report_modified_files)
Christian Heimesada8c3b2008-03-18 18:26:33 +000091def normalize_whitespace(file_paths):
92 """Make sure that the whitespace for .py files have been normalized."""
93 reindent.makebackup = False # No need to create backups.
Benjamin Peterson4177eff2011-06-27 18:25:06 -050094 fixed = [path for path in file_paths if path.endswith('.py') and
Éric Araujoad548b82011-07-31 18:33:00 +020095 reindent.check(os.path.join(SRCDIR, path))]
Brett Cannon058173e2010-07-04 22:05:34 +000096 return fixed
Christian Heimesada8c3b2008-03-18 18:26:33 +000097
Florent Xiclunae4a33802010-08-09 12:24:20 +000098
Georg Brandla9afb682010-10-21 12:49:28 +000099@status("Fixing C file whitespace", info=report_modified_files)
100def normalize_c_whitespace(file_paths):
101 """Report if any C files """
102 fixed = []
103 for path in file_paths:
Éric Araujoa3e072b2011-07-30 21:34:04 +0200104 abspath = os.path.join(SRCDIR, path)
105 with open(abspath, 'r') as f:
Georg Brandla9afb682010-10-21 12:49:28 +0000106 if '\t' not in f.read():
107 continue
Éric Araujoa3e072b2011-07-30 21:34:04 +0200108 untabify.process(abspath, 8, verbose=False)
Georg Brandla9afb682010-10-21 12:49:28 +0000109 fixed.append(path)
110 return fixed
111
112
113ws_re = re.compile(br'\s+(\r?\n)$')
114
115@status("Fixing docs whitespace", info=report_modified_files)
116def normalize_docs_whitespace(file_paths):
117 fixed = []
118 for path in file_paths:
Éric Araujoa3e072b2011-07-30 21:34:04 +0200119 abspath = os.path.join(SRCDIR, path)
Georg Brandla9afb682010-10-21 12:49:28 +0000120 try:
Éric Araujoa3e072b2011-07-30 21:34:04 +0200121 with open(abspath, 'rb') as f:
Georg Brandla9afb682010-10-21 12:49:28 +0000122 lines = f.readlines()
123 new_lines = [ws_re.sub(br'\1', line) for line in lines]
124 if new_lines != lines:
Éric Araujoa3e072b2011-07-30 21:34:04 +0200125 shutil.copyfile(abspath, abspath + '.bak')
126 with open(abspath, 'wb') as f:
Georg Brandla9afb682010-10-21 12:49:28 +0000127 f.writelines(new_lines)
128 fixed.append(path)
129 except Exception as err:
130 print('Cannot fix %s: %s' % (path, err))
131 return fixed
132
133
Christian Heimesada8c3b2008-03-18 18:26:33 +0000134@status("Docs modified", modal=True)
135def docs_modified(file_paths):
Brett Cannon058173e2010-07-04 22:05:34 +0000136 """Report if any file in the Doc directory has been changed."""
137 return bool(file_paths)
Christian Heimesada8c3b2008-03-18 18:26:33 +0000138
Florent Xiclunae4a33802010-08-09 12:24:20 +0000139
Christian Heimesada8c3b2008-03-18 18:26:33 +0000140@status("Misc/ACKS updated", modal=True)
141def credit_given(file_paths):
142 """Check if Misc/ACKS has been changed."""
Terry Jan Reedy6e2711b2013-07-21 20:57:44 -0400143 return os.path.join('Misc', 'ACKS') in file_paths
Christian Heimesada8c3b2008-03-18 18:26:33 +0000144
Florent Xiclunae4a33802010-08-09 12:24:20 +0000145
Christian Heimesada8c3b2008-03-18 18:26:33 +0000146@status("Misc/NEWS updated", modal=True)
147def reported_news(file_paths):
148 """Check if Misc/NEWS has been changed."""
Terry Jan Reedy6e2711b2013-07-21 20:57:44 -0400149 return os.path.join('Misc', 'NEWS') in file_paths
Christian Heimesada8c3b2008-03-18 18:26:33 +0000150
Ross Lagerwall6c52c572012-03-11 19:21:07 +0200151@status("configure regenerated", modal=True, info=str)
152def regenerated_configure(file_paths):
153 """Check if configure has been regenerated."""
Matthias Klose5ce31cc2012-03-14 23:17:31 +0100154 if 'configure.ac' in file_paths:
Ross Lagerwall6c52c572012-03-11 19:21:07 +0200155 return "yes" if 'configure' in file_paths else "no"
156 else:
157 return "not needed"
158
159@status("pyconfig.h.in regenerated", modal=True, info=str)
160def regenerated_pyconfig_h_in(file_paths):
161 """Check if pyconfig.h.in has been regenerated."""
Matthias Klose5ce31cc2012-03-14 23:17:31 +0100162 if 'configure.ac' in file_paths:
Ross Lagerwall6c52c572012-03-11 19:21:07 +0200163 return "yes" if 'pyconfig.h.in' in file_paths else "no"
164 else:
165 return "not needed"
Christian Heimesada8c3b2008-03-18 18:26:33 +0000166
167def main():
168 file_paths = changed_files()
Brett Cannon058173e2010-07-04 22:05:34 +0000169 python_files = [fn for fn in file_paths if fn.endswith('.py')]
170 c_files = [fn for fn in file_paths if fn.endswith(('.c', '.h'))]
Georg Brandl24f07172014-10-19 11:54:08 +0200171 doc_files = [fn for fn in file_paths if fn.startswith('Doc') and
172 fn.endswith(('.rst', '.inc'))]
Terry Jan Reedy6e2711b2013-07-21 20:57:44 -0400173 misc_files = {os.path.join('Misc', 'ACKS'), os.path.join('Misc', 'NEWS')}\
174 & set(file_paths)
Brett Cannon058173e2010-07-04 22:05:34 +0000175 # PEP 8 whitespace rules enforcement.
176 normalize_whitespace(python_files)
Georg Brandla9afb682010-10-21 12:49:28 +0000177 # C rules enforcement.
178 normalize_c_whitespace(c_files)
179 # Doc whitespace enforcement.
180 normalize_docs_whitespace(doc_files)
Christian Heimesada8c3b2008-03-18 18:26:33 +0000181 # Docs updated.
Georg Brandla9afb682010-10-21 12:49:28 +0000182 docs_modified(doc_files)
Christian Heimesada8c3b2008-03-18 18:26:33 +0000183 # Misc/ACKS changed.
Terry Jan Reedy6e2711b2013-07-21 20:57:44 -0400184 credit_given(misc_files)
Christian Heimesada8c3b2008-03-18 18:26:33 +0000185 # Misc/NEWS changed.
Terry Jan Reedy6e2711b2013-07-21 20:57:44 -0400186 reported_news(misc_files)
Ross Lagerwall6c52c572012-03-11 19:21:07 +0200187 # Regenerated configure, if necessary.
188 regenerated_configure(file_paths)
189 # Regenerated pyconfig.h.in, if necessary.
190 regenerated_pyconfig_h_in(file_paths)
Christian Heimesada8c3b2008-03-18 18:26:33 +0000191
192 # Test suite run and passed.
Éric Araujofbc5ff62011-08-12 17:50:08 +0200193 if python_files or c_files:
Ezio Melotti5e12bb72013-01-11 14:07:47 +0200194 end = " and check for refleaks?" if c_files else "?"
Éric Araujofbc5ff62011-08-12 17:50:08 +0200195 print()
Ezio Melotti5e12bb72013-01-11 14:07:47 +0200196 print("Did you run the test suite" + end)
Christian Heimesada8c3b2008-03-18 18:26:33 +0000197
198
199if __name__ == '__main__':
200 main()