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
9import commands
10import os
11import sys
12
13
14_FILE_EXTENSIONS_TO_SANITIZE = ['cpp', 'h', 'c', 'gyp', 'gypi']
15
16_SUBDIRS_TO_IGNORE = ['.git', '.svn', 'third_party']
17
18
19def 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
85def 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
93def 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
100def 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
110def 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
117def 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
126def 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
139if '__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