Remove a FIXME comment that is already fixed.
[WebKit-https.git] / Tools / Scripts / webkitpy / style / filereader.py
1 # Copyright (C) 2009 Google Inc. All rights reserved.
2 # Copyright (C) 2010 Chris Jerdonek (chris.jerdonek@gmail.com)
3 # Copyright (C) 2010 ProFUSION embedded systems
4 #
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions are
7 # met:
8 #
9 #     * Redistributions of source code must retain the above copyright
10 # notice, this list of conditions and the following disclaimer.
11 #     * Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following disclaimer
13 # in the documentation and/or other materials provided with the
14 # distribution.
15 #     * Neither the name of Google Inc. nor the names of its
16 # contributors may be used to endorse or promote products derived from
17 # this software without specific prior written permission.
18 #
19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31 """Supports reading and processing text files."""
32
33 import logging
34 import sys
35
36
37 _log = logging.getLogger(__name__)
38
39
40 class TextFileReader(object):
41
42     """Supports reading and processing text files.
43
44        Attributes:
45          file_count: The total number of files passed to this instance
46                      for processing, including non-text files and files
47                      that should be skipped.
48          delete_only_file_count: The total number of files that are not
49                                  processed this instance actually because
50                                  the files don't have any modified lines
51                                  but should be treated as processed.
52
53     """
54
55     def __init__(self, filesystem, processor):
56         """Create an instance.
57
58         Arguments:
59           processor: A ProcessorBase instance.
60
61         """
62
63         self.filesystem = filesystem
64         self._processor = processor
65         self.file_count = 0
66         self.delete_only_file_count = 0
67
68     def _read_lines(self, file_path):
69         """Read the file at a path, and return its lines.
70
71         Raises:
72           IOError: If the file does not exist or cannot be read.
73
74         """
75         # Support the UNIX convention of using "-" for stdin.
76         if file_path == '-':
77             file = self.filesystem.open_stdin()
78         else:
79             # We do not open the file with universal newline support
80             # (codecs does not support it anyway), so the resulting
81             # lines contain trailing "\r" characters if we are reading
82             # a file with CRLF endings.
83             file = self.filesystem.open_text_file_for_reading(file_path, 'replace')
84
85         try:
86             contents = file.read()
87         finally:
88             file.close()
89
90         lines = contents.split('\n')
91         return lines
92
93     def process_file(self, file_path, **kwargs):
94         """Process the given file by calling the processor's process() method.
95
96         Args:
97           file_path: The path of the file to process.
98           **kwargs: Any additional keyword parameters that should be passed
99                     to the processor's process() method.  The process()
100                     method should support these keyword arguments.
101
102         Raises:
103           SystemExit: If no file at file_path exists.
104
105         """
106         self.file_count += 1
107
108         if not self.filesystem.exists(file_path) and file_path != "-":
109             _log.error("File does not exist: '%s'" % file_path)
110             raise IOError("File does not exist")
111
112         if not self._processor.should_process(file_path):
113             _log.debug("Skipping file: '%s'" % file_path)
114             return
115         _log.debug("Processing file: '%s'" % file_path)
116
117         try:
118             lines = self._read_lines(file_path)
119         except IOError, err:
120             message = ("Could not read file. Skipping: '%s'\n  %s" % (file_path, err))
121             _log.warn(message)
122             return
123
124         self._processor.process(lines, file_path, **kwargs)
125
126     def _process_directory(self, directory):
127         """Process all files in the given directory, recursively."""
128         for file_path in self.filesystem.files_under(directory):
129             self.process_file(file_path)
130
131     def process_paths(self, paths):
132         for path in paths:
133             if self.filesystem.isdir(path):
134                 self._process_directory(directory=path)
135             else:
136                 self.process_file(path)
137
138     def count_delete_only_file(self):
139         """Count up files that contains only deleted lines.
140
141         Files which has no modified or newly-added lines don't need
142         to check style, but should be treated as checked. For that
143         purpose, we just count up the number of such files.
144         """
145         self.delete_only_file_count += 1