Index: Lib/shutil.py =================================================================== --- Lib/shutil.py (revision 62418) +++ Lib/shutil.py (working copy) @@ -8,6 +8,7 @@ import sys import stat from os.path import abspath +import glob __all__ = ["copyfileobj","copyfile","copymode","copystat","copy","copy2", "copytree","move","rmtree","Error"] @@ -94,7 +95,7 @@ copystat(src, dst) -def copytree(src, dst, symlinks=False): +def copytree(src, dst, symlinks=False, exclude=None): """Recursively copy a directory tree using copy2(). The destination directory must not already exist. @@ -105,21 +106,38 @@ it is false, the contents of the files pointed to by symbolic links are copied. + The optional exclude argument can be a sequence or a callable. If a + sequence is given, each element mustbe a glob-style string the + function will use to exclude files or folders that matches the + expression. In case of a callable, the path that is being copied will + be passed to the callable. If it returns true, the element will not be + copied. + XXX Consider this example code rather than the ultimate tool. """ + exclude_files = [] + if exclude is not None and not callable(exclude): + for pattern in exclude: + pattern = os.path.join(src, pattern) + exclude_files.extend(glob.glob(pattern)) + names = os.listdir(src) os.makedirs(dst) errors = [] for name in names: srcname = os.path.join(src, name) + if callable(exclude) and exclude(srcname): + continue + elif srcname in exclude_files: + continue dstname = os.path.join(dst, name) try: if symlinks and os.path.islink(srcname): linkto = os.readlink(srcname) os.symlink(linkto, dstname) elif os.path.isdir(srcname): - copytree(srcname, dstname, symlinks) + copytree(srcname, dstname, symlinks, exclude) else: copy2(srcname, dstname) # XXX What about devices, sockets etc.? Index: Lib/test/test_shutil.py =================================================================== --- Lib/test/test_shutil.py (revision 62418) +++ Lib/test/test_shutil.py (working copy) @@ -108,7 +108,76 @@ if os.path.exists(path): shutil.rmtree(path) + def test_copytree_with_exclude(self): + + def write_data(path, data): + f = open(path, "w") + f.write(data) + f.close() + def read_data(path): + f = open(path) + data = f.read() + f.close() + return data + + # creating data + join = os.path.join + exists = os.path.exists + src_dir = tempfile.mkdtemp() + dst_dir = join(tempfile.mkdtemp(), 'destination') + write_data(join(src_dir, 'test.txt'), '123') + write_data(join(src_dir, 'test.tmp'), '123') + os.mkdir(join(src_dir, 'test_dir')) + write_data(join(src_dir, 'test_dir', 'test.txt'), '456') + os.mkdir(join(src_dir, 'test_dir2')) + write_data(join(src_dir, 'test_dir2', 'test.txt'), '456') + os.mkdir(join(src_dir, 'test_dir2', 'subdir')) + os.mkdir(join(src_dir, 'test_dir2', 'subdir2')) + write_data(join(src_dir, 'test_dir2', 'subdir', 'test.txt'), '456') + write_data(join(src_dir, 'test_dir2', 'subdir2', 'test.py'), '456') + + + # testing glob-like patterns + try: + shutil.copytree(src_dir, dst_dir, exclude=('*.tmp', 'test_dir2',)) + # cheking the result: some elements should not be copied + self.assert_(exists(join(dst_dir, 'test.txt'))) + self.assert_(not exists(join(dst_dir, 'test.tmp'))) + self.assert_(not exists(join(dst_dir, 'test_dir2'))) + finally: + if os.path.exists(dst_dir): + shutil.rmtree(dst_dir) + try: + shutil.copytree(src_dir, dst_dir, exclude=('*.tmp', + 'subdir*',)) + # checking the result: some elements should not be copied + self.assert_(not exists(join(dst_dir, 'test.tmp'))) + self.assert_(not exists(join(dst_dir, 'test_dir2', 'subdir2'))) + self.assert_(not exists(join(dst_dir, 'test_dir2', 'subdir'))) + finally: + if os.path.exists(dst_dir): + shutil.rmtree(dst_dir) + + # testing callable-style + try: + def _filter(path): + if os.path.isdir(path) and path.split()[-1] == 'subdir': + return True + return os.path.splitext(path)[-1] in ('.py') + + shutil.copytree(src_dir, dst_dir, exclude=_filter) + + # checking the result: some elements should not be copied + self.assert_(not exists(join(dst_dir, 'test_dir2', 'subdir2', + 'test.py'))) + self.assert_(not exists(join(dst_dir, 'test_dir2', 'subdir'))) + + finally: + if os.path.exists(src_dir): + shutil.rmtree(src_dir) + + if hasattr(os, "symlink"): def test_dont_copy_file_onto_link_to_itself(self): # bug 851123.