summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnish Athalye <me@anishathalye.com>2019-12-31 19:14:23 -0500
committerAnish Athalye <me@anishathalye.com>2019-12-31 19:14:23 -0500
commite38e021ab324ea676a285ac4667f1fba1469a6bf (patch)
treec26695cb033f5019e798d9bdebace6e95147d050
parent81f0d7495518a1fe441343024f2320c95eff86b4 (diff)
Add option to clean recursively
-rw-r--r--README.md8
-rw-r--r--dotbot/plugins/clean.py11
-rw-r--r--test/tests/clean-recursive.bash34
3 files changed, 49 insertions, 4 deletions
diff --git a/README.md b/README.md
index 00094aa..ca7242d 100644
--- a/README.md
+++ b/README.md
@@ -303,7 +303,9 @@ Clean commands are specified as an array of directories to be cleaned.
Clean commands support an extended configuration syntax. In this type of
configuration, commands are specified as directory paths mapping to options. If
the `force` option is set to `true`, dead links are removed even if they don't
-point to a file inside the dotfiles directory.
+point to a file inside the dotfiles directory. If `recursive` is set to `true`,
+the directory is traversed recursively (not recommended for `~` because it will
+be slow).
#### Example
@@ -311,8 +313,10 @@ point to a file inside the dotfiles directory.
- clean: ['~']
- clean:
- ~/.config:
+ ~/:
force: true
+ ~/.config:
+ recursive: true
```
### Defaults
diff --git a/dotbot/plugins/clean.py b/dotbot/plugins/clean.py
index 89251b9..82d77a6 100644
--- a/dotbot/plugins/clean.py
+++ b/dotbot/plugins/clean.py
@@ -20,16 +20,18 @@ class Clean(dotbot.Plugin):
defaults = self._context.defaults().get(self._directive, {})
for target in targets:
force = defaults.get('force', False)
+ recursive = defaults.get('recursive', False)
if isinstance(targets, dict) and isinstance(targets[target], dict):
force = targets[target].get('force', force)
- success &= self._clean(target, force)
+ recursive = targets[target].get('recursive', recursive)
+ success &= self._clean(target, force, recursive)
if success:
self._log.info('All targets have been cleaned')
else:
self._log.error('Some targets were not successfully cleaned')
return success
- def _clean(self, target, force):
+ def _clean(self, target, force, recursive):
'''
Cleans all the broken symbolic links in target if they point to
a subdirectory of the base directory or if forced to clean.
@@ -39,6 +41,11 @@ class Clean(dotbot.Plugin):
return True
for item in os.listdir(os.path.expandvars(os.path.expanduser(target))):
path = os.path.join(os.path.expandvars(os.path.expanduser(target)), item)
+ if recursive and os.path.isdir(path):
+ # isdir implies not islink -- we don't want to descend into
+ # symlinked directories. okay to do a recursive call here
+ # because depth should be fairly limited
+ self._clean(path, force, recursive)
if not os.path.exists(path) and os.path.islink(path):
points_at = os.path.join(os.path.dirname(path), os.readlink(path))
if self._in_directory(path, self._context.base_directory()) or force:
diff --git a/test/tests/clean-recursive.bash b/test/tests/clean-recursive.bash
new file mode 100644
index 0000000..8d8c09d
--- /dev/null
+++ b/test/tests/clean-recursive.bash
@@ -0,0 +1,34 @@
+test_description='clean removes recursively'
+. '../test-lib.bash'
+
+test_expect_success 'setup' '
+mkdir -p ~/a/b
+ln -s /nowhere ~/c
+ln -s /nowhere ~/a/d
+ln -s /nowhere ~/a/b/e
+'
+
+test_expect_success 'run' '
+run_dotbot <<EOF
+- clean:
+ ~/:
+ force: true
+EOF
+'
+
+test_expect_success 'test' '
+! test -h ~/c && test -h ~/a/d && test -h ~/a/b/e
+'
+
+test_expect_success 'run 2' '
+run_dotbot <<EOF
+- clean:
+ ~/:
+ force: true
+ recursive: true
+EOF
+'
+
+test_expect_success 'test 2' '
+! test -h ~/a/d && ! test -h ~/a/b/e
+'