Home | History | Annotate | Download | only in tools
      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