Home | History | Annotate | Download | only in tools
      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 """Module that sanitizes source files with specified modifiers."""
      7 
      8 
      9 import commands
     10 import os
     11 import sys
     12 
     13 
     14 _FILE_EXTENSIONS_TO_SANITIZE = ['cpp', 'h', 'c', 'gyp', 'gypi']
     15 
     16 _SUBDIRS_TO_IGNORE = ['.git', '.svn', 'third_party']
     17 
     18 
     19 def SanitizeFilesWithModifiers(directory, file_modifiers, line_modifiers):
     20   """Sanitizes source files with the specified file and line modifiers.
     21 
     22   Args:
     23     directory: string - The directory which will be recursively traversed to
     24         find source files to apply modifiers to.
     25     file_modifiers: list - file-modification methods which should be applied to
     26         the complete file content (Eg: EOFOneAndOnlyOneNewlineAdder).
     27     line_modifiers: list - line-modification methods which should be applied to
     28         lines in a file (Eg: TabReplacer).
     29   """
     30   for item in os.listdir(directory):
     31 
     32     full_item_path = os.path.join(directory, item)
     33 
     34     if os.path.isfile(full_item_path):  # Item is a file.
     35 
     36       # Only sanitize files with extensions we care about.
     37       if (len(full_item_path.split('.')) > 1 and
     38           full_item_path.split('.')[-1] in _FILE_EXTENSIONS_TO_SANITIZE):
     39         f = file(full_item_path)
     40         try:
     41           lines = f.readlines()
     42         finally:
     43           f.close()
     44 
     45         new_lines = []  # Collect changed lines here.
     46         line_number = 0  # Keeps track of line numbers in the source file.
     47         write_to_file = False  # File is written to only if this flag is set.
     48 
     49         # Run the line modifiers for each line in this file.
     50         for line in lines:
     51           original_line = line
     52           line_number += 1
     53 
     54           for modifier in line_modifiers:
     55             line = modifier(line, full_item_path, line_number)
     56             if original_line != line:
     57               write_to_file = True
     58           new_lines.append(line)
     59 
     60         # Run the file modifiers.
     61         old_content = ''.join(lines)
     62         new_content = ''.join(new_lines)
     63         for modifier in file_modifiers:
     64           new_content = modifier(new_content, full_item_path)
     65         if new_content != old_content:
     66           write_to_file = True
     67 
     68         # Write modifications to the file.
     69         if write_to_file:
     70           f = file(full_item_path, 'w')
     71           try:
     72             f.write(new_content)
     73           finally:
     74             f.close()
     75           print 'Made changes to %s' % full_item_path
     76 
     77     elif item not in _SUBDIRS_TO_IGNORE:
     78       # Item is a directory recursively call the method.
     79       SanitizeFilesWithModifiers(full_item_path, file_modifiers, line_modifiers)
     80 
     81 
     82 ############## Line Modification methods ##############
     83 
     84 
     85 def TrailingWhitespaceRemover(line, file_path, line_number):
     86   """Strips out trailing whitespaces from the specified line."""
     87   stripped_line = line.rstrip() + '\n'
     88   if line != stripped_line:
     89     print 'Removing trailing whitespace in %s:%s' % (file_path, line_number)
     90   return stripped_line
     91 
     92 
     93 def CrlfReplacer(line, file_path, line_number):
     94   """Replaces CRLF with LF."""
     95   if '\r\n' in line:
     96     print 'Replacing CRLF with LF in %s:%s' % (file_path, line_number)
     97   return line.replace('\r\n', '\n')
     98 
     99 
    100 def TabReplacer(line, file_path, line_number):
    101   """Replaces Tabs with 4 whitespaces."""
    102   if '\t' in line:
    103     print 'Replacing Tab with whitespace in %s:%s' % (file_path, line_number)
    104   return line.replace('\t', '    ')
    105 
    106 
    107 ############## File Modification methods ##############
    108 
    109 
    110 def CopywriteChecker(file_content, unused_file_path):
    111   """Ensures that the copywrite information is correct."""
    112   # TODO(rmistry): Figure out the legal implications of changing old copyright
    113   # headers.
    114   return file_content
    115 
    116 
    117 def EOFOneAndOnlyOneNewlineAdder(file_content, file_path):
    118   """Adds one and only one LF at the end of the file."""
    119   if file_content and (file_content[-1] != '\n' or file_content[-2:-1] == '\n'):
    120     file_content = file_content.rstrip()
    121     file_content += '\n'
    122     print 'Added exactly one newline to %s' % file_path
    123   return file_content
    124 
    125 
    126 def SvnEOLChecker(file_content, file_path):
    127   """Sets svn:eol-style property to LF."""
    128   output = commands.getoutput(
    129       'svn propget svn:eol-style %s' % file_path)
    130   if output != 'LF':
    131     print 'Setting svn:eol-style property to LF in %s' % file_path
    132     os.system('svn ps svn:eol-style LF %s' % file_path)
    133   return file_content
    134 
    135 
    136 #######################################################
    137 
    138 
    139 if '__main__' == __name__:
    140   sys.exit(SanitizeFilesWithModifiers(
    141       os.getcwd(),
    142       file_modifiers=[
    143           CopywriteChecker,
    144           EOFOneAndOnlyOneNewlineAdder,
    145           SvnEOLChecker,
    146       ],
    147       line_modifiers=[
    148           CrlfReplacer,
    149           TabReplacer,
    150           TrailingWhitespaceRemover,
    151       ],
    152   ))
    153