blob: 438e44eeff8a0ce65e288836ef592fccaecdbc35 [file] [log] [blame]
Éric Araujo0fb681e2011-07-29 12:06:13 +02001#!/usr/bin/env python
Georg Brandlef212e02010-11-26 08:04:57 +00002import re
3import sys
4import shutil
Brett Cannona8b09fd2008-03-18 17:25:13 +00005import os.path
6import subprocess
Éric Araujo35a7f552011-07-30 21:34:04 +02007import sysconfig
Brett Cannona8b09fd2008-03-18 17:25:13 +00008
9import reindent
Georg Brandlef212e02010-11-26 08:04:57 +000010import untabify
11
12
Éric Araujo35a7f552011-07-30 21:34:04 +020013SRCDIR = sysconfig.get_config_var('srcdir')
14
15
Georg Brandlef212e02010-11-26 08:04:57 +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 "")
Brett Cannona8b09fd2008-03-18 17:25:13 +000019
20
21def 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:
Georg Brandlef212e02010-11-26 08:04:57 +000033 print "yes" if result else "NO"
Brett Cannona8b09fd2008-03-18 17:25:13 +000034 return result
35 return call_fxn
36 return decorated_fxn
37
Brett Cannona8b09fd2008-03-18 17:25:13 +000038
Nadeem Vawdaf00011a2012-02-22 11:40:09 +020039def mq_patches_applied():
40 """Check if there are any applied MQ patches."""
41 cmd = 'hg qapplied'
42 st = subprocess.Popen(cmd.split(),
43 stdout=subprocess.PIPE,
44 stderr=subprocess.PIPE)
45 try:
46 bstdout, _ = st.communicate()
47 return st.returncode == 0 and bstdout
48 finally:
49 st.stdout.close()
50 st.stderr.close()
51
52
Georg Brandlef212e02010-11-26 08:04:57 +000053@status("Getting the list of files that have been added/changed",
54 info=lambda x: n_files_str(len(x)))
55def changed_files():
56 """Get the list of changed or added files from the VCS."""
Éric Araujo35a7f552011-07-30 21:34:04 +020057 if os.path.isdir(os.path.join(SRCDIR, '.hg')):
Georg Brandlef212e02010-11-26 08:04:57 +000058 vcs = 'hg'
59 cmd = 'hg status --added --modified --no-status'
Nadeem Vawdaf00011a2012-02-22 11:40:09 +020060 if mq_patches_applied():
61 cmd += ' --rev qparent'
Georg Brandlef212e02010-11-26 08:04:57 +000062 elif os.path.isdir('.svn'):
63 vcs = 'svn'
64 cmd = 'svn status --quiet --non-interactive --ignore-externals'
65 else:
66 sys.exit('need a checkout to get modified files')
67
68 st = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
69 try:
70 st.wait()
71 if vcs == 'hg':
72 return [x.decode().rstrip() for x in st.stdout]
73 else:
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))
77 finally:
78 st.stdout.close()
79
80
81def report_modified_files(file_paths):
82 count = len(file_paths)
83 if count == 0:
84 return n_files_str(count)
85 else:
86 lines = ["{}:".format(n_files_str(count))]
87 for path in file_paths:
88 lines.append(" {}".format(path))
89 return "\n".join(lines)
90
91
92@status("Fixing whitespace", info=report_modified_files)
Brett Cannona8b09fd2008-03-18 17:25:13 +000093def 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.
Georg Brandlef212e02010-11-26 08:04:57 +000096 fixed = []
97 for path in (x for x in file_paths if x.endswith('.py')):
Éric Araujo35a7f552011-07-30 21:34:04 +020098 if reindent.check(os.path.join(SRCDIR, path)):
Georg Brandlef212e02010-11-26 08:04:57 +000099 fixed.append(path)
100 return fixed
101
102
103@status("Fixing C file whitespace", info=report_modified_files)
104def normalize_c_whitespace(file_paths):
105 """Report if any C files """
106 fixed = []
107 for path in file_paths:
Éric Araujo35a7f552011-07-30 21:34:04 +0200108 abspath = os.path.join(SRCDIR, path)
109 with open(abspath, 'r') as f:
Georg Brandlef212e02010-11-26 08:04:57 +0000110 if '\t' not in f.read():
111 continue
Éric Araujo35a7f552011-07-30 21:34:04 +0200112 untabify.process(abspath, 8, verbose=False)
Georg Brandlef212e02010-11-26 08:04:57 +0000113 fixed.append(path)
114 return fixed
115
116
117ws_re = re.compile(br'\s+(\r?\n)$')
118
119@status("Fixing docs whitespace", info=report_modified_files)
120def normalize_docs_whitespace(file_paths):
121 fixed = []
122 for path in file_paths:
Éric Araujo35a7f552011-07-30 21:34:04 +0200123 abspath = os.path.join(SRCDIR, path)
Georg Brandlef212e02010-11-26 08:04:57 +0000124 try:
Éric Araujo35a7f552011-07-30 21:34:04 +0200125 with open(abspath, 'rb') as f:
Georg Brandlef212e02010-11-26 08:04:57 +0000126 lines = f.readlines()
127 new_lines = [ws_re.sub(br'\1', line) for line in lines]
128 if new_lines != lines:
Éric Araujo35a7f552011-07-30 21:34:04 +0200129 shutil.copyfile(abspath, abspath + '.bak')
130 with open(abspath, 'wb') as f:
Georg Brandlef212e02010-11-26 08:04:57 +0000131 f.writelines(new_lines)
132 fixed.append(path)
133 except Exception as err:
134 print 'Cannot fix %s: %s' % (path, err)
135 return fixed
136
Brett Cannona8b09fd2008-03-18 17:25:13 +0000137
138@status("Docs modified", modal=True)
139def docs_modified(file_paths):
Georg Brandlef212e02010-11-26 08:04:57 +0000140 """Report if any file in the Doc directory has been changed."""
141 return bool(file_paths)
142
Brett Cannona8b09fd2008-03-18 17:25:13 +0000143
144@status("Misc/ACKS updated", modal=True)
145def credit_given(file_paths):
146 """Check if Misc/ACKS has been changed."""
Benjamin Petersoneee95832009-01-10 17:18:55 +0000147 return 'Misc/ACKS' in file_paths
Brett Cannona8b09fd2008-03-18 17:25:13 +0000148
Georg Brandlef212e02010-11-26 08:04:57 +0000149
Brett Cannona8b09fd2008-03-18 17:25:13 +0000150@status("Misc/NEWS updated", modal=True)
151def reported_news(file_paths):
152 """Check if Misc/NEWS has been changed."""
Benjamin Petersoneee95832009-01-10 17:18:55 +0000153 return 'Misc/NEWS' in file_paths
Brett Cannona8b09fd2008-03-18 17:25:13 +0000154
155
156def main():
157 file_paths = changed_files()
Georg Brandlef212e02010-11-26 08:04:57 +0000158 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)
Brett Cannona8b09fd2008-03-18 17:25:13 +0000168 # Docs updated.
Georg Brandlef212e02010-11-26 08:04:57 +0000169 docs_modified(doc_files)
Brett Cannona8b09fd2008-03-18 17:25:13 +0000170 # Misc/ACKS changed.
Georg Brandlef212e02010-11-26 08:04:57 +0000171 credit_given(special_files)
Brett Cannona8b09fd2008-03-18 17:25:13 +0000172 # Misc/NEWS changed.
Georg Brandlef212e02010-11-26 08:04:57 +0000173 reported_news(special_files)
Brett Cannona8b09fd2008-03-18 17:25:13 +0000174
175 # Test suite run and passed.
Éric Araujoa5afa492011-08-19 08:41:00 +0200176 if python_files or c_files:
177 print
178 print "Did you run the test suite?"
Brett Cannona8b09fd2008-03-18 17:25:13 +0000179
180
181if __name__ == '__main__':
182 main()