From 16dfb6319488c652315a91f2c504878902a2776b Mon Sep 17 00:00:00 2001 From: Ben Klein Date: Fri, 23 Feb 2018 16:56:19 -0500 Subject: [PATCH 1/4] First iteration of LinkIf shell conditional plugin --- dotbot/dispatcher.py | 1 + plugins/link.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/dotbot/dispatcher.py b/dotbot/dispatcher.py index d1a4f95..c6d66a6 100644 --- a/dotbot/dispatcher.py +++ b/dotbot/dispatcher.py @@ -43,6 +43,7 @@ class Dispatcher(object): def _load_plugins(self): self._plugins = [plugin(self._context) for plugin in Plugin.__subclasses__()] + self._log.debug("Found plugins: " + str(self._plugins)) class DispatchError(Exception): pass diff --git a/plugins/link.py b/plugins/link.py index 5274e3b..29db49d 100644 --- a/plugins/link.py +++ b/plugins/link.py @@ -2,6 +2,8 @@ import os import glob import shutil import dotbot +import subprocess +import copy class Link(dotbot.Plugin): @@ -217,3 +219,44 @@ class Link(dotbot.Plugin): self._log.lowinfo('Link exists %s -> %s' % (link_name, source)) success = True return success + +class Linkif(Link, dotbot.Plugin): + ''' + Symbolically links dotfiles conditionally. + ''' + + _directive = 'linkif' + _config_key = 'if' + + def handle(self, directive, data): + if directive != self._directive: + raise ValueError('LinkIf cannot handle directive %s' % directive) + success = True + data_filtered = copy.deepcopy(data) + for destination, config in data.items(): + if isinstance(config, dict) and config[self._config_key] is not None: + commandsuccess = self._test_success(config[self._config_key]) + if commandsuccess: + del data_filtered[destination][self._config_key] + else: + # remove the item from data + del data_filtered[destination] + else: + self._log.error("Could not find valid conditional in linkif 'if'!") + success &= False + + return self._process_links(data_filtered) + + def _test_success(self, testcommand): + try: + osstdout = subprocess.check_output( + testcommand, + stderr=subprocess.STDOUT, + shell=True, + executable=os.environ.get("SHELL", "/bin/sh") + ) + except subprocess.CalledProcessError as e: + # self._log.warning("Command failed: " + str(testcommand)) + self._log.debug("Command returned "+str(e.returncode)+": \n" + str(testcommand)) + return False + return True From 1abdd1e45636cc7802a96646a91a767799827bad Mon Sep 17 00:00:00 2001 From: Ben Klein Date: Sat, 14 Apr 2018 15:39:52 -0400 Subject: [PATCH 2/4] Move If into Link.handle --- dotbot/dispatcher.py | 2 +- plugins/link.py | 70 ++++++++++++++++++-------------------------- 2 files changed, 29 insertions(+), 43 deletions(-) diff --git a/dotbot/dispatcher.py b/dotbot/dispatcher.py index c6d66a6..090db30 100644 --- a/dotbot/dispatcher.py +++ b/dotbot/dispatcher.py @@ -34,7 +34,7 @@ class Dispatcher(object): self._log.error( 'An error was encountered while executing action %s' % action) - self._log.debug(err) + self._log.debug("Err: " + str(type(err)) + ": " + str(err)) if not handled: success = False self._log.error('Action %s not handled' % action) diff --git a/plugins/link.py b/plugins/link.py index 29db49d..49d7461 100644 --- a/plugins/link.py +++ b/plugins/link.py @@ -12,6 +12,7 @@ class Link(dotbot.Plugin): ''' _directive = 'link' + _config_if_key = 'if' def can_handle(self, directive): return directive == self._directive @@ -19,7 +20,19 @@ class Link(dotbot.Plugin): def handle(self, directive, data): if directive != self._directive: raise ValueError('Link cannot handle directive %s' % directive) - return self._process_links(data) + data_filtered = copy.deepcopy(data) + for destination, config in data_filtered.items(): + if isinstance(config, dict) and self._config_if_key in config: + self._log.debug("testing conditional: %s" % config[self._config_if_key]) + commandsuccess = self._test_success(config[self._config_if_key]) + if commandsuccess: + # command successful, treat as normal link + del data_filtered[destination][self._config_if_key] + else: + # remove the item from data, we aren't going to link it + del data_filtered[destination] + + return self._process_links(data_filtered) def _process_links(self, links): success = True @@ -92,6 +105,20 @@ class Link(dotbot.Plugin): self._log.error('Some links were not successfully set up') return success + def _test_success(self, testcommand): + try: + osstdout = subprocess.check_output( + testcommand, + stderr=subprocess.STDOUT, + shell=True, + executable=os.environ.get("SHELL", "/bin/sh") + ) + except subprocess.CalledProcessError as e: + # self._log.warning("Command failed: " + str(testcommand)) + self._log.debug("Command returned "+str(e.returncode)+": \n" + str(testcommand)) + return False + return True + def _default_source(self, destination, source): if source is None: basename = os.path.basename(destination) @@ -219,44 +246,3 @@ class Link(dotbot.Plugin): self._log.lowinfo('Link exists %s -> %s' % (link_name, source)) success = True return success - -class Linkif(Link, dotbot.Plugin): - ''' - Symbolically links dotfiles conditionally. - ''' - - _directive = 'linkif' - _config_key = 'if' - - def handle(self, directive, data): - if directive != self._directive: - raise ValueError('LinkIf cannot handle directive %s' % directive) - success = True - data_filtered = copy.deepcopy(data) - for destination, config in data.items(): - if isinstance(config, dict) and config[self._config_key] is not None: - commandsuccess = self._test_success(config[self._config_key]) - if commandsuccess: - del data_filtered[destination][self._config_key] - else: - # remove the item from data - del data_filtered[destination] - else: - self._log.error("Could not find valid conditional in linkif 'if'!") - success &= False - - return self._process_links(data_filtered) - - def _test_success(self, testcommand): - try: - osstdout = subprocess.check_output( - testcommand, - stderr=subprocess.STDOUT, - shell=True, - executable=os.environ.get("SHELL", "/bin/sh") - ) - except subprocess.CalledProcessError as e: - # self._log.warning("Command failed: " + str(testcommand)) - self._log.debug("Command returned "+str(e.returncode)+": \n" + str(testcommand)) - return False - return True From 15c8e8c12ddb2bfec9df1fa9d6629bf9bb015acf Mon Sep 17 00:00:00 2001 From: Ben Klein Date: Thu, 24 May 2018 10:34:48 -0400 Subject: [PATCH 3/4] Fix Runtime dictionary modifications --- plugins/link.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/link.py b/plugins/link.py index 49d7461..243f157 100644 --- a/plugins/link.py +++ b/plugins/link.py @@ -21,16 +21,16 @@ class Link(dotbot.Plugin): if directive != self._directive: raise ValueError('Link cannot handle directive %s' % directive) data_filtered = copy.deepcopy(data) - for destination, config in data_filtered.items(): - if isinstance(config, dict) and self._config_if_key in config: - self._log.debug("testing conditional: %s" % config[self._config_if_key]) - commandsuccess = self._test_success(config[self._config_if_key]) + for d_key in list(data_filtered): + if isinstance(data_filtered[d_key], dict) and self._config_if_key in data_filtered[d_key]: + self._log.debug("testing conditional: %s" % data_filtered[d_key][self._config_if_key]) + commandsuccess = self._test_success(data_filtered[d_key][self._config_if_key]) if commandsuccess: # command successful, treat as normal link - del data_filtered[destination][self._config_if_key] + del data_filtered[d_key][self._config_if_key] else: # remove the item from data, we aren't going to link it - del data_filtered[destination] + del data_filtered[d_key] return self._process_links(data_filtered) From 81937086b24cf13918270c4a2d6500d86e2ec5d5 Mon Sep 17 00:00:00 2001 From: Ben Klein Date: Wed, 19 Sep 2018 20:37:24 -0400 Subject: [PATCH 4/4] Add `if` to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1e5a5fe..759647e 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,7 @@ Available extended configuration parameters: | `force` | Force removes the old target, file or folder, and forces a new link (default:false) | | `relative` | Use a relative path when creating the symlink (default:false, absolute links) | | `glob` | Treat a `*` character as a wildcard, and perform link operations on all of those matches (default:false) | +| `if` | Execute this in your `$SHELL` and only link if it is successful. | #### Example