| #! /usr/bin/env python |
| # Copyright 2015 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import cStringIO |
| import functools |
| import imp |
| import inspect |
| import itertools |
| import operator |
| import os |
| import re |
| import sys |
| |
| from catapult_base import refactor |
| from catapult_base.refactor_util import move |
| |
| from telemetry.internal.util import command_line |
| from telemetry.internal.util import path |
| |
| |
| _RELATIVE_BASE_DIRS = ( |
| ('chrome', 'test', 'telemetry'), |
| ('content', 'test', 'gpu'), |
| ('tools', 'bisect-manual-test.py'), |
| ('tools', 'chrome_proxy'), |
| ('tools', 'perf'), |
| ('tools', 'profile_chrome', 'perf_controller.py'), |
| ('tools', 'run-bisect-manual-test.py'), |
| ('third_party', 'skia', 'tools', 'skp', 'page_sets'), |
| ('third_party', 'trace-viewer'), |
| ) |
| # All folders dependent on Telemetry, found using a code search. |
| # Note that this is not the same as the directory that imports are relative to. |
| BASE_DIRS = [path.GetTelemetryDir()] + [ |
| os.path.join(path.GetChromiumSrcDir(), *dir_path) |
| for dir_path in _RELATIVE_BASE_DIRS] |
| |
| |
| def SortImportGroups(module_path): |
| """Sort each group of imports in the given Python module. |
| |
| A group is a collection of adjacent import statements, with no non-import |
| lines in between. Groups are sorted according to the Google Python Style |
| Guide: "lexicographically, ignoring case, according to each module's full |
| package path." |
| """ |
| _TransformImportGroups(module_path, _SortImportGroup) |
| |
| |
| def _SortImportGroup(import_group): |
| def _ImportComparator(import1, import2): |
| _, root1, module1, _, _ = import1 |
| _, root2, module2, _, _ = import2 |
| full_module1 = (root1 + '.' + module1 if root1 else module1).lower() |
| full_module2 = (root2 + '.' + module2 if root2 else module2).lower() |
| return cmp(full_module1, full_module2) |
| return sorted(import_group, cmp=_ImportComparator) |
| |
| |
| def _TransformImportGroups(module_path, transformation): |
| """Apply a transformation to each group of imports in the given module. |
| |
| An import is a tuple of (indent, root, module, alias, suffix), |
| serialized as <indent>from <root> import <module> as <alias><suffix>. |
| |
| Args: |
| module_path: The module to apply transformations on. |
| transformation: A function that takes in an import group and returns a |
| modified import group. An import group is a list of import tuples. |
| |
| Returns: |
| True iff the module was modified, and False otherwise. |
| """ |
| def _WriteImports(output_stream, import_group): |
| for indent, root, module, alias, suffix in transformation(import_group): |
| output_stream.write(indent) |
| if root: |
| output_stream.write('from ') |
| output_stream.write(root) |
| output_stream.write(' ') |
| output_stream.write('import ') |
| output_stream.write(module) |
| if alias: |
| output_stream.write(' as ') |
| output_stream.write(alias) |
| output_stream.write(suffix) |
| output_stream.write('\n') |
| |
| # Read the file so we can diff it later to determine if we made any changes. |
| with open(module_path, 'r') as module_file: |
| original_file = module_file.read() |
| |
| # Locate imports using regex, group them, and transform each one. |
| # This regex produces a tuple of (indent, root, module, alias, suffix). |
| regex = (r'(\s*)(?:from ((?:[a-z0-9_]+\.)*[a-z0-9_]+) )?' |
| r'import ((?:[a-z0-9_]+\.)*[A-Za-z0-9_]+)(?: as ([A-Za-z0-9_]+))?(.*)') |
| pattern = re.compile(regex) |
| |
| updated_file = cStringIO.StringIO() |
| with open(module_path, 'r') as module_file: |
| import_group = [] |
| for line in module_file: |
| import_match = pattern.match(line) |
| if import_match: |
| import_group.append(list(import_match.groups())) |
| continue |
| |
| if not import_group: |
| updated_file.write(line) |
| continue |
| |
| _WriteImports(updated_file, import_group) |
| import_group = [] |
| |
| updated_file.write(line) |
| |
| if import_group: |
| _WriteImports(updated_file, import_group) |
| import_group = [] |
| |
| if original_file == updated_file.getvalue(): |
| return False |
| |
| with open(module_path, 'w') as module_file: |
| module_file.write(updated_file.getvalue()) |
| return True |
| |
| |
| def _CountInterfaces(module): |
| return (len(list(module.FindChildren(refactor.Class))) + |
| len(list(module.FindChildren(refactor.Function)))) |
| |
| |
| def _IsSourceDir(dir_name): |
| return dir_name[0] != '.' and dir_name != 'third_party' |
| |
| |
| def _IsPythonModule(file_name): |
| _, ext = os.path.splitext(file_name) |
| return ext == '.py' |
| |
| |
| class Count(command_line.Command): |
| """Print the number of public modules.""" |
| |
| @classmethod |
| def AddCommandLineArgs(cls, parser): |
| parser.add_argument('type', nargs='?', choices=('interfaces', 'modules'), |
| default='modules') |
| |
| def Run(self, args): |
| module_paths = path.ListFiles( |
| path.GetTelemetryDir(), self._IsPublicApiDir, self._IsPublicApiFile) |
| |
| if args.type == 'modules': |
| print len(module_paths) |
| elif args.type == 'interfaces': |
| print reduce(operator.add, refactor.Transform(_CountInterfaces, module_paths)) |
| |
| return 0 |
| |
| @staticmethod |
| def _IsPublicApiDir(dir_name): |
| return (dir_name[0] != '.' and dir_name[0] != '_' and |
| dir_name != 'internal' and dir_name != 'third_party') |
| |
| @staticmethod |
| def _IsPublicApiFile(file_name): |
| root, ext = os.path.splitext(file_name) |
| return (file_name[0] != '.' and |
| not root.endswith('_unittest') and ext == '.py') |
| |
| |
| def _TelemetryFiles(): |
| list_files = functools.partial(path.ListFiles, |
| should_include_dir=_IsSourceDir, |
| should_include_file=_IsPythonModule) |
| return sorted(itertools.chain(*map(list_files, BASE_DIRS))) |
| |
| |
| class Mv(command_line.Command): |
| """Move modules or packages.""" |
| |
| @classmethod |
| def AddCommandLineArgs(cls, parser): |
| parser.add_argument('source', nargs='+') |
| parser.add_argument('target') |
| |
| @classmethod |
| def ProcessCommandLineArgs(cls, parser, args): |
| # Check source file paths. |
| for source_path in args.source: |
| # Ensure source path exists. |
| if not os.path.exists(source_path): |
| parser.error('"%s" not found.' % source_path) |
| |
| # Ensure source path is in one of the BASE_DIRS. |
| for base_dir in BASE_DIRS: |
| if path.IsSubpath(source_path, base_dir): |
| break |
| else: |
| parser.error('"%s" is not in any of the base dirs.') |
| |
| # Ensure target directory exists. |
| if not (os.path.exists(args.target) or |
| os.path.exists(os.path.dirname(args.target))): |
| parser.error('"%s" not found.' % args.target) |
| |
| # Ensure target path is in one of the BASE_DIRS. |
| for base_dir in BASE_DIRS: |
| if path.IsSubpath(args.target, base_dir): |
| break |
| else: |
| parser.error('"%s" is not in any of the base dirs.') |
| |
| # If there are multiple source paths, ensure target is a directory. |
| if len(args.source) > 1 and not os.path.isdir(args.target): |
| parser.error('Target "%s" is not a directory.' % args.target) |
| |
| # Ensure target is not in any of the source paths. |
| for source_path in args.source: |
| if path.IsSubpath(args.target, source_path): |
| parser.error('Cannot move "%s" to a subdirectory of itself, "%s".' % |
| (source_path, args.target)) |
| |
| def Run(self, args): |
| move.Run(args.source, args.target, _TelemetryFiles()) |
| for module_path in _TelemetryFiles(): |
| SortImportGroups(module_path) |
| return 0 |
| |
| |
| class Sort(command_line.Command): |
| """Sort imports.""" |
| |
| @classmethod |
| def AddCommandLineArgs(cls, parser): |
| parser.add_argument('target', nargs='*') |
| |
| @classmethod |
| def ProcessCommandLineArgs(cls, parser, args): |
| for target in args.target: |
| if not os.path.exists(target): |
| parser.error('"%s" not found.' % target) |
| |
| def Run(self, args): |
| if args.target: |
| targets = args.target |
| else: |
| targets = BASE_DIRS |
| |
| for base_dir in targets: |
| for module_path in path.ListFiles(base_dir, _IsSourceDir, _IsPythonModule): |
| SortImportGroups(module_path) |
| return 0 |
| |
| |
| class RefactorCommand(command_line.SubcommandCommand): |
| commands = (Count, Mv, Sort,) |
| |
| |
| if __name__ == '__main__': |
| sys.exit(RefactorCommand.main()) |