Chromium Code Reviews| 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 |