]>
Commit | Line | Data |
---|---|---|
4710c53d | 1 | import re\r |
2 | import sys\r | |
3 | import shutil\r | |
4 | import os.path\r | |
5 | import subprocess\r | |
6 | \r | |
7 | import reindent\r | |
8 | import untabify\r | |
9 | \r | |
10 | \r | |
11 | def n_files_str(count):\r | |
12 | """Return 'N file(s)' with the proper plurality on 'file'."""\r | |
13 | return "{} file{}".format(count, "s" if count != 1 else "")\r | |
14 | \r | |
15 | \r | |
16 | def status(message, modal=False, info=None):\r | |
17 | """Decorator to output status info to stdout."""\r | |
18 | def decorated_fxn(fxn):\r | |
19 | def call_fxn(*args, **kwargs):\r | |
20 | sys.stdout.write(message + ' ... ')\r | |
21 | sys.stdout.flush()\r | |
22 | result = fxn(*args, **kwargs)\r | |
23 | if not modal and not info:\r | |
24 | print "done"\r | |
25 | elif info:\r | |
26 | print info(result)\r | |
27 | else:\r | |
28 | print "yes" if result else "NO"\r | |
29 | return result\r | |
30 | return call_fxn\r | |
31 | return decorated_fxn\r | |
32 | \r | |
33 | \r | |
34 | @status("Getting the list of files that have been added/changed",\r | |
35 | info=lambda x: n_files_str(len(x)))\r | |
36 | def changed_files():\r | |
37 | """Get the list of changed or added files from the VCS."""\r | |
38 | if os.path.isdir('.hg'):\r | |
39 | vcs = 'hg'\r | |
40 | cmd = 'hg status --added --modified --no-status'\r | |
41 | elif os.path.isdir('.svn'):\r | |
42 | vcs = 'svn'\r | |
43 | cmd = 'svn status --quiet --non-interactive --ignore-externals'\r | |
44 | else:\r | |
45 | sys.exit('need a checkout to get modified files')\r | |
46 | \r | |
47 | st = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)\r | |
48 | try:\r | |
49 | st.wait()\r | |
50 | if vcs == 'hg':\r | |
51 | return [x.decode().rstrip() for x in st.stdout]\r | |
52 | else:\r | |
53 | output = (x.decode().rstrip().rsplit(None, 1)[-1]\r | |
54 | for x in st.stdout if x[0] in 'AM')\r | |
55 | return set(path for path in output if os.path.isfile(path))\r | |
56 | finally:\r | |
57 | st.stdout.close()\r | |
58 | \r | |
59 | \r | |
60 | def report_modified_files(file_paths):\r | |
61 | count = len(file_paths)\r | |
62 | if count == 0:\r | |
63 | return n_files_str(count)\r | |
64 | else:\r | |
65 | lines = ["{}:".format(n_files_str(count))]\r | |
66 | for path in file_paths:\r | |
67 | lines.append(" {}".format(path))\r | |
68 | return "\n".join(lines)\r | |
69 | \r | |
70 | \r | |
71 | @status("Fixing whitespace", info=report_modified_files)\r | |
72 | def normalize_whitespace(file_paths):\r | |
73 | """Make sure that the whitespace for .py files have been normalized."""\r | |
74 | reindent.makebackup = False # No need to create backups.\r | |
75 | fixed = []\r | |
76 | for path in (x for x in file_paths if x.endswith('.py')):\r | |
77 | if reindent.check(path):\r | |
78 | fixed.append(path)\r | |
79 | return fixed\r | |
80 | \r | |
81 | \r | |
82 | @status("Fixing C file whitespace", info=report_modified_files)\r | |
83 | def normalize_c_whitespace(file_paths):\r | |
84 | """Report if any C files """\r | |
85 | fixed = []\r | |
86 | for path in file_paths:\r | |
87 | with open(path, 'r') as f:\r | |
88 | if '\t' not in f.read():\r | |
89 | continue\r | |
90 | untabify.process(path, 8, verbose=False)\r | |
91 | fixed.append(path)\r | |
92 | return fixed\r | |
93 | \r | |
94 | \r | |
95 | ws_re = re.compile(br'\s+(\r?\n)$')\r | |
96 | \r | |
97 | @status("Fixing docs whitespace", info=report_modified_files)\r | |
98 | def normalize_docs_whitespace(file_paths):\r | |
99 | fixed = []\r | |
100 | for path in file_paths:\r | |
101 | try:\r | |
102 | with open(path, 'rb') as f:\r | |
103 | lines = f.readlines()\r | |
104 | new_lines = [ws_re.sub(br'\1', line) for line in lines]\r | |
105 | if new_lines != lines:\r | |
106 | shutil.copyfile(path, path + '.bak')\r | |
107 | with open(path, 'wb') as f:\r | |
108 | f.writelines(new_lines)\r | |
109 | fixed.append(path)\r | |
110 | except Exception as err:\r | |
111 | print 'Cannot fix %s: %s' % (path, err)\r | |
112 | return fixed\r | |
113 | \r | |
114 | \r | |
115 | @status("Docs modified", modal=True)\r | |
116 | def docs_modified(file_paths):\r | |
117 | """Report if any file in the Doc directory has been changed."""\r | |
118 | return bool(file_paths)\r | |
119 | \r | |
120 | \r | |
121 | @status("Misc/ACKS updated", modal=True)\r | |
122 | def credit_given(file_paths):\r | |
123 | """Check if Misc/ACKS has been changed."""\r | |
124 | return 'Misc/ACKS' in file_paths\r | |
125 | \r | |
126 | \r | |
127 | @status("Misc/NEWS updated", modal=True)\r | |
128 | def reported_news(file_paths):\r | |
129 | """Check if Misc/NEWS has been changed."""\r | |
130 | return 'Misc/NEWS' in file_paths\r | |
131 | \r | |
132 | \r | |
133 | def main():\r | |
134 | file_paths = changed_files()\r | |
135 | python_files = [fn for fn in file_paths if fn.endswith('.py')]\r | |
136 | c_files = [fn for fn in file_paths if fn.endswith(('.c', '.h'))]\r | |
137 | doc_files = [fn for fn in file_paths if fn.startswith('Doc')]\r | |
138 | special_files = {'Misc/ACKS', 'Misc/NEWS'} & set(file_paths)\r | |
139 | # PEP 8 whitespace rules enforcement.\r | |
140 | normalize_whitespace(python_files)\r | |
141 | # C rules enforcement.\r | |
142 | normalize_c_whitespace(c_files)\r | |
143 | # Doc whitespace enforcement.\r | |
144 | normalize_docs_whitespace(doc_files)\r | |
145 | # Docs updated.\r | |
146 | docs_modified(doc_files)\r | |
147 | # Misc/ACKS changed.\r | |
148 | credit_given(special_files)\r | |
149 | # Misc/NEWS changed.\r | |
150 | reported_news(special_files)\r | |
151 | \r | |
152 | # Test suite run and passed.\r | |
153 | print\r | |
154 | print "Did you run the test suite?"\r | |
155 | \r | |
156 | \r | |
157 | if __name__ == '__main__':\r | |
158 | main()\r |