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.
16 from catapult_base
import refactor
17 from catapult_base
.refactor_util
import move
19 from telemetry
.internal
.util
import command_line
20 from telemetry
.internal
.util
import path
23 _RELATIVE_BASE_DIRS
= (
24 ('chrome', 'test', 'telemetry'),
25 ('content', 'test', 'gpu'),
26 ('tools', 'bisect-manual-test.py'),
27 ('tools', 'chrome_proxy'),
29 ('tools', 'profile_chrome', 'perf_controller.py'),
30 ('tools', 'run-bisect-manual-test.py'),
31 ('third_party', 'skia', 'tools', 'skp', 'page_sets'),
32 ('third_party', 'trace-viewer'),
34 # All folders dependent on Telemetry, found using a code search.
35 # Note that this is not the same as the directory that imports are relative to.
36 BASE_DIRS
= [path
.GetTelemetryDir()] + [
37 os
.path
.join(path
.GetChromiumSrcDir(), *dir_path
)
38 for dir_path
in _RELATIVE_BASE_DIRS
]
41 def SortImportGroups(module_path
):
42 """Sort each group of imports in the given Python module.
44 A group is a collection of adjacent import statements, with no non-import
45 lines in between. Groups are sorted according to the Google Python Style
46 Guide: "lexicographically, ignoring case, according to each module's full
49 _TransformImportGroups(module_path
, _SortImportGroup
)
52 def _SortImportGroup(import_group
):
53 def _ImportComparator(import1
, import2
):
54 _
, root1
, module1
, _
, _
= import1
55 _
, root2
, module2
, _
, _
= import2
56 full_module1
= (root1
+ '.' + module1
if root1
else module1
).lower()
57 full_module2
= (root2
+ '.' + module2
if root2
else module2
).lower()
58 return cmp(full_module1
, full_module2
)
59 return sorted(import_group
, cmp=_ImportComparator
)
62 def _TransformImportGroups(module_path
, transformation
):
63 """Apply a transformation to each group of imports in the given module.
65 An import is a tuple of (indent, root, module, alias, suffix),
66 serialized as <indent>from <root> import <module> as <alias><suffix>.
69 module_path: The module to apply transformations on.
70 transformation: A function that takes in an import group and returns a
71 modified import group. An import group is a list of import tuples.
74 True iff the module was modified, and False otherwise.
76 def _WriteImports(output_stream
, import_group
):
77 for indent
, root
, module
, alias
, suffix
in transformation(import_group
):
78 output_stream
.write(indent
)
80 output_stream
.write('from ')
81 output_stream
.write(root
)
82 output_stream
.write(' ')
83 output_stream
.write('import ')
84 output_stream
.write(module
)
86 output_stream
.write(' as ')
87 output_stream
.write(alias
)
88 output_stream
.write(suffix
)
89 output_stream
.write('\n')
91 # Read the file so we can diff it later to determine if we made any changes.
92 with
open(module_path
, 'r') as module_file
:
93 original_file
= module_file
.read()
95 # Locate imports using regex, group them, and transform each one.
96 # This regex produces a tuple of (indent, root, module, alias, suffix).
97 regex
= (r
'(\s*)(?:from ((?:[a-z0-9_]+\.)*[a-z0-9_]+) )?'
98 r
'import ((?:[a-z0-9_]+\.)*[A-Za-z0-9_]+)(?: as ([A-Za-z0-9_]+))?(.*)')
99 pattern
= re
.compile(regex
)
101 updated_file
= cStringIO
.StringIO()
102 with
open(module_path
, 'r') as module_file
:
104 for line
in module_file
:
105 import_match
= pattern
.match(line
)
107 import_group
.append(list(import_match
.groups()))
111 updated_file
.write(line
)
114 _WriteImports(updated_file
, import_group
)
117 updated_file
.write(line
)
120 _WriteImports(updated_file
, import_group
)
123 if original_file
== updated_file
.getvalue():
126 with
open(module_path
, 'w') as module_file
:
127 module_file
.write(updated_file
.getvalue())
131 def _CountInterfaces(module
):
132 return (len(list(module
.FindChildren(refactor
.Class
))) +
133 len(list(module
.FindChildren(refactor
.Function
))))
136 def _IsSourceDir(dir_name
):
137 return dir_name
[0] != '.' and dir_name
!= 'third_party'
140 def _IsPythonModule(file_name
):
141 _
, ext
= os
.path
.splitext(file_name
)
145 class Count(command_line
.Command
):
146 """Print the number of public modules."""
149 def AddCommandLineArgs(cls
, parser
):
150 parser
.add_argument('type', nargs
='?', choices
=('interfaces', 'modules'),
154 module_paths
= path
.ListFiles(
155 path
.GetTelemetryDir(), self
._IsPublicApiDir
, self
._IsPublicApiFile
)
157 if args
.type == 'modules':
158 print len(module_paths
)
159 elif args
.type == 'interfaces':
160 print reduce(operator
.add
, refactor
.Transform(_CountInterfaces
, module_paths
))
165 def _IsPublicApiDir(dir_name
):
166 return (dir_name
[0] != '.' and dir_name
[0] != '_' and
167 dir_name
!= 'internal' and dir_name
!= 'third_party')
170 def _IsPublicApiFile(file_name
):
171 root
, ext
= os
.path
.splitext(file_name
)
172 return (file_name
[0] != '.' and
173 not root
.endswith('_unittest') and ext
== '.py')
176 def _TelemetryFiles():
177 list_files
= functools
.partial(path
.ListFiles
,
178 should_include_dir
=_IsSourceDir
,
179 should_include_file
=_IsPythonModule
)
180 return sorted(itertools
.chain(*map(list_files
, BASE_DIRS
)))
183 class Mv(command_line
.Command
):
184 """Move modules or packages."""
187 def AddCommandLineArgs(cls
, parser
):
188 parser
.add_argument('source', nargs
='+')
189 parser
.add_argument('target')
192 def ProcessCommandLineArgs(cls
, parser
, args
):
193 # Check source file paths.
194 for source_path
in args
.source
:
195 # Ensure source path exists.
196 if not os
.path
.exists(source_path
):
197 parser
.error('"%s" not found.' % source_path
)
199 # Ensure source path is in one of the BASE_DIRS.
200 for base_dir
in BASE_DIRS
:
201 if path
.IsSubpath(source_path
, base_dir
):
204 parser
.error('"%s" is not in any of the base dirs.')
206 # Ensure target directory exists.
207 if not (os
.path
.exists(args
.target
) or
208 os
.path
.exists(os
.path
.dirname(args
.target
))):
209 parser
.error('"%s" not found.' % args
.target
)
211 # Ensure target path is in one of the BASE_DIRS.
212 for base_dir
in BASE_DIRS
:
213 if path
.IsSubpath(args
.target
, base_dir
):
216 parser
.error('"%s" is not in any of the base dirs.')
218 # If there are multiple source paths, ensure target is a directory.
219 if len(args
.source
) > 1 and not os
.path
.isdir(args
.target
):
220 parser
.error('Target "%s" is not a directory.' % args
.target
)
222 # Ensure target is not in any of the source paths.
223 for source_path
in args
.source
:
224 if path
.IsSubpath(args
.target
, source_path
):
225 parser
.error('Cannot move "%s" to a subdirectory of itself, "%s".' %
226 (source_path
, args
.target
))
229 move
.Run(args
.source
, args
.target
, _TelemetryFiles())
230 for module_path
in _TelemetryFiles():
231 SortImportGroups(module_path
)
235 class Sort(command_line
.Command
):
239 def AddCommandLineArgs(cls
, parser
):
240 parser
.add_argument('target', nargs
='*')
243 def ProcessCommandLineArgs(cls
, parser
, args
):
244 for target
in args
.target
:
245 if not os
.path
.exists(target
):
246 parser
.error('"%s" not found.' % target
)
250 targets
= args
.target
254 for base_dir
in targets
:
255 for module_path
in path
.ListFiles(base_dir
, _IsSourceDir
, _IsPythonModule
):
256 SortImportGroups(module_path
)
260 class RefactorCommand(command_line
.SubcommandCommand
):
261 commands
= (Count
, Mv
, Sort
,)
264 if __name__
== '__main__':
265 sys
.exit(RefactorCommand
.main())