OLD | NEW |
---|---|
(Empty) | |
1 #! /usr/bin/env python | |
2 # Copyright 2015 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 import cStringIO | |
7 import imp | |
8 import inspect | |
9 import os | |
10 import re | |
11 import sys | |
12 | |
13 from telemetry.core import command_line | |
14 from telemetry.util import path | |
15 | |
16 | |
17 # All folders dependent on Telemetry, found using a code search. | |
18 BASE_DIRS = ( | |
19 path.GetTelemetryDir(), | |
20 os.path.join(path.GetChromiumSrcDir(), 'chrome', 'test', 'telemetry'), | |
21 os.path.join(path.GetChromiumSrcDir(), 'content', 'test', 'gpu'), | |
22 os.path.join(path.GetChromiumSrcDir(), 'tools', 'bisect-manual-test.py'), | |
23 os.path.join(path.GetChromiumSrcDir(), 'tools', 'chrome_proxy'), | |
24 os.path.join(path.GetChromiumSrcDir(), 'tools', 'perf'), | |
25 os.path.join(path.GetChromiumSrcDir(), | |
26 'tools', 'profile_chrome', 'perf_controller.py'), | |
27 os.path.join(path.GetChromiumSrcDir(), 'tools', 'run-bisect-manual-test.py'), | |
28 os.path.join(path.GetChromiumSrcDir(), | |
29 'third_party', 'skia', 'tools', 'skp', 'page_sets'), | |
30 os.path.join(path.GetChromiumSrcDir(), 'third_party', 'trace-viewer', | |
eakuefner
2015/03/27 17:55:05
I'm a little worried about potentially accidentall
dtu
2015/03/27 19:17:15
Since we plan on using Telemetry in the future, it
| |
31 'third_party', 'tvcm', 'tvcm', 'browser_controller.py'), | |
32 ) | |
33 | |
34 | |
35 def SortImportGroups(module_path): | |
36 """Sort each group of imports in the given Python module. | |
37 | |
38 A group is a collection of adjacent import statements, with no non-import | |
39 lines in between. Groups are sorted according to the Google Python Style | |
40 Guide: "lexicographically, ignoring case, according to each module's full | |
41 package path." | |
42 """ | |
43 _TransformImportGroups(module_path, _SortImportGroup) | |
44 | |
45 | |
46 def _SortImportGroup(import_group): | |
47 def _ImportComparator(import1, import2): | |
48 _, root1, module1, _, _ = import1 | |
49 _, root2, module2, _, _ = import2 | |
50 full_module1 = (root1 + '.' + module1 if root1 else module1).lower() | |
51 full_module2 = (root2 + '.' + module2 if root2 else module2).lower() | |
52 return cmp(full_module1, full_module2) | |
53 return sorted(import_group, cmp=_ImportComparator) | |
54 | |
55 | |
56 def _TransformImportGroups(module_path, transformation): | |
57 """Apply a transformation to each group of imports in the given module.""" | |
eakuefner
2015/03/27 17:55:06
Please write the expected type of `transformation`
dtu
2015/03/27 19:17:15
I did it anyway, since the function is non-trivial
| |
58 def _WriteImports(output_stream, import_group): | |
59 for indent, root, module, alias, suffix in transformation(import_group): | |
60 output_stream.write(indent) | |
61 if root: | |
62 output_stream.write('from ') | |
63 output_stream.write(root) | |
64 output_stream.write(' ') | |
65 output_stream.write('import ') | |
66 output_stream.write(module) | |
67 if alias: | |
68 output_stream.write(' as ') | |
69 output_stream.write(alias) | |
70 output_stream.write(suffix) | |
71 output_stream.write('\n') | |
72 | |
73 # Read the file so we can diff it later to determine if we made any changes. | |
74 with open(module_path, 'r') as module_file: | |
75 original_file = module_file.read() | |
76 | |
77 # Locate imports using regex, group them, and transform each one. | |
78 # This regex produces a tuple of (indent, root, module, alias, suffix). | |
79 regex = (r'(\s*)(?:from ((?:[a-z0-9_]+\.)*[a-z0-9_]+) )?' | |
80 r'import ((?:[a-z0-9_]+\.)*[A-Za-z0-9_]+)(?: as ([A-Za-z0-9_]+))?(.*)') | |
81 pattern = re.compile(regex) | |
82 | |
83 updated_file = cStringIO.StringIO() | |
84 with open(module_path, 'r') as module_file: | |
85 import_group = [] | |
86 for line in module_file: | |
87 import_match = pattern.match(line) | |
88 if import_match: | |
89 import_group.append(list(import_match.groups())) | |
90 continue | |
91 | |
92 if not import_group: | |
93 updated_file.write(line) | |
94 continue | |
95 | |
96 _WriteImports(updated_file, import_group) | |
97 import_group = [] | |
98 | |
99 Updated_file.write(line) | |
100 | |
101 if import_group: | |
102 _WriteImports(updated_file, import_group) | |
103 import_group = [] | |
104 | |
105 if original_file == updated_file.getvalue(): | |
106 return False | |
107 | |
108 with open(module_path, 'w') as module_file: | |
109 module_file.write(updated_file.getvalue()) | |
110 return True | |
111 | |
112 | |
113 def _ListFiles(base_directory, should_include_dir, should_include_file): | |
114 matching_files = [] | |
115 for root, dirs, files in os.walk(base_directory): | |
116 dirs[:] = [dir_name for dir_name in dirs if should_include_dir(dir_name)] | |
117 matching_files += [os.path.join(root, file_name) | |
118 for file_name in files if should_include_file(file_name)] | |
119 return sorted(matching_files) | |
120 | |
121 | |
122 class Count(command_line.Command): | |
123 """Print the number of public modules.""" | |
124 | |
125 def Run(self, args): | |
126 modules = _ListFiles(path.GetTelemetryDir(), | |
127 self._IsPublicApiDir, self._IsPublicApiFile) | |
128 print len(modules) | |
129 return 0 | |
130 | |
131 @staticmethod | |
132 def _IsPublicApiDir(dir_name): | |
133 return (dir_name[0] != '.' and dir_name[0] != '_' and | |
134 not dir_name.startswith('internal') and not dir_name == 'third_party') | |
135 | |
136 @staticmethod | |
137 def _IsPublicApiFile(file_name): | |
138 root, ext = os.path.splitext(file_name) | |
139 return (file_name[0] != '.' and | |
140 not root.endswith('_unittest') and ext == '.py') | |
141 | |
142 | |
143 class Mv(command_line.Command): | |
144 """Move modules or packages.""" | |
145 | |
146 @classmethod | |
147 def AddCommandLineArgs(cls, parser): | |
148 parser.add_argument('source', nargs='+') | |
149 parser.add_argument('destination') | |
150 | |
151 @classmethod | |
152 def ProcessCommandLineArgs(cls, parser, args): | |
153 for source in args.source: | |
154 # Ensure source path exists. | |
155 if not os.path.exists(source): | |
156 parser.error('"%s" not found.' % source) | |
157 | |
158 # Ensure source path is in one of the BASE_DIRS. | |
159 for base_dir in BASE_DIRS: | |
160 if path.InDirectory(source, base_dir): | |
161 break | |
162 else: | |
163 parser.error('Source path "%s" is not in any of the base dirs.') | |
164 | |
165 # Ensure destination path exists. | |
166 if not os.path.exists(args.destination): | |
167 parser.error('"%s" not found.' % args.destination) | |
168 | |
169 # Ensure destination path is in one of the BASE_DIRS. | |
170 for base_dir in BASE_DIRS: | |
171 if path.InDirectory(args.destination, base_dir): | |
172 break | |
173 else: | |
174 parser.error('Destination path "%s" is not in any of the base dirs.') | |
175 | |
176 # If there are multiple source paths, ensure destination is a directory. | |
177 if len(args.source) > 1 and not os.path.isdir(args.destination): | |
178 parser.error('Target "%s" is not a directory.' % args.destination) | |
179 | |
180 # Ensure destination is not in any of the source paths. | |
181 for source in args.source: | |
182 if path.InDirectory(args.destination, source): | |
183 parser.error('Cannot move "%s" to a subdirectory of itself, "%s".' % | |
184 (source, args.destination)) | |
185 | |
186 def Run(self, args): | |
187 for dest_base_dir in BASE_DIRS: | |
188 if path.InDirectory(args.destination, dest_base_dir): | |
189 break | |
190 | |
191 # Get a list of old and new module names for renaming imports. | |
192 moved_modules = {} | |
193 for source in args.source: | |
194 for source_base_dir in BASE_DIRS: | |
195 if path.InDirectory(source, source_base_dir): | |
196 break | |
197 | |
198 source_dir = os.path.dirname(os.path.normpath(source)) | |
199 | |
200 if os.path.isdir(source): | |
201 source_files = _ListFiles(source, | |
202 self._IsSourceDir, self._IsPythonModule) | |
203 else: | |
204 source_files = (source,) | |
205 | |
206 for source_file_path in source_files: | |
207 source_rel_path = os.path.relpath(source_file_path, source_base_dir) | |
208 source_module_name = os.path.splitext( | |
209 source_rel_path)[0].replace(os.sep, '.') | |
210 | |
211 source_tree = os.path.relpath(source_file_path, source_dir) | |
212 dest_path = os.path.join(args.destination, source_tree) | |
213 dest_rel_path = os.path.relpath(dest_path, dest_base_dir) | |
214 dest_module_name = os.path.splitext( | |
215 dest_rel_path)[0].replace(os.sep, '.') | |
216 | |
217 moved_modules[source_module_name] = dest_module_name | |
218 | |
219 # Move things! | |
220 if os.path.isdir(args.destination): | |
221 for source in args.source: | |
222 destination_path = os.path.join( | |
223 args.destination, os.path.split(os.path.normpath(source))[1]) | |
224 os.rename(source, destination_path) | |
225 else: | |
226 assert len(args.source) == 1 | |
227 os.rename(args.source.pop(), args.destination) | |
228 | |
229 # Update imports! | |
230 def _UpdateImportGroup(import_group): | |
231 modified = False | |
232 for import_line in import_group: | |
233 _, root, module, _, _ = import_line | |
234 full_module = root + '.' + module if root else module | |
235 | |
236 if full_module not in moved_modules: | |
237 continue | |
238 | |
239 modified = True | |
240 | |
241 # Update import line. | |
242 new_root, _, new_module = moved_modules[full_module].rpartition('.') | |
243 import_line[1] = new_root | |
244 import_line[2] = new_module | |
245 | |
246 if modified: | |
247 return _SortImportGroup(import_group) | |
248 else: | |
249 return import_group | |
250 | |
251 for base_dir in BASE_DIRS: | |
252 for module_path in _ListFiles(base_dir, | |
253 self._IsSourceDir, self._IsPythonModule): | |
254 if not _TransformImportGroups(module_path, _UpdateImportGroup): | |
255 continue | |
256 | |
257 # TODO(dtu): Update occurrences. | |
258 | |
259 print moved_modules | |
260 | |
261 return 0 | |
262 | |
263 @staticmethod | |
264 def _IsSourceDir(dir_name): | |
265 return dir_name[0] != '.' and dir_name != 'third_party' | |
266 | |
267 @staticmethod | |
268 def _IsPythonModule(file_name): | |
269 _, ext = os.path.splitext(file_name) | |
270 return ext == '.py' | |
271 | |
272 | |
273 class RefactorCommand(command_line.SubcommandCommand): | |
eakuefner
2015/03/27 17:55:05
Do we get usage text for free with this?
dtu
2015/03/27 19:17:15
Yes, but its default formatting is like so-so.
| |
274 commands = (Count, Mv,) | |
275 | |
276 | |
277 if __name__ == '__main__': | |
278 sys.exit(RefactorCommand.main()) | |
OLD | NEW |