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.
13 from telemetry
.internal
.util
import command_line
14 from telemetry
.internal
.util
import path
17 # All folders dependent on Telemetry, found using a code search.
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'),
34 def SortImportGroups(module_path
):
35 """Sort each group of imports in the given Python module.
37 A group is a collection of adjacent import statements, with no non-import
38 lines in between. Groups are sorted according to the Google Python Style
39 Guide: "lexicographically, ignoring case, according to each module's full
42 _TransformImportGroups(module_path
, _SortImportGroup
)
45 def _SortImportGroup(import_group
):
46 def _ImportComparator(import1
, import2
):
47 _
, root1
, module1
, _
, _
= import1
48 _
, root2
, module2
, _
, _
= import2
49 full_module1
= (root1
+ '.' + module1
if root1
else module1
).lower()
50 full_module2
= (root2
+ '.' + module2
if root2
else module2
).lower()
51 return cmp(full_module1
, full_module2
)
52 return sorted(import_group
, cmp=_ImportComparator
)
55 def _TransformImportGroups(module_path
, transformation
):
56 """Apply a transformation to each group of imports in the given module.
58 An import is a tuple of (indent, root, module, alias, suffix),
59 serialized as <indent>from <root> import <module> as <alias><suffix>.
62 module_path: The module to apply transformations on.
63 transformation: A function that takes in an import group and returns a
64 modified import group. An import group is a list of import tuples.
67 True iff the module was modified, and False otherwise.
69 def _WriteImports(output_stream
, import_group
):
70 for indent
, root
, module
, alias
, suffix
in transformation(import_group
):
71 output_stream
.write(indent
)
73 output_stream
.write('from ')
74 output_stream
.write(root
)
75 output_stream
.write(' ')
76 output_stream
.write('import ')
77 output_stream
.write(module
)
79 output_stream
.write(' as ')
80 output_stream
.write(alias
)
81 output_stream
.write(suffix
)
82 output_stream
.write('\n')
84 # Read the file so we can diff it later to determine if we made any changes.
85 with
open(module_path
, 'r') as module_file
:
86 original_file
= module_file
.read()
88 # Locate imports using regex, group them, and transform each one.
89 # This regex produces a tuple of (indent, root, module, alias, suffix).
90 regex
= (r
'(\s*)(?:from ((?:[a-z0-9_]+\.)*[a-z0-9_]+) )?'
91 r
'import ((?:[a-z0-9_]+\.)*[A-Za-z0-9_]+)(?: as ([A-Za-z0-9_]+))?(.*)')
92 pattern
= re
.compile(regex
)
94 updated_file
= cStringIO
.StringIO()
95 with
open(module_path
, 'r') as module_file
:
97 for line
in module_file
:
98 import_match
= pattern
.match(line
)
100 import_group
.append(list(import_match
.groups()))
104 updated_file
.write(line
)
107 _WriteImports(updated_file
, import_group
)
110 updated_file
.write(line
)
113 _WriteImports(updated_file
, import_group
)
116 if original_file
== updated_file
.getvalue():
119 with
open(module_path
, 'w') as module_file
:
120 module_file
.write(updated_file
.getvalue())
124 def _ListFiles(base_directory
, should_include_dir
, should_include_file
):
126 for root
, dirs
, files
in os
.walk(base_directory
):
127 dirs
[:] = [dir_name
for dir_name
in dirs
if should_include_dir(dir_name
)]
128 matching_files
+= [os
.path
.join(root
, file_name
)
129 for file_name
in files
if should_include_file(file_name
)]
130 return sorted(matching_files
)
133 def _IsSourceDir(dir_name
):
134 return dir_name
[0] != '.' and dir_name
!= 'third_party'
137 def _IsPythonModule(file_name
):
138 _
, ext
= os
.path
.splitext(file_name
)
142 class Count(command_line
.Command
):
143 """Print the number of public modules."""
146 modules
= _ListFiles(path
.GetTelemetryDir(),
147 self
._IsPublicApiDir
, self
._IsPublicApiFile
)
152 def _IsPublicApiDir(dir_name
):
153 return (dir_name
[0] != '.' and dir_name
[0] != '_' and
154 not dir_name
.startswith('internal') and not dir_name
== 'third_party')
157 def _IsPublicApiFile(file_name
):
158 root
, ext
= os
.path
.splitext(file_name
)
159 return (file_name
[0] != '.' and
160 not root
.endswith('_unittest') and ext
== '.py')
163 class Mv(command_line
.Command
):
164 """Move modules or packages."""
167 def AddCommandLineArgs(cls
, parser
):
168 parser
.add_argument('source', nargs
='+')
169 parser
.add_argument('destination')
172 def ProcessCommandLineArgs(cls
, parser
, args
):
173 for source
in args
.source
:
174 # Ensure source path exists.
175 if not os
.path
.exists(source
):
176 parser
.error('"%s" not found.' % source
)
178 # Ensure source path is in one of the BASE_DIRS.
179 for base_dir
in BASE_DIRS
:
180 if path
.IsSubpath(source
, base_dir
):
183 parser
.error('Source path "%s" is not in any of the base dirs.')
185 # Ensure destination path exists.
186 if not os
.path
.exists(args
.destination
):
187 parser
.error('"%s" not found.' % args
.destination
)
189 # Ensure destination path is in one of the BASE_DIRS.
190 for base_dir
in BASE_DIRS
:
191 if path
.IsSubpath(args
.destination
, base_dir
):
194 parser
.error('Destination path "%s" is not in any of the base dirs.')
196 # If there are multiple source paths, ensure destination is a directory.
197 if len(args
.source
) > 1 and not os
.path
.isdir(args
.destination
):
198 parser
.error('Target "%s" is not a directory.' % args
.destination
)
200 # Ensure destination is not in any of the source paths.
201 for source
in args
.source
:
202 if path
.IsSubpath(args
.destination
, source
):
203 parser
.error('Cannot move "%s" to a subdirectory of itself, "%s".' %
204 (source
, args
.destination
))
207 for dest_base_dir
in BASE_DIRS
:
208 if path
.IsSubpath(args
.destination
, dest_base_dir
):
211 # Get a list of old and new module names for renaming imports.
213 for source
in args
.source
:
214 for source_base_dir
in BASE_DIRS
:
215 if path
.IsSubpath(source
, source_base_dir
):
218 source_dir
= os
.path
.dirname(os
.path
.normpath(source
))
220 if os
.path
.isdir(source
):
221 source_files
= _ListFiles(source
, _IsSourceDir
, _IsPythonModule
)
223 source_files
= (source
,)
225 for source_file_path
in source_files
:
226 source_rel_path
= os
.path
.relpath(source_file_path
, source_base_dir
)
227 source_module_name
= os
.path
.splitext(
228 source_rel_path
)[0].replace(os
.sep
, '.')
230 source_tree
= os
.path
.relpath(source_file_path
, source_dir
)
231 dest_path
= os
.path
.join(args
.destination
, source_tree
)
232 dest_rel_path
= os
.path
.relpath(dest_path
, dest_base_dir
)
233 dest_module_name
= os
.path
.splitext(
234 dest_rel_path
)[0].replace(os
.sep
, '.')
236 moved_modules
[source_module_name
] = dest_module_name
239 if os
.path
.isdir(args
.destination
):
240 for source
in args
.source
:
241 destination_path
= os
.path
.join(
242 args
.destination
, os
.path
.split(os
.path
.normpath(source
))[1])
243 os
.rename(source
, destination_path
)
245 assert len(args
.source
) == 1
246 os
.rename(args
.source
.pop(), args
.destination
)
249 def _UpdateImportGroup(import_group
):
251 for import_line
in import_group
:
252 _
, root
, module
, _
, _
= import_line
253 full_module
= root
+ '.' + module
if root
else module
255 if full_module
not in moved_modules
:
260 # Update import line.
261 new_root
, _
, new_module
= moved_modules
[full_module
].rpartition('.')
262 import_line
[1] = new_root
263 import_line
[2] = new_module
266 return _SortImportGroup(import_group
)
270 for base_dir
in BASE_DIRS
:
271 for module_path
in _ListFiles(base_dir
, _IsSourceDir
, _IsPythonModule
):
272 if not _TransformImportGroups(module_path
, _UpdateImportGroup
):
275 # TODO(dtu): Update occurrences.
282 class Sort(command_line
.Command
):
286 def AddCommandLineArgs(cls
, parser
):
287 parser
.add_argument('target', nargs
='*')
290 def ProcessCommandLineArgs(cls
, parser
, args
):
291 for target
in args
.target
:
292 if not os
.path
.exists(target
):
293 parser
.error('"%s" not found.' % target
)
297 targets
= args
.target
301 for base_dir
in targets
:
302 for module_path
in _ListFiles(base_dir
, _IsSourceDir
, _IsPythonModule
):
303 SortImportGroups(module_path
)
307 class RefactorCommand(command_line
.SubcommandCommand
):
308 commands
= (Count
, Mv
, Sort
,)
311 if __name__
== '__main__':
312 sys
.exit(RefactorCommand
.main())