diff -r 768ee07a6521 Doc/library/shutil.rst --- a/Doc/library/shutil.rst Sat Jul 30 10:58:30 2011 +0800 +++ b/Doc/library/shutil.rst Sat Jul 30 22:16:15 2011 +0600 @@ -157,6 +157,33 @@ will not be caught. +.. function:: cleartree(path, ignore_errors=False, onerror=None, ignore=None, invert=False) + + Clear a directory tree from files; *path* must point to a directory (but not a + symbolic link to a directory). If *ignore_errors* is true, errors resulting + from failed removals will be ignored; if false or omitted, such errors are + handled by calling a handler specified by *onerror* or, if that is omitted, + they raise an exception. + + If *onerror* is provided, it must be a callable that accepts three + parameters: *function*, *path*, and *excinfo*. The first parameter, + *function*, is the function which raised the exception; it will be + :func:`os.path.islink`, :func:`os.listdir`, :func:`os.remove` or + :func:`os.rmdir`. The second parameter, *path*, will be the path name passed + to *function*. The third parameter, *excinfo*, will be the exception + information return by :func:`sys.exc_info`. Exceptions raised by *onerror* + will not be caught. + + The optional *ignore* argument is callable. If given, it + is called with the `path` parameter, and `names` which is the list of + `path` contents, as returned by os.listdir(): + + callable(src, names) -> ignored_names + + If *invert* is True, only files matching *ignore* patterns will be deleted. + Otherwise all files, except files in *ignore* will be deleted. + + .. function:: move(src, dst) Recursively move a file or directory (*src*) to another location (*dst*). diff -r 768ee07a6521 Lib/shutil.py --- a/Lib/shutil.py Sat Jul 30 10:58:30 2011 +0800 +++ b/Lib/shutil.py Sat Jul 30 22:16:15 2011 +0600 @@ -146,7 +146,7 @@ copystat(src, dst) def ignore_patterns(*patterns): - """Function that can be used as copytree() ignore parameter. + """Function that can be used as copytree() or cleartree() ignore parameter. Patterns is a sequence of glob-style patterns that are used to exclude files""" @@ -288,6 +288,72 @@ onerror(os.rmdir, path, sys.exc_info()) +def cleartree(path, ignore_errors=False, onerror=None, ignore=None, invert=False): + """Recursively clear a directory tree from files. + + If ignore_errors is set, errors are ignored; otherwise, if onerror + is set, it is called to handle the error with arguments (func, + path, exc_info) where func is os.listdir, os.remove; + path is the argument to that function that caused it to fail; and + exc_info is a tuple returned by sys.exc_info(). If ignore_errors + is false and onerror is None, an exception is raised. + + The optional ignore argument is callable. If given, it + is called with the `path` parameter, and `names` which is the list of + `path` contents, as returned by os.listdir(): + + callable(src, names) -> ignored_names + + If invert is True, only files matching ignore patterns will be deleted. + Otherwise all files, except files in ignore will be deleted. + + """ + if ignore_errors: + def onerror(*args): + pass + elif onerror is None: + def onerror(*args): + raise + try: + if os.path.islink(path): + # symlinks to directories are forbidden, see bug #1669 + raise OSError("Cannot call rmtree on a symbolic link") + except OSError: + onerror(os.path.islink, path, sys.exc_info()) + # can't continue even if onerror hook returns + return + names = [] + try: + names = os.listdir(path) + except os.error as err: + onerror(os.listdir, path, sys.exc_info()) + + if ignore is not None: + ignored_names = ignore(path, names) + else: + ignored_names = set() + + for name in names: + fullname = os.path.join(path, name) + try: + mode = os.lstat(fullname).st_mode + except os.error: + mode = 0 + if stat.S_ISDIR(mode): + cleartree(fullname, ignore_errors, onerror, ignore, invert) + else: + if invert: + if name not in ignored_names: + continue + else: + if name in ignored_names: + continue + try: + os.remove(fullname) + except os.error as err: + onerror(os.remove, fullname, sys.exc_info()) + + def _basename(path): # A basename() variant which first strips the trailing slash, if present. # Thus we always get the last component of the path, even for directories. diff -r 768ee07a6521 Lib/test/test_shutil.py --- a/Lib/test/test_shutil.py Sat Jul 30 10:58:30 2011 +0800 +++ b/Lib/test/test_shutil.py Sat Jul 30 22:16:15 2011 +0600 @@ -972,6 +972,70 @@ if os.path.exists(dst_dir): os.rmdir(dst_dir) + def test_cleartree_with_ignore(self): + # Test shutil.cleartree with ignore option + try: + d1 = os.path.join(TESTFN, '1') + d2 = os.path.join(TESTFN, '2') + + # Create some dirs + os.makedirs(d1) + os.makedirs(d2) + + # Create some files + # must use msktemp, so files can be visible + _, f1 = tempfile.mkstemp(dir=d1) + _, f2 = tempfile.mkstemp(dir=d2) + _, f3 = tempfile.mkstemp(dir=TESTFN) + + # Create ignore pattern + ignore = shutil.ignore_patterns(os.path.basename(f1), + os.path.basename(f3)) + + shutil.cleartree(TESTFN, ignore=ignore) + + self.assertTrue(os.path.exists(d1)) + self.assertTrue(os.path.exists(TESTFN)) + self.assertTrue(os.path.exists(d2)) + self.assertTrue(os.path.exists(f1)) + self.assertTrue(os.path.exists(f3)) + self.assertFalse(os.path.exists(f2)) + finally: + # Cleanup + shutil.rmtree(TESTFN) + + def test_cleartree_with_invert(self): + # Test shutil.cleartree with ignore option + try: + d1 = os.path.join(TESTFN, '1') + d2 = os.path.join(TESTFN, '2') + + # Create some dirs + os.makedirs(d1) + os.makedirs(d2) + + # Create some files + # must use msktemp, so files can be visible + _, f1 = tempfile.mkstemp(dir=d1) + _, f2 = tempfile.mkstemp(dir=d2) + _, f3 = tempfile.mkstemp(dir=TESTFN) + + # Create remove pattern + remove = shutil.ignore_patterns(os.path.basename(f1), + os.path.basename(f3)) + + shutil.cleartree(TESTFN, ignore=remove, invert=True) + + self.assertTrue(os.path.exists(d1)) + self.assertTrue(os.path.exists(TESTFN)) + self.assertTrue(os.path.exists(d2)) + self.assertFalse(os.path.exists(f1)) + self.assertFalse(os.path.exists(f3)) + self.assertTrue(os.path.exists(f2)) + finally: + # Cleanup + shutil.rmtree(TESTFN) + def test_main(): diff -r 768ee07a6521 Misc/ACKS --- a/Misc/ACKS Sat Jul 30 10:58:30 2011 +0800 +++ b/Misc/ACKS Sat Jul 30 22:16:15 2011 +0600 @@ -965,6 +965,7 @@ Andrew Vant Atul Varma Dmitry Vasiliev +Leonid Vasiliev Alexandre Vassalotti Nadeem Vawda Frank Vercruesse