[391] | 1 | #!/usr/bin/env python
|
---|
| 2 | import re
|
---|
| 3 | import sys
|
---|
| 4 | import shutil
|
---|
[2] | 5 | import os.path
|
---|
| 6 | import subprocess
|
---|
[391] | 7 | import sysconfig
|
---|
[2] | 8 |
|
---|
| 9 | import reindent
|
---|
[391] | 10 | import untabify
|
---|
[2] | 11 |
|
---|
| 12 |
|
---|
[391] | 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 |
|
---|
[2] | 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:
|
---|
[391] | 33 | print "yes" if result else "NO"
|
---|
[2] | 34 | return result
|
---|
| 35 | return call_fxn
|
---|
| 36 | return decorated_fxn
|
---|
| 37 |
|
---|
[391] | 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 |
|
---|
[2] | 53 | @status("Getting the list of files that have been added/changed",
|
---|
[391] | 54 | info=lambda x: n_files_str(len(x)))
|
---|
[2] | 55 | def changed_files():
|
---|
[391] | 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')
|
---|
[2] | 67 |
|
---|
[391] | 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)
|
---|
[2] | 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.
|
---|
[391] | 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
|
---|
[2] | 101 |
|
---|
[391] | 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 |
|
---|
[2] | 138 | @status("Docs modified", modal=True)
|
---|
| 139 | def docs_modified(file_paths):
|
---|
[391] | 140 | """Report if any file in the Doc directory has been changed."""
|
---|
| 141 | return bool(file_paths)
|
---|
[2] | 142 |
|
---|
[391] | 143 |
|
---|
[2] | 144 | @status("Misc/ACKS updated", modal=True)
|
---|
| 145 | def credit_given(file_paths):
|
---|
| 146 | """Check if Misc/ACKS has been changed."""
|
---|
[391] | 147 | return os.path.join('Misc', 'ACKS') in file_paths
|
---|
[2] | 148 |
|
---|
[391] | 149 |
|
---|
[2] | 150 | @status("Misc/NEWS updated", modal=True)
|
---|
| 151 | def reported_news(file_paths):
|
---|
| 152 | """Check if Misc/NEWS has been changed."""
|
---|
[391] | 153 | return os.path.join('Misc', 'NEWS') in file_paths
|
---|
[2] | 154 |
|
---|
| 155 |
|
---|
| 156 | def main():
|
---|
| 157 | file_paths = changed_files()
|
---|
[391] | 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)
|
---|
[2] | 169 | # Docs updated.
|
---|
[391] | 170 | docs_modified(doc_files)
|
---|
[2] | 171 | # Misc/ACKS changed.
|
---|
[391] | 172 | credit_given(misc_files)
|
---|
[2] | 173 | # Misc/NEWS changed.
|
---|
[391] | 174 | reported_news(misc_files)
|
---|
[2] | 175 |
|
---|
| 176 | # Test suite run and passed.
|
---|
[391] | 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
|
---|
[2] | 181 |
|
---|
| 182 |
|
---|
| 183 | if __name__ == '__main__':
|
---|
| 184 | main()
|
---|