Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Usage: mffr.py [-d] [-g *.h] [-g *.cc] REGEXP REPLACEMENT | 6 """Usage: mffr.py [-d] [-g *.h] [-g *.cc] REGEXP REPLACEMENT |
| 7 | 7 |
| 8 This tool performs a fast find-and-replace operation on files in | 8 This tool performs a fast find-and-replace operation on files in |
| 9 the current git repository. | 9 the current git repository. |
| 10 | 10 |
| 11 The -d flag selects a default set of globs (C++ and Objective-C/C++ | 11 The -d flag selects a default set of globs (C++ and Objective-C/C++ |
| 12 source files). The -g flag adds a single glob to the list and may | 12 source files). The -g flag adds a single glob to the list and may |
| 13 be used multiple times. If neither -d nor -g is specified, the tool | 13 be used multiple times. If neither -d nor -g is specified, the tool |
| 14 searches all files (*.*). | 14 searches all files (*.*). |
| 15 | 15 |
| 16 REGEXP uses full Python regexp syntax. REPLACEMENT can use | 16 REGEXP uses full Python regexp syntax. REPLACEMENT can use |
| 17 back-references. | 17 back-references. |
| 18 """ | 18 """ |
| 19 | 19 |
| 20 import optparse | |
| 20 import re | 21 import re |
| 21 import subprocess | 22 import subprocess |
| 22 import sys | 23 import sys |
| 23 | 24 |
| 24 | 25 |
| 25 # We need to use shell=True with subprocess on Windows so that it | 26 # We need to use shell=True with subprocess on Windows so that it |
| 26 # finds 'git' from the path, but can lead to undesired behavior on | 27 # finds 'git' from the path, but can lead to undesired behavior on |
| 27 # Linux. | 28 # Linux. |
| 28 _USE_SHELL = (sys.platform == 'win32') | 29 _USE_SHELL = (sys.platform == 'win32') |
| 29 | 30 |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 66 if contents == original_contents: | 67 if contents == original_contents: |
| 67 raise Exception('No change in file %s although matched in grep' % | 68 raise Exception('No change in file %s although matched in grep' % |
| 68 referee) | 69 referee) |
| 69 with open(referee, 'wb') as f: | 70 with open(referee, 'wb') as f: |
| 70 f.write(contents) | 71 f.write(contents) |
| 71 | 72 |
| 72 return referees | 73 return referees |
| 73 | 74 |
| 74 | 75 |
| 75 def main(): | 76 def main(): |
| 76 file_globs = [] | 77 parser = optparse.OptionParser(usage=''' |
| 77 force_unsafe_run = False | 78 (1) %prog <options> REGEXP REPLACEMENT |
| 78 args = sys.argv[1:] | 79 REGEXP uses full Python regexp syntax. REPLACEMENT can use back-references. |
| 79 while args and args[0].startswith('-'): | 80 |
| 80 if args[0] == '-d': | 81 (2) %prog <options> -i <file> |
| 81 file_globs = ['*.cc', '*.h', '*.m', '*.mm'] | 82 <file> should contain a list of [REGEXP, REPLACEMENT, [GLOBS]] lists, e.g.: |
|
Jói
2013/06/21 11:29:35
Perhaps make it more explicit, "...a list (in Pyth
blundell
2013/06/21 13:33:07
Done.
| |
| 82 args = args[1:] | 83 [ |
| 83 elif args[0] == '-g': | 84 [r"(foo|bar)", r"\1baz", ["*.cc", "*.h"]], |
| 84 file_globs.append(args[1]) | 85 ["54", "42"], |
| 85 args = args[2:] | 86 ] |
| 86 elif args[0] == '-f': | 87 As shown above, [GLOBS] can be omitted for a given search-replace list, in which |
| 87 force_unsafe_run = True | 88 case the corresponding search-replace will use the globs specified on the |
| 88 args = args[1:] | 89 command line.''') |
| 89 if not file_globs: | 90 parser.add_option('-d', action='store_true', |
| 90 file_globs = ['*.*'] | 91 dest='use_default_glob', |
| 91 if not args: | 92 help='Perform the change on C++ and Objective-C(++) source ' |
| 92 print globals()['__doc__'] | 93 'and header files.') |
| 94 parser.add_option('-f', action='store_true', | |
| 95 dest='force_unsafe_run', | |
| 96 help='Perform the run even if there are uncommitted local ' | |
| 97 'changes.') | |
| 98 parser.add_option('-g', action='append', | |
| 99 type='string', | |
| 100 default=[], | |
| 101 metavar="<glob>", | |
| 102 dest='user_supplied_globs', | |
| 103 help='Perform the change on the specified glob. Can be ' | |
| 104 'specified multiple times, in which case the globs are ' | |
| 105 'unioned.') | |
| 106 parser.add_option('-i', "--input_file", | |
| 107 type='string', | |
| 108 action='store', | |
| 109 default='', | |
| 110 metavar="<file>", | |
| 111 dest='input_filename', | |
| 112 help='Read arguments from <file> rather than the command ' | |
| 113 'line. NOTE: To be sure of regular expressions being ' | |
| 114 'interpreted correctly, use raw strings.') | |
| 115 opts, args = parser.parse_args() | |
| 116 if opts.use_default_glob and opts.user_supplied_globs: | |
| 117 print '"-d" and "-g" cannot be used together' | |
| 118 parser.print_help() | |
| 93 return 1 | 119 return 1 |
| 94 if not force_unsafe_run: | 120 |
| 121 from_file = opts.input_filename != "" | |
| 122 if (from_file and len(args) != 0) or (not from_file and len(args) != 2): | |
| 123 parser.print_help() | |
| 124 return 1 | |
| 125 | |
| 126 if not opts.force_unsafe_run: | |
| 95 out, err = subprocess.Popen(['git', 'status', '--porcelain'], | 127 out, err = subprocess.Popen(['git', 'status', '--porcelain'], |
| 96 stdout=subprocess.PIPE, | 128 stdout=subprocess.PIPE, |
| 97 shell=_USE_SHELL).communicate() | 129 shell=_USE_SHELL).communicate() |
| 98 if out: | 130 if out: |
| 99 print 'ERROR: This tool does not print any confirmation prompts,' | 131 print 'ERROR: This tool does not print any confirmation prompts,' |
| 100 print 'so you should only run it with a clean staging area and cache' | 132 print 'so you should only run it with a clean staging area and cache' |
| 101 print 'so that reverting a bad find/replace is as easy as running' | 133 print 'so that reverting a bad find/replace is as easy as running' |
| 102 print ' git checkout -- .' | 134 print ' git checkout -- .' |
| 103 print '' | 135 print '' |
| 104 print 'To override this safeguard, pass the -f flag.' | 136 print 'To override this safeguard, pass the -f flag.' |
| 105 return 1 | 137 return 1 |
| 106 original = args[0] | 138 |
| 107 replacement = args[1] | 139 global_file_globs = ['*.*'] |
| 108 print 'File globs: %s' % file_globs | 140 if opts.use_default_glob: |
| 109 print 'Original: %s' % original | 141 global_file_globs = ['*.cc', '*.h', '*.m', '*.mm'] |
| 110 print 'Replacement: %s' % replacement | 142 elif opts.user_supplied_globs: |
| 111 MultiFileFindReplace(original, replacement, file_globs) | 143 global_file_globs = opts.user_supplied_globs |
| 144 | |
| 145 # Construct list of search-replace tasks. | |
| 146 search_replace_tasks = [] | |
| 147 if opts.input_filename == '': | |
| 148 original = args[0] | |
| 149 replacement = args[1] | |
| 150 search_replace_tasks.append([original, replacement, global_file_globs]) | |
| 151 else: | |
| 152 f = open(opts.input_filename) | |
| 153 search_replace_tasks = eval("".join(f.readlines())) | |
| 154 for task in search_replace_tasks: | |
| 155 if len(task) == 2: | |
| 156 task.append(global_file_globs) | |
| 157 f.close() | |
| 158 | |
| 159 for (original, replacement, file_globs) in search_replace_tasks: | |
| 160 print 'File globs: %s' % file_globs | |
| 161 print 'Original: %s' % original | |
| 162 print 'Replacement: %s' % replacement | |
| 163 MultiFileFindReplace(original, replacement, file_globs) | |
| 112 return 0 | 164 return 0 |
| 113 | 165 |
| 114 | 166 |
| 115 if __name__ == '__main__': | 167 if __name__ == '__main__': |
| 116 sys.exit(main()) | 168 sys.exit(main()) |
| OLD | NEW |