diff --git a/README.md b/README.md index 5b77cf0..bc20e9d 100644 --- a/README.md +++ b/README.md @@ -159,15 +159,28 @@ base directory (that is specified when running the installer). #### Format -Shell commands are specified as an array of commands, where each command is a -two element array containing the actual shell command as the first element and -a human-readable description as the second element. +Shell commands can be specified in several different ways. The simplest way is +just to specify a command as a string containing the command to be run. Another +way is to specify a two element array where the first element is the shell +command and the second is an optional human-readable description. Shell +commands support an extended syntax as well, which provides more fine-grained +control. A command can be specified as a dictionary that contains the command +to be run, a description, and whether stdin, stdout, and stderr are enabled. In +this syntax, all keys are optional except for the command itself. ##### Example (YAML) ```yaml - shell: + - mkdir -p ~/src - [mkdir -p ~/downloads, Creating downloads directory] + - + command: read var && echo Your variable is $var + stdin: true + stdout: true + - + command: read fail + stderr: true ``` ##### Example (JSON) @@ -175,7 +188,17 @@ a human-readable description as the second element. ```json [{ "shell": [ - ["mkdir -p ~/downloads", "Creating downloads directory"] + "mkdir -p ~/src", + ["mkdir -p ~/downloads", "Creating downloads directory"], + { + "command": "read var && echo Your variable is $var", + "stdin": true, + "stdout": true + }, + { + "command": "read fail", + "stderr": true + } ] }] ``` diff --git a/dotbot/executor/commandrunner.py b/dotbot/executor/commandrunner.py index af4d1ef..fd44e49 100644 --- a/dotbot/executor/commandrunner.py +++ b/dotbot/executor/commandrunner.py @@ -20,10 +20,29 @@ class CommandRunner(Executor): def _process_commands(self, data): success = True with open(os.devnull, 'w') as devnull: - for cmd, msg in data: - self._log.lowinfo('%s [%s]' % (msg, cmd)) - ret = subprocess.call(cmd, shell = True, stdout = devnull, - stderr = devnull, cwd = self._base_directory) + for item in data: + stdin = stdout = stderr = devnull + if isinstance(item, dict): + cmd = item['command'] + msg = item.get('description', None) + if item.get('stdin', False) is True: + stdin = None + if item.get('stdout', False) is True: + stdout = None + if item.get('stderr', False) is True: + stderr = None + 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) + else: + self._log.lowinfo('%s [%s]' % (msg, cmd)) + ret = subprocess.call(cmd, shell = True, stdin = stdin, stdout = stdout, + stderr = stderr, cwd = self._base_directory) if ret != 0: success = False self._log.warning('Command [%s] failed' % cmd)