16b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner# Copyright (C) 2009 Google Inc. All rights reserved.
26b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner#
36b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner# Redistribution and use in source and binary forms, with or without
46b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner# modification, are permitted provided that the following conditions are
56b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner# met:
66b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner#
76b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner#    * Redistributions of source code must retain the above copyright
86b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner# notice, this list of conditions and the following disclaimer.
96b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner#    * Redistributions in binary form must reproduce the above
106b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner# copyright notice, this list of conditions and the following disclaimer
116b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner# in the documentation and/or other materials provided with the
126b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner# distribution.
136b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner#    * Neither the name of Google Inc. nor the names of its
146b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner# contributors may be used to endorse or promote products derived from
156b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner# this software without specific prior written permission.
166b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner#
176b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
186b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
196b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
206b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
216b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
226b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
236b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
246b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
256b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
266b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
276b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
286b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner
296b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brennerimport errno
306b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brennerimport os
31cad810f21b803229eb11403f9209855525a25d57Steve Blockimport re
326b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner
332fc2651226baac27029e38c9d6ef883fa32084dbSteve Blockfrom webkitpy.common.system import path
342fc2651226baac27029e38c9d6ef883fa32084dbSteve Blockfrom webkitpy.common.system import ospath
352fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
366b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner
376b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brennerclass MockFileSystem(object):
382daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch    def __init__(self, files=None, cwd='/'):
396b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner        """Initializes a "mock" filesystem that can be used to completely
406b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner        stub out a filesystem.
416b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner
426b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner        Args:
436b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner            files: a dict of filenames -> file contents. A file contents
446b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner                value of None is used to indicate that the file should
454576aa36e9a9671459299c7963ac95aa94beaea9Shimeng (Simon) Wang                not exist.
466b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner        """
47f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch        self.files = files or {}
48ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        self.written_files = {}
492fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        self._sep = '/'
50ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        self.current_tmpno = 0
512daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch        self.cwd = cwd
522daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch        self.dirs = {}
53ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
542fc2651226baac27029e38c9d6ef883fa32084dbSteve Block    def _get_sep(self):
552fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        return self._sep
562fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
572fc2651226baac27029e38c9d6ef883fa32084dbSteve Block    sep = property(_get_sep, doc="pathname separator")
582fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
59ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def _raise_not_found(self, path):
60ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        raise IOError(errno.ENOENT, path, os.strerror(errno.ENOENT))
61ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
62ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def _split(self, path):
632fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        return path.rsplit(self.sep, 1)
64ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
65ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def abspath(self, path):
662daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch        if os.path.isabs(path):
672daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch            return self.normpath(path)
682daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch        return self.abspath(self.join(self.cwd, path))
69ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
70ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def basename(self, path):
71ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        return self._split(path)[1]
72ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
732daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch    def chdir(self, path):
742daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch        path = self.normpath(path)
752daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch        if not self.isdir(path):
762daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch            raise OSError(errno.ENOENT, path, os.strerror(errno.ENOENT))
772daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch        self.cwd = path
782daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch
79ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def copyfile(self, source, destination):
80ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        if not self.exists(source):
81ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            self._raise_not_found(source)
82ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        if self.isdir(source):
83ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            raise IOError(errno.EISDIR, source, os.strerror(errno.ISDIR))
84ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        if self.isdir(destination):
85ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            raise IOError(errno.EISDIR, destination, os.strerror(errno.ISDIR))
86ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
87ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        self.files[destination] = self.files[source]
882fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        self.written_files[destination] = self.files[source]
89ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
90ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def dirname(self, path):
91ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        return self._split(path)[0]
926b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner
936b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner    def exists(self, path):
94f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch        return self.isfile(path) or self.isdir(path)
95f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch
96ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def files_under(self, path, dirs_to_skip=[], file_filter=None):
97ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        def filter_all(fs, dirpath, basename):
98ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            return True
99ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
100ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        file_filter = file_filter or filter_all
101ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        files = []
102ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        if self.isfile(path):
103ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            if file_filter(self, self.dirname(path), self.basename(path)):
104ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch                files.append(path)
105ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            return files
106ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
107ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        if self.basename(path) in dirs_to_skip:
108ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            return []
109ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
1102fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        if not path.endswith(self.sep):
1112fc2651226baac27029e38c9d6ef883fa32084dbSteve Block            path += self.sep
112ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
1132fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        dir_substrings = [self.sep + d + self.sep for d in dirs_to_skip]
114ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        for filename in self.files:
115ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            if not filename.startswith(path):
116ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch                continue
117ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
118ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            suffix = filename[len(path) - 1:]
119ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            if any(dir_substring in suffix for dir_substring in dir_substrings):
120ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch                continue
121ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
122ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            dirpath, basename = self._split(filename)
123ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            if file_filter(self, dirpath, basename):
124ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch                files.append(filename)
125ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
126ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        return files
127ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
1282daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch    def getcwd(self, path):
1292daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch        return self.cwd
1302daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch
131ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def glob(self, path):
132ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        # FIXME: This only handles a wildcard '*' at the end of the path.
133ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        # Maybe it should handle more?
134ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        if path[-1] == '*':
135ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            return [f for f in self.files if f.startswith(path[:-1])]
136ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        else:
137ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            return [f for f in self.files if f == path]
138ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
139ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def isabs(self, path):
1402fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        return path.startswith(self.sep)
141ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
142f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch    def isfile(self, path):
143f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch        return path in self.files and self.files[path] is not None
144f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch
145f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch    def isdir(self, path):
1466b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner        if path in self.files:
147f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch            return False
1482daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch        path = self.normpath(path)
1492daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch        if path in self.dirs:
1502daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch            return True
151ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
152ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        # We need to use a copy of the keys here in order to avoid switching
153ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        # to a different thread and potentially modifying the dict in
154ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        # mid-iteration.
155ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        files = self.files.keys()[:]
1562daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch        result = any(f.startswith(path) for f in files)
1572daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch        if result:
1582daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch            self.dirs[path] = True
1592daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch        return result
1606b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner
1616b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner    def join(self, *comps):
1622fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        # FIXME: might want tests for this and/or a better comment about how
1632fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        # it works.
1642fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        return re.sub(re.escape(os.path.sep), self.sep, os.path.join(*comps))
165f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch
166f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch    def listdir(self, path):
167f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch        if not self.isdir(path):
168f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch            raise OSError("%s is not a directory" % path)
169f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch
1702fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        if not path.endswith(self.sep):
1712fc2651226baac27029e38c9d6ef883fa32084dbSteve Block            path += self.sep
172f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch
173f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch        dirs = []
174f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch        files = []
175f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch        for f in self.files:
176f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch            if self.exists(f) and f.startswith(path):
177f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch                remaining = f[len(path):]
1782fc2651226baac27029e38c9d6ef883fa32084dbSteve Block                if self.sep in remaining:
1792fc2651226baac27029e38c9d6ef883fa32084dbSteve Block                    dir = remaining[:remaining.index(self.sep)]
180f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch                    if not dir in dirs:
181f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch                        dirs.append(dir)
182f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch                else:
183f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch                    files.append(remaining)
184f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch        return dirs + files
1856b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner
186ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def mtime(self, path):
187ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        if self.exists(path):
188ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            return 0
189ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        self._raise_not_found(path)
190ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
191ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def _mktemp(self, suffix='', prefix='tmp', dir=None, **kwargs):
192ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        if dir is None:
1932fc2651226baac27029e38c9d6ef883fa32084dbSteve Block            dir = self.sep + '__im_tmp'
194ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        curno = self.current_tmpno
195ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        self.current_tmpno += 1
196ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        return self.join(dir, "%s_%u_%s" % (prefix, curno, suffix))
197ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
198ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def mkdtemp(self, **kwargs):
199ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        class TemporaryDirectory(object):
200ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            def __init__(self, fs, **kwargs):
201ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch                self._kwargs = kwargs
202ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch                self._filesystem = fs
203ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch                self._directory_path = fs._mktemp(**kwargs)
204ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch                fs.maybe_make_directory(self._directory_path)
205ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
206ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            def __str__(self):
207ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch                return self._directory_path
208ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
209ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            def __enter__(self):
210ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch                return self._directory_path
211ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
212ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            def __exit__(self, type, value, traceback):
213ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch                # Only self-delete if necessary.
214ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
215ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch                # FIXME: Should we delete non-empty directories?
216ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch                if self._filesystem.exists(self._directory_path):
217ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch                    self._filesystem.rmtree(self._directory_path)
218ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
219ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        return TemporaryDirectory(fs=self, **kwargs)
220ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
2216b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner    def maybe_make_directory(self, *path):
2222daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch        norm_path = self.normpath(self.join(*path))
2232daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch        if not self.isdir(norm_path):
2242daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch            self.dirs[norm_path] = True
2256b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner
2262fc2651226baac27029e38c9d6ef883fa32084dbSteve Block    def move(self, source, destination):
2272fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        if self.files[source] is None:
2282fc2651226baac27029e38c9d6ef883fa32084dbSteve Block            self._raise_not_found(source)
2292fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        self.files[destination] = self.files[source]
2302fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        self.written_files[destination] = self.files[destination]
2312fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        self.files[source] = None
2322fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        self.written_files[source] = None
233ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
234ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def normpath(self, path):
2352daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch        # Like join(), relies on os.path functionality but normalizes the
2362daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch        # path separator to the mock one.
2372daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch        return re.sub(re.escape(os.path.sep), self.sep, os.path.normpath(path))
238ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
2392fc2651226baac27029e38c9d6ef883fa32084dbSteve Block    def open_binary_tempfile(self, suffix=''):
240ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        path = self._mktemp(suffix)
2412fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        return (WritableFileObject(self, path), path)
242ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
243ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def open_text_file_for_writing(self, path, append=False):
244ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        return WritableFileObject(self, path, append)
245ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
2466b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner    def read_text_file(self, path):
2472fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        return self.read_binary_file(path).decode('utf-8')
2486b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner
2492bde8e466a4451c7319e3a072d118917957d6554Steve Block    def open_binary_file_for_reading(self, path):
2502bde8e466a4451c7319e3a072d118917957d6554Steve Block        if self.files[path] is None:
2512bde8e466a4451c7319e3a072d118917957d6554Steve Block            self._raise_not_found(path)
2522bde8e466a4451c7319e3a072d118917957d6554Steve Block        return ReadableFileObject(self, path, self.files[path])
2532bde8e466a4451c7319e3a072d118917957d6554Steve Block
2546b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner    def read_binary_file(self, path):
255ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        # Intentionally raises KeyError if we don't recognize the path.
256ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        if self.files[path] is None:
257ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            self._raise_not_found(path)
258ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        return self.files[path]
259ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
2602fc2651226baac27029e38c9d6ef883fa32084dbSteve Block    def relpath(self, path, start='.'):
2612fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        return ospath.relpath(path, start, self.abspath, self.sep)
2622fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
263ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def remove(self, path):
264ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        if self.files[path] is None:
265ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            self._raise_not_found(path)
266ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        self.files[path] = None
2672fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        self.written_files[path] = None
268ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
269ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def rmtree(self, path):
2702fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        if not path.endswith(self.sep):
2712fc2651226baac27029e38c9d6ef883fa32084dbSteve Block            path += self.sep
272ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
273ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        for f in self.files:
274ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            if f.startswith(path):
275ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch                self.files[f] = None
276ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
277ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def splitext(self, path):
278ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        idx = path.rfind('.')
279ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        if idx == -1:
280ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            idx = 0
281ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        return (path[0:idx], path[idx:])
2826b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner
2836b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner    def write_text_file(self, path, contents):
2842fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        return self.write_binary_file(path, contents.encode('utf-8'))
2856b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner
2866b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner    def write_binary_file(self, path, contents):
2876b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner        self.files[path] = contents
288ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        self.written_files[path] = contents
289f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch
290f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch
291ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdochclass WritableFileObject(object):
292ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def __init__(self, fs, path, append=False, encoding=None):
293ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        self.fs = fs
294ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        self.path = path
295ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        self.closed = False
296ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        if path not in self.fs.files or not append:
297ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch            self.fs.files[path] = ""
298cad810f21b803229eb11403f9209855525a25d57Steve Block
299ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def __enter__(self):
300ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        return self
301cad810f21b803229eb11403f9209855525a25d57Steve Block
302ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def __exit__(self, type, value, traceback):
303ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        self.close()
304ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch
305ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def close(self):
306ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        self.closed = True
30765f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch
308ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch    def write(self, str):
309ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        self.fs.files[self.path] += str
310ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddbBen Murdoch        self.fs.written_files[self.path] = self.fs.files[self.path]
3112bde8e466a4451c7319e3a072d118917957d6554Steve Block
3122bde8e466a4451c7319e3a072d118917957d6554Steve Block
3132bde8e466a4451c7319e3a072d118917957d6554Steve Blockclass ReadableFileObject(object):
3142bde8e466a4451c7319e3a072d118917957d6554Steve Block    def __init__(self, fs, path, data=""):
3152bde8e466a4451c7319e3a072d118917957d6554Steve Block        self.fs = fs
3162bde8e466a4451c7319e3a072d118917957d6554Steve Block        self.path = path
3172bde8e466a4451c7319e3a072d118917957d6554Steve Block        self.closed = False
3182bde8e466a4451c7319e3a072d118917957d6554Steve Block        self.data = data
3192bde8e466a4451c7319e3a072d118917957d6554Steve Block        self.offset = 0
3202bde8e466a4451c7319e3a072d118917957d6554Steve Block
3212bde8e466a4451c7319e3a072d118917957d6554Steve Block    def __enter__(self):
3222bde8e466a4451c7319e3a072d118917957d6554Steve Block        return self
3232bde8e466a4451c7319e3a072d118917957d6554Steve Block
3242bde8e466a4451c7319e3a072d118917957d6554Steve Block    def __exit__(self, type, value, traceback):
3252bde8e466a4451c7319e3a072d118917957d6554Steve Block        self.close()
3262bde8e466a4451c7319e3a072d118917957d6554Steve Block
3272bde8e466a4451c7319e3a072d118917957d6554Steve Block    def close(self):
3282bde8e466a4451c7319e3a072d118917957d6554Steve Block        self.closed = True
3292bde8e466a4451c7319e3a072d118917957d6554Steve Block
3302bde8e466a4451c7319e3a072d118917957d6554Steve Block    def read(self, bytes=None):
3312bde8e466a4451c7319e3a072d118917957d6554Steve Block        if not bytes:
3322bde8e466a4451c7319e3a072d118917957d6554Steve Block            return self.data[self.offset:]
3332bde8e466a4451c7319e3a072d118917957d6554Steve Block        start = self.offset
3342bde8e466a4451c7319e3a072d118917957d6554Steve Block        self.offset += bytes
3352bde8e466a4451c7319e3a072d118917957d6554Steve Block        return self.data[start:self.offset]
336