diff --git a/README.md b/README.md index 10ad29c..3c443af 100644 --- a/README.md +++ b/README.md @@ -183,8 +183,9 @@ 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. +`stderr` are enabled. Another useful key is `confirm` which, if true, prompts +the user at install time, allowing them to not run that script that time. +In this syntax, all keys are optional except for the command itself. #### Example @@ -199,6 +200,9 @@ command itself. - command: read fail stderr: true + - + command: rm -rf ~/.config + confirm: true ``` ### Clean diff --git a/dotbot/executor/commandrunner.py b/dotbot/executor/commandrunner.py index 45d40f5..65e81cd 100644 --- a/dotbot/executor/commandrunner.py +++ b/dotbot/executor/commandrunner.py @@ -17,6 +17,42 @@ class CommandRunner(Executor): directive) return self._process_commands(data) +# Modified, but source: +# http://stackoverflow.com/questions/3041986/python-command-line-yes-no-input + def _query_yes_no(question, default="yes"): + """Ask a yes/no question via raw_input() and return their answer. + + "question" is a string that is presented to the user. + "default" is the presumed answer if the user just hits . + It must be "yes" (the default), "no" or None (meaning + an answer is required of the user). + + The "answer" return value is True for "yes" or False for "no". + """ + valid = {"yes": True, "y": True, "ye": True, + "Yes": True, "No", False, + "no": False, "n": False} + if default is None: + prompt = " [y/n] " + elif default in valid: + if valid[default] == True: + prompt = " [Y/n] " + elif valid[default] == False: + prompt = " [y/N] " + else: + raise ValueError("invalid default answer: '%s'" % default) + + while True: + sys.stdout.write(question + prompt) + choice = raw_input().lower() + if default is not None and choice == '': + return valid[default] + elif choice in valid: + return valid[choice] + else: + sys.stdout.write("Please respond with 'yes' or 'no'.\n") + + def _process_commands(self, data): success = True with open(os.devnull, 'w') as devnull: @@ -31,6 +67,8 @@ class CommandRunner(Executor): stdout = None if item.get('stderr', False) is True: stderr = None + if item.get('confirm', False) is True: + confirm = True elif isinstance(item, list): cmd = item[0] msg = item[1] if len(item) > 1 else None @@ -41,11 +79,17 @@ class CommandRunner(Executor): 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) + if confirm: + run = self._query_yes_no('Do you want to run this command:' + ' %s' % cmd) + else: + run = True; + if run: + 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) if success: self._log.info('All commands have been executed') else: