From e38e021ab324ea676a285ac4667f1fba1469a6bf Mon Sep 17 00:00:00 2001 From: Anish Athalye Date: Tue, 31 Dec 2019 19:14:23 -0500 Subject: Add option to clean recursively --- README.md | 8 ++++++-- dotbot/plugins/clean.py | 11 +++++++++-- test/tests/clean-recursive.bash | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 test/tests/clean-recursive.bash 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 <