initial import
[vuplus_webkit] / Tools / Scripts / webkitpy / common / system / filesystem.py
1 # Copyright (C) 2010 Google Inc. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are
5 # met:
6 #
7 #     * Redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer.
9 #     * Redistributions in binary form must reproduce the above
10 # copyright notice, this list of conditions and the following disclaimer
11 # in the documentation and/or other materials provided with the
12 # distribution.
13 #     * Neither the name of Google Inc. nor the names of its
14 # contributors may be used to endorse or promote products derived from
15 # this software without specific prior written permission.
16 #
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29 """Wrapper object for the file system / source tree."""
30
31 from __future__ import with_statement
32
33 import codecs
34 import errno
35 import exceptions
36 import glob
37 import hashlib
38 import os
39 import shutil
40 import sys
41 import tempfile
42 import time
43
44 from webkitpy.common.system import ospath
45
46 class FileSystem(object):
47     """FileSystem interface for webkitpy.
48
49     Unless otherwise noted, all paths are allowed to be either absolute
50     or relative."""
51     def __init__(self):
52         self._sep = os.sep
53
54     def _get_sep(self):
55         return self._sep
56
57     sep = property(_get_sep, doc="pathname separator")
58
59     def abspath(self, path):
60         return os.path.abspath(path)
61
62     def path_to_module(self, module_name):
63         """A wrapper for all calls to __file__ to allow easy unit testing."""
64         # FIXME: This is the only use of sys in this file. It's possible this function should move elsewhere.
65         return sys.modules[module_name].__file__  # __file__ is always an absolute path.
66
67     def expanduser(self, path):
68         return os.path.expanduser(path)
69
70     def basename(self, path):
71         return os.path.basename(path)
72
73     def chdir(self, path):
74         return os.chdir(path)
75
76     def copyfile(self, source, destination):
77         shutil.copyfile(source, destination)
78
79     def dirname(self, path):
80         return os.path.dirname(path)
81
82     def exists(self, path):
83         return os.path.exists(path)
84
85     def files_under(self, path, dirs_to_skip=[], file_filter=None):
86         """Return the list of all files under the given path in topdown order.
87
88         Args:
89             dirs_to_skip: a list of directories to skip over during the
90                 traversal (e.g., .svn, resources, etc.)
91             file_filter: if not None, the filter will be invoked
92                 with the filesystem object and the dirname and basename of
93                 each file found. The file is included in the result if the
94                 callback returns True.
95         """
96         def filter_all(fs, dirpath, basename):
97             return True
98
99         file_filter = file_filter or filter_all
100         files = []
101         if self.isfile(path):
102             if file_filter(self, self.dirname(path), self.basename(path)):
103                 files.append(path)
104             return files
105
106         if self.basename(path) in dirs_to_skip:
107             return []
108
109         for (dirpath, dirnames, filenames) in os.walk(path):
110             for d in dirs_to_skip:
111                 if d in dirnames:
112                     dirnames.remove(d)
113
114             for filename in filenames:
115                 if file_filter(self, dirpath, filename):
116                     files.append(self.join(dirpath, filename))
117         return files
118
119     def getcwd(self):
120         return os.getcwd()
121
122     def glob(self, path):
123         return glob.glob(path)
124
125     def isabs(self, path):
126         return os.path.isabs(path)
127
128     def isfile(self, path):
129         return os.path.isfile(path)
130
131     def isdir(self, path):
132         return os.path.isdir(path)
133
134     def join(self, *comps):
135         return os.path.join(*comps)
136
137     def listdir(self, path):
138         return os.listdir(path)
139
140     def mkdtemp(self, **kwargs):
141         """Create and return a uniquely named directory.
142
143         This is like tempfile.mkdtemp, but if used in a with statement
144         the directory will self-delete at the end of the block (if the
145         directory is empty; non-empty directories raise errors). The
146         directory can be safely deleted inside the block as well, if so
147         desired.
148
149         Note that the object returned is not a string and does not support all of the string
150         methods. If you need a string, coerce the object to a string and go from there.
151         """
152         class TemporaryDirectory(object):
153             def __init__(self, **kwargs):
154                 self._kwargs = kwargs
155                 self._directory_path = tempfile.mkdtemp(**self._kwargs)
156
157             def __str__(self):
158                 return self._directory_path
159
160             def __enter__(self):
161                 return self._directory_path
162
163             def __exit__(self, type, value, traceback):
164                 # Only self-delete if necessary.
165
166                 # FIXME: Should we delete non-empty directories?
167                 if os.path.exists(self._directory_path):
168                     os.rmdir(self._directory_path)
169
170         return TemporaryDirectory(**kwargs)
171
172     def maybe_make_directory(self, *path):
173         """Create the specified directory if it doesn't already exist."""
174         try:
175             os.makedirs(self.join(*path))
176         except OSError, e:
177             if e.errno != errno.EEXIST:
178                 raise
179
180     def move(self, source, destination):
181         shutil.move(source, destination)
182
183     def mtime(self, path):
184         return os.stat(path).st_mtime
185
186     def normpath(self, path):
187         return os.path.normpath(path)
188
189     def open_binary_tempfile(self, suffix):
190         """Create, open, and return a binary temp file. Returns a tuple of the file and the name."""
191         temp_fd, temp_name = tempfile.mkstemp(suffix)
192         f = os.fdopen(temp_fd, 'wb')
193         return f, temp_name
194
195     def open_binary_file_for_reading(self, path):
196         return codecs.open(path, 'rb')
197
198     def read_binary_file(self, path):
199         """Return the contents of the file at the given path as a byte string."""
200         with file(path, 'rb') as f:
201             return f.read()
202
203     def write_binary_file(self, path, contents):
204         with file(path, 'wb') as f:
205             f.write(contents)
206
207     def open_text_file_for_reading(self, path):
208         return codecs.open(path, 'r', 'utf8')
209
210     def open_text_file_for_writing(self, path):
211         return codecs.open(path, 'w', 'utf8')
212
213     def read_text_file(self, path):
214         """Return the contents of the file at the given path as a Unicode string.
215
216         The file is read assuming it is a UTF-8 encoded file with no BOM."""
217         with codecs.open(path, 'r', 'utf8') as f:
218             return f.read()
219
220     def write_text_file(self, path, contents):
221         """Write the contents to the file at the given location.
222
223         The file is written encoded as UTF-8 with no BOM."""
224         with codecs.open(path, 'w', 'utf8') as f:
225             f.write(contents)
226
227     def sha1(self, path):
228         contents = self.read_binary_file(path)
229         return hashlib.sha1(contents).hexdigest()
230
231     def relpath(self, path, start='.'):
232         return ospath.relpath(path, start)
233
234     class _WindowsError(exceptions.OSError):
235         """Fake exception for Linux and Mac."""
236         pass
237
238     def remove(self, path, osremove=os.remove):
239         """On Windows, if a process was recently killed and it held on to a
240         file, the OS will hold on to the file for a short while.  This makes
241         attempts to delete the file fail.  To work around that, this method
242         will retry for a few seconds until Windows is done with the file."""
243         try:
244             exceptions.WindowsError
245         except AttributeError:
246             exceptions.WindowsError = FileSystem._WindowsError
247
248         retry_timeout_sec = 3.0
249         sleep_interval = 0.1
250         while True:
251             try:
252                 osremove(path)
253                 return True
254             except exceptions.WindowsError, e:
255                 time.sleep(sleep_interval)
256                 retry_timeout_sec -= sleep_interval
257                 if retry_timeout_sec < 0:
258                     raise e
259
260     def rmtree(self, path):
261         """Delete the directory rooted at path, whether empty or not."""
262         shutil.rmtree(path, ignore_errors=True)
263
264     def split(self, path):
265         """Return (dirname, basename + '.' + ext)"""
266         return os.path.split(path)
267
268     def splitext(self, path):
269         """Return (dirname + os.sep + basename, '.' + ext)"""
270         return os.path.splitext(path)