1 #!/usr/bin/env python 2 # Copyright (c) 2011 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 """Find and fix files with inconsistent line endings. 7 8 This script requires 'dos2unix.exe' and 'unix2dos.exe' from Cygwin; they 9 must be in the user's PATH. 10 11 Arg: Either one or more files to examine, or (with --file-list) one or more 12 files that themselves contain lists of files. The argument(s) passed to 13 this script, as well as the paths in the file if any, may be relative or 14 absolute Windows-style paths (with either type of slash). The list might 15 be generated with 'find -type f' or extracted from a gcl change listing, 16 for example. 17 """ 18 19 import errno 20 import logging 21 import optparse 22 import subprocess 23 import sys 24 25 26 # Whether to produce excessive debugging output for each file in the list. 27 DEBUGGING = False 28 29 30 class Error(Exception): 31 """Local exception class.""" 32 pass 33 34 35 def CountChars(text, str): 36 """Count the number of instances of the given string in the text.""" 37 split = text.split(str) 38 logging.debug(len(split) - 1) 39 return len(split) - 1 40 41 42 def PrevailingEOLName(crlf, cr, lf): 43 """Describe the most common line ending. 44 45 Args: 46 crlf: How many CRLF (\r\n) sequences are in the file. 47 cr: How many CR (\r) characters are in the file, excluding CRLF sequences. 48 lf: How many LF (\n) characters are in the file, excluding CRLF sequences. 49 50 Returns: 51 A string describing the most common of the three line endings. 52 """ 53 most = max(crlf, cr, lf) 54 if most == cr: 55 return 'cr' 56 if most == crlf: 57 return 'crlf' 58 return 'lf' 59 60 61 def FixEndings(file, crlf, cr, lf): 62 """Change the file's line endings to CRLF or LF, whichever is more common.""" 63 most = max(crlf, cr, lf) 64 if most == crlf: 65 result = subprocess.call('unix2dos.exe %s' % file, shell=True) 66 if result: 67 raise Error('Error running unix2dos.exe %s' % file) 68 else: 69 result = subprocess.call('dos2unix.exe %s' % file, shell=True) 70 if result: 71 raise Error('Error running dos2unix.exe %s' % file) 72 73 74 def ProcessFiles(filelist): 75 """Fix line endings in each file in the filelist list.""" 76 for filename in filelist: 77 filename = filename.strip() 78 logging.debug(filename) 79 try: 80 # Open in binary mode to preserve existing line endings. 81 text = open(filename, 'rb').read() 82 except IOError, e: 83 if e.errno != errno.ENOENT: 84 raise 85 logging.warning('File %s not found.' % filename) 86 continue 87 88 crlf = CountChars(text, '\r\n') 89 cr = CountChars(text, '\r') - crlf 90 lf = CountChars(text, '\n') - crlf 91 92 if options.force_lf: 93 if crlf > 0 or cr > 0: 94 print '%s: forcing to LF' % filename 95 # Fudge the counts to force switching to LF. 96 FixEndings(filename, 0, 0, 1) 97 else: 98 if ((crlf > 0 and cr > 0) or 99 (crlf > 0 and lf > 0) or 100 ( lf > 0 and cr > 0)): 101 print '%s: mostly %s' % (filename, PrevailingEOLName(crlf, cr, lf)) 102 FixEndings(filename, crlf, cr, lf) 103 104 105 def process(options, args): 106 """Process the files.""" 107 if not args or len(args) < 1: 108 raise Error('No files given.') 109 110 if options.file_list: 111 for arg in args: 112 filelist = open(arg, 'r').readlines() 113 ProcessFiles(filelist) 114 else: 115 filelist = args 116 ProcessFiles(filelist) 117 return 0 118 119 120 def main(): 121 if DEBUGGING: 122 debug_level = logging.DEBUG 123 else: 124 debug_level = logging.INFO 125 logging.basicConfig(level=debug_level, 126 format='%(asctime)s %(levelname)-7s: %(message)s', 127 datefmt='%H:%M:%S') 128 129 option_parser = optparse.OptionParser() 130 option_parser.add_option("", "--file-list", action="store_true", 131 default=False, 132 help="Treat the arguments as files containing " 133 "lists of files to examine, rather than as " 134 "the files to be checked.") 135 option_parser.add_option("", "--force-lf", action="store_true", 136 default=False, 137 help="Force any files with CRLF to LF instead.") 138 options, args = option_parser.parse_args() 139 return process(options, args) 140 141 142 if '__main__' == __name__: 143 sys.exit(main()) 144