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