2011-01-06 James Kozianski <koz@chromium.org>
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 7 Jan 2011 07:13:57 +0000 (07:13 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 7 Jan 2011 07:13:57 +0000 (07:13 +0000)
        Reviewed by Mihai Parparita.

        Add classes to provide a consistent interface to a set of files.
        https://bugs.webkit.org/show_bug.cgi?id=50901

        These classes allow us to write code that is agnostic to whether a
        particular set of files resides in a local directory or in a zip file
        on a remote machine.

        * Scripts/webkitpy/common/system/directoryfileset.py: Added.
        * Scripts/webkitpy/common/system/directoryfileset_unittest.py: Added.
        * Scripts/webkitpy/common/system/fileset.py: Added.
        * Scripts/webkitpy/common/system/filesystem.py:
        * Scripts/webkitpy/common/system/filesystem_mock.py:
        * Scripts/webkitpy/common/system/zipfileset.py: Added.
        * Scripts/webkitpy/common/system/zipfileset_unittest.py: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@75233 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Tools/ChangeLog
Tools/Scripts/webkitpy/common/system/directoryfileset.py [new file with mode: 0644]
Tools/Scripts/webkitpy/common/system/directoryfileset_unittest.py [new file with mode: 0644]
Tools/Scripts/webkitpy/common/system/fileset.py [new file with mode: 0644]
Tools/Scripts/webkitpy/common/system/filesystem.py
Tools/Scripts/webkitpy/common/system/filesystem_mock.py
Tools/Scripts/webkitpy/common/system/zipfileset.py [new file with mode: 0644]
Tools/Scripts/webkitpy/common/system/zipfileset_unittest.py [new file with mode: 0644]

index fa4c28e81bb706c9257db0cd871386273e4032de..1b9421677670e669795fba2ca8463809e5afe42a 100644 (file)
@@ -1,3 +1,22 @@
+2011-01-06  James Kozianski  <koz@chromium.org>
+
+        Reviewed by Mihai Parparita.
+
+        Add classes to provide a consistent interface to a set of files.
+        https://bugs.webkit.org/show_bug.cgi?id=50901
+
+        These classes allow us to write code that is agnostic to whether a
+        particular set of files resides in a local directory or in a zip file
+        on a remote machine.
+
+        * Scripts/webkitpy/common/system/directoryfileset.py: Added.
+        * Scripts/webkitpy/common/system/directoryfileset_unittest.py: Added.
+        * Scripts/webkitpy/common/system/fileset.py: Added.
+        * Scripts/webkitpy/common/system/filesystem.py:
+        * Scripts/webkitpy/common/system/filesystem_mock.py:
+        * Scripts/webkitpy/common/system/zipfileset.py: Added.
+        * Scripts/webkitpy/common/system/zipfileset_unittest.py: Added.
+
 2011-01-06  Eric Seidel  <eric@webkit.org>
 
         Reviewed by Adam Barth.
diff --git a/Tools/Scripts/webkitpy/common/system/directoryfileset.py b/Tools/Scripts/webkitpy/common/system/directoryfileset.py
new file mode 100644 (file)
index 0000000..11acaf4
--- /dev/null
@@ -0,0 +1,77 @@
+# Copyright (C) 2010 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1.  Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+# 2.  Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from __future__ import with_statement
+
+import os
+import shutil
+
+from webkitpy.common.system.fileset import FileSetFileHandle
+from webkitpy.common.system.filesystem import FileSystem
+import webkitpy.common.system.ospath as ospath
+
+
+class DirectoryFileSet(object):
+    """The set of files under a local directory."""
+    def __init__(self, path, filesystem=None):
+        self._path = path
+        self._filesystem = filesystem or FileSystem()
+        if not self._path.endswith(os.path.sep):
+            self._path += os.path.sep
+
+    def _full_path(self, filename):
+        assert self._is_under(self._path, filename)
+        return self._filesystem.join(self._path, filename)
+
+    def _drop_directory_prefix(self, path):
+        return path[len(self._path):]
+
+    def _files_in_directory(self):
+        """Returns a list of all the files in the directory, including the path
+           to the directory"""
+        return self._filesystem.files_under(self._path)
+
+    def _is_under(self, dir, filename):
+        return bool(ospath.relpath(self._filesystem.join(dir, filename), dir))
+
+    def open(self, filename):
+        return FileSetFileHandle(self, filename, self._filesystem)
+
+    def namelist(self):
+        return map(self._drop_directory_prefix, self._files_in_directory())
+
+    def read(self, filename):
+        return self._filesystem.read_text_file(self._full_path(filename))
+
+    def extract(self, filename, path):
+        """Extracts a file from this file set to the specified directory."""
+        src = self._full_path(filename)
+        dest = self._filesystem.join(path, filename)
+        # As filename may have slashes in it, we must ensure that the same
+        # directory hierarchy exists at the output path.
+        self._filesystem.maybe_make_directory(os.path.split(dest)[0])
+        self._filesystem.copyfile(src, dest)
+
+    def delete(self, filename):
+        filename = self._full_path(filename)
+        self._filesystem.remove(filename)
diff --git a/Tools/Scripts/webkitpy/common/system/directoryfileset_unittest.py b/Tools/Scripts/webkitpy/common/system/directoryfileset_unittest.py
new file mode 100644 (file)
index 0000000..a3850ee
--- /dev/null
@@ -0,0 +1,70 @@
+# Copyright (C) 2010 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1.  Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+# 2.  Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from __future__ import with_statement
+
+import unittest
+
+from webkitpy.common.system.directoryfileset import DirectoryFileSet
+from webkitpy.common.system.filesystem_mock import MockFileSystem
+
+
+class DirectoryFileSetTest(unittest.TestCase):
+    def setUp(self):
+        files = {}
+        files['/test/some-file'] = 'contents'
+        files['/test/some-other-file'] = 'other contents'
+        files['/test/b/c'] = 'c'
+        self._filesystem = MockFileSystem(files)
+        self._fileset = DirectoryFileSet('/test', self._filesystem)
+
+    def test_files_in_namelist(self):
+        self.assertTrue('some-file' in self._fileset.namelist())
+        self.assertTrue('some-other-file' in self._fileset.namelist())
+        self.assertTrue('b/c' in self._fileset.namelist())
+
+    def test_read(self):
+        self.assertEquals('contents', self._fileset.read('some-file'))
+
+    def test_open(self):
+        file = self._fileset.open('some-file')
+        self.assertEquals('some-file', file.name())
+        self.assertEquals('contents', file.contents())
+
+    def test_extract(self):
+        self._fileset.extract('some-file', '/test-directory')
+        contents = self._filesystem.read_text_file('/test-directory/some-file')
+        self.assertEquals('contents', contents)
+
+    def test_extract_deep_file(self):
+        self._fileset.extract('b/c', '/test-directory')
+        self.assertTrue(self._filesystem.exists('/test-directory/b/c'))
+
+    def test_delete(self):
+        self.assertTrue(self._filesystem.exists('/test/some-file'))
+        self._fileset.delete('some-file')
+        self.assertFalse(self._filesystem.exists('/test/some-file'))
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/Tools/Scripts/webkitpy/common/system/fileset.py b/Tools/Scripts/webkitpy/common/system/fileset.py
new file mode 100644 (file)
index 0000000..5dc2821
--- /dev/null
@@ -0,0 +1,63 @@
+# Copyright (C) 2010 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1.  Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+# 2.  Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import os
+
+from webkitpy.common.system.filesystem import FileSystem
+
+
+class FileSetFileHandle(object):
+    """Points to a file that resides in a file set"""
+    def __init__(self, fileset, filename, filesystem=None):
+        self._filename = filename
+        self._fileset = fileset
+        self._contents = None
+        self._filesystem = filesystem or FileSystem()
+
+    def __str__(self):
+        return "%s:%s" % (self._fileset, self._filename)
+
+    def contents(self):
+        if self._contents is None:
+            self._contents = self._fileset.read(self._filename)
+        return self._contents
+
+    def save_to(self, path, filename=None):
+        if filename is None:
+            self._fileset.extract(self._filename, path)
+            return
+        with self._filesystem.mkdtemp() as temp_dir:
+            self._fileset.extract(self._filename, temp_dir)
+
+            src = self._filesystem.join(temp_dir, self._filename)
+            dest = self._filesystem.join(path, filename)
+            self._filesystem.copyfile(src, dest)
+
+    def delete(self):
+        self._fileset.delete(self._filename)
+
+    def name(self):
+        return self._filename
+
+    def splitext(self):
+        return os.path.splitext(self.name())
index f0b5e44fcac7f6f073fe6774aec1e64b12b6c8d4..8830ea8dc6c051da4b324eeffca20617071290d1 100644 (file)
@@ -93,7 +93,7 @@ class FileSystem(object):
     def maybe_make_directory(self, *path):
         """Create the specified directory if it doesn't already exist."""
         try:
-            os.makedirs(os.path.join(*path))
+            os.makedirs(self.join(*path))
         except OSError, e:
             if e.errno != errno.EEXIST:
                 raise
@@ -152,3 +152,9 @@ class FileSystem(object):
         """Copies the contents of the file at the given path to the destination
         path."""
         shutil.copyfile(source, destination)
+
+    def files_under(self, path):
+        """Return the list of all files under the given path."""
+        return [self.join(path_to_file, filename)
+            for (path_to_file, _, filenames) in os.walk(path)
+            for filename in filenames]
index ea0f3f957ff492fd2c76add375c0d2f4a1cde141..c605cb218d7be26bbf3301e88c7d8691aec248fc 100644 (file)
@@ -29,6 +29,7 @@
 import errno
 import os
 import path
+import re
 
 
 class MockFileSystem(object):
@@ -57,7 +58,7 @@ class MockFileSystem(object):
         return any(f.startswith(path) for f in self.files)
 
     def join(self, *comps):
-        return '/'.join(comps)
+        return re.sub(re.escape(os.path.sep), '/', os.path.join(*comps))
 
     def listdir(self, path):
         if not self.isdir(path):
@@ -107,3 +108,11 @@ class MockFileSystem(object):
             raise IOError(errno.EISDIR, destination, os.strerror(errno.ISDIR))
 
         self.files[destination] = self.files[source]
+
+    def files_under(self, path):
+        if not path.endswith('/'):
+            path += '/'
+        return [file for file in self.files if file.startswith(path)]
+
+    def remove(self, path):
+        del self.files[path]
diff --git a/Tools/Scripts/webkitpy/common/system/zipfileset.py b/Tools/Scripts/webkitpy/common/system/zipfileset.py
new file mode 100644 (file)
index 0000000..fa2b762
--- /dev/null
@@ -0,0 +1,65 @@
+# Copyright (C) 2010 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1.  Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+# 2.  Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import urllib
+import zipfile
+
+from webkitpy.common.net.networktransaction import NetworkTransaction
+from webkitpy.common.system.fileset import FileSetFileHandle
+from webkitpy.common.system.filesystem import FileSystem
+
+
+class ZipFileSet(object):
+    """The set of files in a zip file that resides at a URL (local or remote)"""
+    def __init__(self, zip_url, filesystem=None, zip_factory=None):
+        self._zip_url = zip_url
+        self._zip_file = None
+        self._filesystem = filesystem or FileSystem()
+        self._zip_factory = zip_factory or self._retrieve_zip_file
+
+    def _retrieve_zip_file(self, zip_url):
+        temp_file = NetworkTransaction().run(lambda: urllib.urlretrieve(zip_url)[0])
+        return zipfile.ZipFile(temp_file)
+
+    def _load(self):
+        if self._zip_file is None:
+            self._zip_file = self._zip_factory(self._zip_url)
+
+    def open(self, filename):
+        self._load()
+        return FileSetFileHandle(self, filename, self._filesystem)
+
+    def namelist(self):
+        self._load()
+        return self._zip_file.namelist()
+
+    def read(self, filename):
+        self._load()
+        return self._zip_file.read(filename)
+
+    def extract(self, filename, path):
+        self._load()
+        self._zip_file.extract(filename, path)
+
+    def delete(self, filename):
+        raise Exception("Can't delete from a ZipFileSet.")
diff --git a/Tools/Scripts/webkitpy/common/system/zipfileset_unittest.py b/Tools/Scripts/webkitpy/common/system/zipfileset_unittest.py
new file mode 100644 (file)
index 0000000..a9ba5ad
--- /dev/null
@@ -0,0 +1,95 @@
+# Copyright (C) 2010 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1.  Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+# 2.  Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import os
+import shutil
+import tempfile
+import unittest
+import zipfile
+
+from webkitpy.common.system.filesystem_mock import MockFileSystem
+from webkitpy.common.system.zipfileset import ZipFileSet
+
+
+class FakeZip(object):
+    def __init__(self, filesystem):
+        self._filesystem = filesystem
+        self._files = {}
+
+    def add_file(self, filename, contents):
+        self._files[filename] = contents
+
+    def open(self, filename):
+        return FileSetFileHandle(self, filename, self._filesystem)
+
+    def namelist(self):
+        return self._files.keys()
+
+    def read(self, filename):
+        return self._files[filename]
+
+    def extract(self, filename, path):
+        self._filesystem.write_text_file(self._filesystem.join(path, filename), self.read(filename))
+
+    def delete(self, filename):
+        raise Exception("Can't delete from a ZipFileSet.")
+
+
+class ZipFileSetTest(unittest.TestCase):
+    def setUp(self):
+        self._filesystem = MockFileSystem()
+        self._zip = ZipFileSet('blah', self._filesystem, self.make_fake_zip)
+
+    def make_fake_zip(self, zip_url):
+        result = FakeZip(self._filesystem)
+        result.add_file('some-file', 'contents')
+        result.add_file('a/b/some-other-file', 'other contents')
+        return result
+
+    def test_open(self):
+        file = self._zip.open('a/b/some-other-file')
+        self.assertEquals('a/b/some-other-file', file.name())
+        self.assertEquals('other contents', file.contents())
+
+    def test_read(self):
+        self.assertEquals('contents', self._zip.read('some-file'))
+
+    def test_extract(self):
+        self._filesystem.maybe_make_directory('/some-dir')
+        self._zip.extract('some-file', '/some-dir')
+        self.assertTrue(self._filesystem.isfile('/some-dir/some-file'))
+
+    def test_deep_extract(self):
+        self._filesystem.maybe_make_directory('/some-dir')
+        self._zip.extract('a/b/some-other-file', '/some-dir')
+        self.assertTrue(self._filesystem.isfile('/some-dir/a/b/some-other-file'))
+
+    def test_cant_delete(self):
+        self.assertRaises(Exception, self._zip.delete, 'some-file')
+
+    def test_namelist(self):
+        self.assertTrue('some-file' in self._zip.namelist())
+
+
+if __name__ == '__main__':
+    unittest.main()