Skip to content

Commit

Permalink
util.rrmdir returns list of deleted folders
Browse files Browse the repository at this point in the history
  • Loading branch information
k1o0 committed Feb 20, 2024
1 parent 25d299d commit 02d9535
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 28 deletions.
14 changes: 12 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
# Changelog
## [Latest](https://github.com/int-brain-lab/iblutil/commits/main) [1.7.4]
* Patches (X.X.1) are small or urgent bug fixes or changes that don't affect compatibility.
* Minor releases (X.1.X) are new features such as added functions or small changes that don't cause major compatibility issues.
* Major releases (1.X.X) are major new features or changes that break backward compatibility in a big way.

## [Latest](https://github.com/int-brain-lab/iblutil/commits/main) [1.8.0]

### Modified

- util.rrmdir returns list of deleted folders; added function should have been minor release

## [1.7.5]

### Added

- util.rrmdir: method to recursively delete empty folders

## [1.7.3]
## [1.7.4]

### Modified

Expand Down
2 changes: 1 addition & 1 deletion iblutil/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.7.5'
__version__ = '1.8.0'
35 changes: 22 additions & 13 deletions iblutil/util.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from itertools import takewhile
from pathlib import Path
import collections
import colorlog
Expand Down Expand Up @@ -219,28 +220,36 @@ def log_to_file(log='ibl', filename=None):
return log


def rrmdir(folder: Path, levels: int = 0) -> None:
def rrmdir(folder: Path, levels: int = 0):
"""
Recursively remove a folder and its parents up to a defined level - if they are empty.
Parameters
----------
folder : Path
folder : pathlib.Path
The path to a folder at which to start the recursion.
levels : int
Recursion level, i.e., the number of parents to delete, relative to
`folder`. Defaults to 0 - which has the same effect as pathlib.Path.rmdir().
Recursion level, i.e. the number of parents to delete, relative to `folder`.
Defaults to 0 - which has the same effect as `pathlib.Path.rmdir` except that it won't
raise an OSError if the directory is not empty.
Returns
-------
list of pathlib.Path
A list of folders that were recursively removed.
Raises
------
FileNotFoundError
If `folder` does not exist
If `folder` does not exist.
PermissionError
Insufficient privileges or folder in use by another process.
NotADirectoryError
The folder provided is most likely a file.
"""
if folder.exists():
if not any(folder.iterdir()) and not folder == Path(folder.anchor):
log.debug(f'Deleting empty folder {folder}')
folder.rmdir()
if levels > 0:
rrmdir(folder.parent, levels=levels-1)
else:
raise FileNotFoundError(folder)
try: # a sorted list of absolute nested folder paths
to_remove = (folder, *folder.parents[:levels]) # py >= 3.9
except TypeError: # py <= 3.8 compatible
to_remove = (folder, *[folder.parents[n] for n in range(levels)])
# filter list to those that are empty; if statement always true as rmdir returns None
return [f for f in takewhile(lambda f: not any(f.iterdir()), to_remove) if not f.rmdir()]
26 changes: 14 additions & 12 deletions tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,28 +155,30 @@ def test_rrmdir(self):
# default level = 0, folder contains file - nothing should happen
folder_level_0.mkdir(parents=True)
(file := folder_level_0.joinpath('file')).touch()
util.rrmdir(folder_level_0)
assert file.exists()
self.assertEqual([], util.rrmdir(folder_level_0))
self.assertTrue(file.exists())

# default level = 0, folder and all parents are empty
file.unlink()
util.rrmdir(folder_level_0)
assert not folder_level_0.exists()
assert folder_level_1.exists()
self.assertEqual([folder_level_0], util.rrmdir(folder_level_0))
self.assertFalse(folder_level_0.exists())
self.assertTrue(folder_level_1.exists())

# remove empty folders to level 2
folder_level_0.mkdir(parents=True)
util.rrmdir(folder_level_0, levels=2)
assert not folder_level_2.exists()
assert folder_level_3.exists()
removed = util.rrmdir(folder_level_0, levels=2)
self.assertEqual([folder_level_0, folder_level_1, folder_level_2], removed)
self.assertFalse(folder_level_2.exists())
self.assertTrue(folder_level_3.exists())

# remove empty folders to level 3, with a file in level 2
folder_level_0.mkdir(parents=True)
(file := folder_level_2.joinpath('file')).touch()
util.rrmdir(folder_level_0, levels=3)
assert not folder_level_1.exists()
assert file.exists()
removed = util.rrmdir(folder_level_0, levels=3)
self.assertEqual(removed, [folder_level_0, folder_level_1])
self.assertFalse(folder_level_1.exists())
self.assertTrue(file.exists())


if __name__ == "__main__":
if __name__ == '__main__':
unittest.main(exit=False)

0 comments on commit 02d9535

Please sign in to comment.