diff --git a/dotbot/plugins/clean.py b/dotbot/plugins/clean.py index 82d77a6..09b11d8 100644 --- a/dotbot/plugins/clean.py +++ b/dotbot/plugins/clean.py @@ -1,4 +1,6 @@ -import os, dotbot +import os +import dotbot + class Clean(dotbot.Plugin): ''' diff --git a/dotbot/plugins/create.py b/dotbot/plugins/create.py index dc119da..015645a 100644 --- a/dotbot/plugins/create.py +++ b/dotbot/plugins/create.py @@ -1,8 +1,5 @@ import os -import glob -import shutil import dotbot -import subprocess class Create(dotbot.Plugin): diff --git a/dotbot/plugins/link.py b/dotbot/plugins/link.py index 82a61ce..6f2b562 100644 --- a/dotbot/plugins/link.py +++ b/dotbot/plugins/link.py @@ -2,6 +2,7 @@ import os import glob import shutil import dotbot +import dotbot.util import subprocess @@ -105,14 +106,7 @@ class Link(dotbot.Plugin): return success def _test_success(self, command): - with open(os.devnull, 'w') as devnull: - ret = subprocess.call( - command, - shell=True, - stdout=devnull, - stderr=devnull, - executable=os.environ.get('SHELL'), - ) + ret = dotbot.util.shell_command(command, cwd=self._context.base_directory()) if ret != 0: self._log.debug('Test \'%s\' returned false' % command) return ret == 0 diff --git a/dotbot/plugins/shell.py b/dotbot/plugins/shell.py index 06a9a89..3092f20 100644 --- a/dotbot/plugins/shell.py +++ b/dotbot/plugins/shell.py @@ -1,4 +1,8 @@ -import os, subprocess, dotbot +import os +import subprocess +import dotbot +import dotbot.util + class Shell(dotbot.Plugin): ''' @@ -19,48 +23,40 @@ class Shell(dotbot.Plugin): def _process_commands(self, data): success = True defaults = self._context.defaults().get('shell', {}) - with open(os.devnull, 'w') as devnull: - for item in data: - stdin = stdout = stderr = devnull - quiet = False - if defaults.get('stdin', False) == True: - stdin = None - if defaults.get('stdout', False) == True: - stdout = None - if defaults.get('stderr', False) == True: - stderr = None - if defaults.get('quiet', False) == True: - quiet = True - if isinstance(item, dict): - cmd = item['command'] - msg = item.get('description', None) - if 'stdin' in item: - stdin = None if item['stdin'] == True else devnull - if 'stdout' in item: - stdout = None if item['stdout'] == True else devnull - if 'stderr' in item: - stderr = None if item['stderr'] == True else devnull - if 'quiet' in item: - quiet = True if item['quiet'] == True else False - elif isinstance(item, list): - cmd = item[0] - msg = item[1] if len(item) > 1 else None - else: - cmd = item - msg = None - if msg is None: - self._log.lowinfo(cmd) - elif quiet: - self._log.lowinfo('%s' % msg) - else: - self._log.lowinfo('%s [%s]' % (msg, cmd)) - executable = os.environ.get('SHELL') - ret = subprocess.call(cmd, shell=True, stdin=stdin, stdout=stdout, - stderr=stderr, cwd=self._context.base_directory(), - executable=executable) - if ret != 0: - success = False - self._log.warning('Command [%s] failed' % cmd) + for item in data: + stdin = defaults.get('stdin', False) + stdout = defaults.get('stdout', False) + stderr = defaults.get('stderr', False) + quiet = defaults.get('quiet', False) + if isinstance(item, dict): + cmd = item['command'] + msg = item.get('description', None) + stdin = item.get('stdin', stdin) + stdout = item.get('stdout', stdout) + stderr = item.get('stderr', stderr) + quiet = item.get('quiet', quiet) + elif isinstance(item, list): + cmd = item[0] + msg = item[1] if len(item) > 1 else None + else: + cmd = item + msg = None + if msg is None: + self._log.lowinfo(cmd) + elif quiet: + self._log.lowinfo('%s' % msg) + else: + self._log.lowinfo('%s [%s]' % (msg, cmd)) + ret = dotbot.util.shell_command( + cmd, + cwd=self._context.base_directory(), + enable_stdin=stdin, + enable_stdout=stdout, + enable_stderr=stderr + ) + if ret != 0: + success = False + self._log.warning('Command [%s] failed' % cmd) if success: self._log.info('All commands have been executed') else: diff --git a/dotbot/util/__init__.py b/dotbot/util/__init__.py index e69de29..0c5a8f5 100644 --- a/dotbot/util/__init__.py +++ b/dotbot/util/__init__.py @@ -0,0 +1 @@ +from .common import shell_command diff --git a/dotbot/util/common.py b/dotbot/util/common.py new file mode 100644 index 0000000..d1e2000 --- /dev/null +++ b/dotbot/util/common.py @@ -0,0 +1,34 @@ +import os +import subprocess +import platform + + +def shell_command(command, cwd=None, enable_stdin=False, enable_stdout=False, enable_stderr=False): + with open(os.devnull, 'w') as devnull_w, open(os.devnull, 'r') as devnull_r: + stdin = None if enable_stdin else devnull_r + stdout = None if enable_stdout else devnull_w + stderr = None if enable_stderr else devnull_w + executable = os.environ.get('SHELL') + if platform.system() == 'Windows': + # We avoid setting the executable kwarg on Windows because it does + # not have the desired effect when combined with shell=True. It + # will result in the correct program being run (e.g. bash), but it + # will be invoked with a '/c' argument instead of a '-c' argument, + # which it won't understand. + # + # See https://github.com/anishathalye/dotbot/issues/219 and + # https://bugs.python.org/issue40467. + # + # This means that complex commands that require Bash's parsing + # won't work; a workaround for this is to write the command as + # `bash -c "..."`. + executable = None + return subprocess.call( + command, + shell=True, + executable=executable, + stdin=stdin, + stdout=stdout, + stderr=stderr, + cwd=cwd + )