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