1 | #!/usr/bin/env python
|
---|
2 | import re
|
---|
3 | import sys
|
---|
4 | import shutil
|
---|
5 | import os.path
|
---|
6 | import subprocess
|
---|
7 | import sysconfig
|
---|
8 |
|
---|
9 | import reindent
|
---|
10 | import untabify
|
---|
11 |
|
---|
12 |
|
---|
13 | SRCDIR = sysconfig.get_config_var('srcdir')
|
---|
14 |
|
---|
15 |
|
---|
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 "")
|
---|
19 |
|
---|
20 |
|
---|
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 + ' ... ')
|
---|
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:
|
---|
33 | print "yes" if result else "NO"
|
---|
34 | return result
|
---|
35 | return call_fxn
|
---|
36 | return decorated_fxn
|
---|
37 |
|
---|
38 |
|
---|
39 | def 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 |
|
---|
53 | @status("Getting the list of files that have been added/changed",
|
---|
54 | info=lambda x: n_files_str(len(x)))
|
---|
55 | def changed_files():
|
---|
56 | """Get the list of changed or added files from the VCS."""
|
---|
57 | if os.path.isdir(os.path.join(SRCDIR, '.hg')):
|
---|
58 | vcs = 'hg'
|
---|
59 | cmd = 'hg status --added --modified --no-status'
|
---|
60 | if mq_patches_applied():
|
---|
61 | cmd += ' --rev qparent'
|
---|
62 | 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 |
|
---|
81 | def 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)
|
---|
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.
|
---|
96 | fixed = []
|
---|
97 | for path in (x for x in file_paths if x.endswith('.py')):
|
---|
98 | if reindent.check(os.path.join(SRCDIR, path)):
|
---|
99 | fixed.append(path)
|
---|
100 | return fixed
|
---|
101 |
|
---|
102 |
|
---|
103 | @status("Fixing C file whitespace", info=report_modified_files)
|
---|
104 | def normalize_c_whitespace(file_paths):
|
---|
105 | """Report if any C files """
|
---|
106 | fixed = []
|
---|
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():
|
---|
111 | continue
|
---|
112 | untabify.process(abspath, 8, verbose=False)
|
---|
113 | fixed.append(path)
|
---|
114 | return fixed
|
---|
115 |
|
---|
116 |
|
---|
117 | ws_re = re.compile(br'\s+(\r?\n)$')
|
---|
118 |
|
---|
119 | @status("Fixing docs whitespace", info=report_modified_files)
|
---|
120 | def normalize_docs_whitespace(file_paths):
|
---|
121 | fixed = []
|
---|
122 | for path in file_paths:
|
---|
123 | abspath = os.path.join(SRCDIR, path)
|
---|
124 | try:
|
---|
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)
|
---|
132 | fixed.append(path)
|
---|
133 | except Exception as err:
|
---|
134 | print 'Cannot fix %s: %s' % (path, err)
|
---|
135 | return fixed
|
---|
136 |
|
---|
137 |
|
---|
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)
|
---|
142 |
|
---|
143 |
|
---|
144 | @status("Misc/ACKS updated", modal=True)
|
---|
145 | def credit_given(file_paths):
|
---|
146 | """Check if Misc/ACKS has been changed."""
|
---|
147 | return os.path.join('Misc', 'ACKS') in file_paths
|
---|
148 |
|
---|
149 |
|
---|
150 | @status("Misc/NEWS updated", modal=True)
|
---|
151 | def reported_news(file_paths):
|
---|
152 | """Check if Misc/NEWS has been changed."""
|
---|
153 | return os.path.join('Misc', 'NEWS') in file_paths
|
---|
154 |
|
---|
155 |
|
---|
156 | def main():
|
---|
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 | misc_files = {os.path.join('Misc', 'ACKS'), os.path.join('Misc', 'NEWS')}\
|
---|
162 | & set(file_paths)
|
---|
163 | # PEP 8 whitespace rules enforcement.
|
---|
164 | normalize_whitespace(python_files)
|
---|
165 | # C rules enforcement.
|
---|
166 | normalize_c_whitespace(c_files)
|
---|
167 | # Doc whitespace enforcement.
|
---|
168 | normalize_docs_whitespace(doc_files)
|
---|
169 | # Docs updated.
|
---|
170 | docs_modified(doc_files)
|
---|
171 | # Misc/ACKS changed.
|
---|
172 | credit_given(misc_files)
|
---|
173 | # Misc/NEWS changed.
|
---|
174 | reported_news(misc_files)
|
---|
175 |
|
---|
176 | # Test suite run and passed.
|
---|
177 | if python_files or c_files:
|
---|
178 | end = " and check for refleaks?" if c_files else "?"
|
---|
179 | print
|
---|
180 | print "Did you run the test suite" + end
|
---|
181 |
|
---|
182 |
|
---|
183 | if __name__ == '__main__':
|
---|
184 | main()
|
---|