| 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 """Given a filename as an argument, sort the #include/#imports in that file. |
| 7 |
| 8 Shows a diff and prompts for confirmation before doing the deed. |
| 9 Works great with tools/git/for-all-touched-files.py. |
| 10 """ |
| 11 |
| 12 import optparse |
| 13 import os |
| 14 import sys |
| 15 import termios |
| 16 import tty |
| 17 |
| 18 |
| 19 def YesNo(prompt): |
| 20 """Prompts with a yes/no question, returns True if yes.""" |
| 21 print prompt, |
| 22 sys.stdout.flush() |
| 23 # http://code.activestate.com/recipes/134892/ |
| 24 fd = sys.stdin.fileno() |
| 25 old_settings = termios.tcgetattr(fd) |
| 26 ch = 'n' |
| 27 try: |
| 28 tty.setraw(sys.stdin.fileno()) |
| 29 ch = sys.stdin.read(1) |
| 30 finally: |
| 31 termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) |
| 32 print ch |
| 33 return ch in ('Y', 'y') |
| 34 |
| 35 |
| 36 def IncludeCompareKey(line): |
| 37 """Sorting comparator key used for comparing two #include lines. |
| 38 Returns the filename without the #include/#import prefix. |
| 39 """ |
| 40 line = line.lower() |
| 41 for prefix in ('#include ', '#import '): |
| 42 if line.startswith(prefix): |
| 43 line = line[len(prefix):] |
| 44 break |
| 45 |
| 46 # The win32 api has all sorts of implicit include order dependencies :-/ |
| 47 # Give a few headers special sort keys that make sure they appear before all |
| 48 # other headers. |
| 49 if line.startswith('<windows.h>'): # Must be before e.g. shellapi.h |
| 50 return '0' |
| 51 if line.startswith('<unknwn.h>'): # Must be before e.g. intshcut.h |
| 52 return '1' |
| 53 |
| 54 # C++ system headers should come after C system headers. |
| 55 if line.startswith('<'): |
| 56 if line.find('.h>') != -1: |
| 57 return '2' + line |
| 58 else: |
| 59 return '3' + line |
| 60 |
| 61 return '4' + line |
| 62 |
| 63 |
| 64 def IsInclude(line): |
| 65 """Returns True if the line is an #include/#import line.""" |
| 66 return line.startswith('#include ') or line.startswith('#import ') |
| 67 |
| 68 |
| 69 def SortHeader(infile, outfile): |
| 70 """Sorts the headers in infile, writing the sorted file to outfile.""" |
| 71 for line in infile: |
| 72 if IsInclude(line): |
| 73 headerblock = [] |
| 74 while IsInclude(line): |
| 75 headerblock.append(line) |
| 76 line = infile.next() |
| 77 for header in sorted(headerblock, key=IncludeCompareKey): |
| 78 outfile.write(header) |
| 79 # Intentionally fall through, to write the line that caused |
| 80 # the above while loop to exit. |
| 81 outfile.write(line) |
| 82 |
| 83 |
| 84 def FixFileWithConfirmFunction(filename, confirm_function): |
| 85 """Creates a fixed version of the file, invokes |confirm_function| |
| 86 to decide whether to use the new file, and cleans up. |
| 87 |
| 88 |confirm_function| takes two parameters, the original filename and |
| 89 the fixed-up filename, and returns True to use the fixed-up file, |
| 90 false to not use it. |
| 91 """ |
| 92 fixfilename = filename + '.new' |
| 93 infile = open(filename, 'r') |
| 94 outfile = open(fixfilename, 'w') |
| 95 SortHeader(infile, outfile) |
| 96 infile.close() |
| 97 outfile.close() # Important so the below diff gets the updated contents. |
| 98 |
| 99 try: |
| 100 if confirm_function(filename, fixfilename): |
| 101 os.rename(fixfilename, filename) |
| 102 finally: |
| 103 try: |
| 104 os.remove(fixfilename) |
| 105 except OSError: |
| 106 # If the file isn't there, we don't care. |
| 107 pass |
| 108 |
| 109 |
| 110 def DiffAndConfirm(filename, should_confirm): |
| 111 """Shows a diff of what the tool would change the file named |
| 112 filename to. Shows a confirmation prompt if should_confirm is true. |
| 113 Saves the resulting file if should_confirm is false or the user |
| 114 answers Y to the confirmation prompt. |
| 115 """ |
| 116 def ConfirmFunction(filename, fixfilename): |
| 117 diff = os.system('diff -u %s %s' % (filename, fixfilename)) |
| 118 if diff >> 8 == 0: # Check exit code. |
| 119 print '%s: no change' % filename |
| 120 return False |
| 121 |
| 122 return (not should_confirm or YesNo('Use new file (y/N)?')) |
| 123 |
| 124 FixFileWithConfirmFunction(filename, ConfirmFunction) |
| 125 |
| 126 |
| 127 def main(): |
| 128 parser = optparse.OptionParser(usage='%prog filename1 filename2 ...') |
| 129 parser.add_option('-f', '--force', action='store_false', default=True, |
| 130 dest='should_confirm', |
| 131 help='Turn off confirmation prompt.') |
| 132 opts, filenames = parser.parse_args() |
| 133 |
| 134 if len(filenames) < 1: |
| 135 parser.print_help() |
| 136 return 1 |
| 137 |
| 138 for filename in filenames: |
| 139 DiffAndConfirm(filename, opts.should_confirm) |
| 140 |
| 141 |
| 142 if __name__ == '__main__': |
| 143 sys.exit(main()) |
| OLD | NEW |