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