OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # Copyright (c) 2012 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. | |
5 | |
6 """Moves a C++ file to a new location, updating any include paths that | |
7 point to it. Updates include guards in moved header files. Assumes | |
8 Chromium coding style. | |
9 | |
10 Does not reorder headers (you can use tools/sort-headers.py), and does | |
11 not update .gypi files. | |
12 | |
13 Relies on git for a fast way to find files that include the moved file. | |
14 """ | |
15 | |
16 | |
17 import os | |
18 import subprocess | |
19 import sys | |
20 | |
21 | |
22 HANDLED_EXTENSIONS = ['.cc', '.mm', '.h', '.hh'] | |
23 | |
24 | |
25 def MoveFile(from_path, to_path): | |
26 """Moves a file from |from_path| to |to_path|, updating its include | |
27 guard to match the new path and updating all #includes and #imports | |
28 of the file in other files in the same git repository, with the | |
29 assumption that they include it using the Chromium style | |
30 guide-standard full path from root. | |
31 """ | |
32 extension = os.path.splitext(from_path)[1] | |
33 if extension not in HANDLED_EXTENSIONS: | |
34 raise Exception('Only intended to move individual source files.') | |
35 | |
36 dest_extension = os.path.splitext(to_path)[1] | |
37 if dest_extension not in HANDLED_EXTENSIONS: | |
38 if to_path.endswith('/') or to_path.endswith('\\'): | |
39 to_path += os.path.basename(from_path) | |
40 else: | |
41 raise Exception('Destination must be either full path or end with /.') | |
42 | |
43 if not os.system('git mv %s %s' % (from_path, to_path)) == 0: | |
44 raise Exception('Fatal: Failed to run git mv command.') | |
45 | |
46 if extension in ['.h', '.hh']: | |
47 UpdateIncludeGuard(from_path, to_path) | |
48 UpdateIncludes(from_path, to_path) | |
49 | |
50 | |
51 def MakeIncludeGuardName(path_from_root): | |
52 """Returns an include guard name given a path from root.""" | |
53 guard = path_from_root.replace('/', '_') | |
54 guard = guard.replace('\\', '_') | |
55 guard = guard.replace('.', '_') | |
56 guard += '_' | |
57 return guard.upper() | |
58 | |
59 | |
60 def UpdateIncludeGuard(old_path, new_path): | |
61 """Updates the include guard in a file now residing at |new_path|, | |
62 previously residing at |old_path|, with an up-to-date include guard. | |
63 | |
64 Errors out if an include guard per Chromium style guide cannot be | |
65 found for the old path. | |
66 """ | |
67 old_guard = MakeIncludeGuardName(old_path) | |
68 new_guard = MakeIncludeGuardName(new_path) | |
69 | |
70 with open(new_path) as f: | |
71 contents = f.read() | |
72 | |
73 new_contents = contents.replace(old_guard, new_guard) | |
74 if new_contents == contents: | |
75 raise Exception( | |
76 'Error updating include guard; perhaps old guard is not per style guide?') | |
77 | |
78 with open(new_path, 'w') as f: | |
79 f.write(new_contents) | |
80 | |
81 | |
82 def UpdateIncludes(old_path, new_path): | |
83 """Given the |old_path| and |new_path| of a file being moved, update | |
84 #include and #import statements in all files in the same git | |
85 repository referring to the moved file. | |
86 """ | |
87 # Include paths always use forward slashes. | |
88 old_path = old_path.replace('\\', '/') | |
89 new_path = new_path.replace('\\', '/') | |
90 | |
91 out, err = subprocess.Popen( | |
92 ['git', 'grep', '--name-only', | |
93 r'#\(include\|import\)\s*["<]%s[>"]' % old_path], | |
94 stdout=subprocess.PIPE).communicate() | |
95 includees = out.splitlines() | |
96 | |
97 for includee in includees: | |
98 with open(includee) as f: | |
99 contents = f.read() | |
100 new_contents = contents.replace('"%s"' % old_path, '"%s"' % new_path) | |
101 new_contents = new_contents.replace('<%s>' % old_path, '<%s>' % new_path) | |
102 if new_contents == contents: | |
103 raise Exception('Error updating include in file %s' % includee) | |
104 with open(includee, 'w') as f: | |
105 f.write(new_contents) | |
106 | |
107 | |
108 def main(): | |
109 if not os.path.isdir('.git'): | |
110 print 'Fatal: You must run from the root of a git checkout.' | |
111 return 1 | |
112 args = sys.argv[1:] | |
113 if len(args) != 2: | |
114 print 'Usage: move_file.py FROM_PATH TO_PATH\n\n%s' % __doc__ | |
115 return 1 | |
116 MoveFile(args[0], args[1]) | |
117 return 0 | |
118 | |
119 | |
120 if __name__ == '__main__': | |
121 sys.exit(main()) | |
OLD | NEW |