1# Copyright 2015 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5from __future__ import print_function 6 7import functools 8import os 9import sys 10 11from py_utils import refactor 12 13 14def Run(sources, target, files_to_update): 15 """Move modules and update imports. 16 17 Args: 18 sources: List of source module or package paths. 19 target: Destination module or package path. 20 files_to_update: Modules whose imports we should check for changes. 21 """ 22 # TODO(dtu): Support moving classes and functions. 23 moves = tuple(_Move(source, target) for source in sources) 24 25 # Update imports and references. 26 refactor.Transform(functools.partial(_Update, moves), files_to_update) 27 28 # Move files. 29 for move in moves: 30 os.rename(move.source_path, move.target_path) 31 32 33def _Update(moves, module): 34 for import_statement in module.FindAll(refactor.Import): 35 for move in moves: 36 try: 37 if move.UpdateImportAndReferences(module, import_statement): 38 break 39 except NotImplementedError as e: 40 print('Error updating %s: %s' % (module.file_path, e), file=sys.stderr) 41 42 43class _Move(object): 44 45 def __init__(self, source, target): 46 self._source_path = os.path.realpath(source) 47 self._target_path = os.path.realpath(target) 48 49 if os.path.isdir(self._target_path): 50 self._target_path = os.path.join( 51 self._target_path, os.path.basename(self._source_path)) 52 53 @property 54 def source_path(self): 55 return self._source_path 56 57 @property 58 def target_path(self): 59 return self._target_path 60 61 @property 62 def source_module_path(self): 63 return _ModulePath(self._source_path) 64 65 @property 66 def target_module_path(self): 67 return _ModulePath(self._target_path) 68 69 def UpdateImportAndReferences(self, module, import_statement): 70 """Update an import statement in a module and all its references.. 71 72 Args: 73 module: The refactor.Module to update. 74 import_statement: The refactor.Import to update. 75 76 Returns: 77 True if the import statement was updated, or False if the import statement 78 needed no updating. 79 """ 80 statement_path_parts = import_statement.path.split('.') 81 source_path_parts = self.source_module_path.split('.') 82 if source_path_parts != statement_path_parts[:len(source_path_parts)]: 83 return False 84 85 # Update import statement. 86 old_name_parts = import_statement.name.split('.') 87 new_name_parts = ([self.target_module_path] + 88 statement_path_parts[len(source_path_parts):]) 89 import_statement.path = '.'.join(new_name_parts) 90 new_name = import_statement.name 91 92 # Update references. 93 for reference in module.FindAll(refactor.Reference): 94 reference_parts = reference.value.split('.') 95 if old_name_parts != reference_parts[:len(old_name_parts)]: 96 continue 97 98 new_reference_parts = [new_name] + reference_parts[len(old_name_parts):] 99 reference.value = '.'.join(new_reference_parts) 100 101 return True 102 103 104def _BaseDir(module_path): 105 if not os.path.isdir(module_path): 106 module_path = os.path.dirname(module_path) 107 108 while '__init__.py' in os.listdir(module_path): 109 module_path = os.path.dirname(module_path) 110 111 return module_path 112 113 114def _ModulePath(module_path): 115 if os.path.split(module_path)[1] == '__init__.py': 116 module_path = os.path.dirname(module_path) 117 rel_path = os.path.relpath(module_path, _BaseDir(module_path)) 118 return os.path.splitext(rel_path)[0].replace(os.sep, '.') 119