From 7e61e620443de222e3734af1cedeb8829923c35d Mon Sep 17 00:00:00 2001 From: Alexey Min Date: Tue, 18 Feb 2020 18:00:37 +0300 Subject: [PATCH] pmb.helpers.cli: add tab completion option for ask() helper (!1875) Add a helper class that provides readline-based tab completion and an extra optional argument "completion_choices" with possible completion targets. While at this, also improve paramters documentation for ask() func. --- pmb/helpers/cli.py | 49 ++++++++++++++++++++++++++++++++++++++---- test/test_questions.py | 2 +- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/pmb/helpers/cli.py b/pmb/helpers/cli.py index 8fc10a68..aee32105 100644 --- a/pmb/helpers/cli.py +++ b/pmb/helpers/cli.py @@ -3,14 +3,46 @@ import datetime import logging import re +import readline + + +class ReadlineTabCompleter: + """ Stores intermediate state for completer function """ + def __init__(self, options): + """ + :param options: list of possible completions + """ + self.options = sorted(options) + self.matches = [] + + def completer_func(self, input_text, iteration): + """ + :param input_text: text that shall be autocompleted + :param iteration: how many times "tab" was hit + """ + # First time: build match list + if iteration == 0: + if input_text: + self.matches = [s for s in self.options if s and s.startswith(input_text)] + else: + self.matches = self.options[:] + + # Return the N'th item from the match list, if we have that many. + if iteration < len(self.matches): + return self.matches[iteration] + return None def ask(args, question="Continue?", choices=["y", "n"], default="n", - lowercase_answer=True, validation_regex=None): + lowercase_answer=True, validation_regex=None, complete=None): """ - Ask a question on the terminal. When validation_regex is set, the user gets - asked until the answer matches the regex. - :returns: the user's answer + Ask a question on the terminal. + :param question: display prompt + :param choices: short list of possible answers, displayed after prompt if set + :param default: default value to return if user doesn't input anything + :param lowercase_answer: if True, convert return value to lower case + :param validation_regex: if set, keep asking until regex matches + :param complete: set to a list to enable tab completion """ while True: date = datetime.datetime.now().strftime("%H:%M:%S") @@ -20,7 +52,16 @@ def ask(args, question="Continue?", choices=["y", "n"], default="n", if default: question_full += " [" + str(default) + "]" + if complete: + readline.parse_and_bind('tab: complete') + readline.set_completer(ReadlineTabCompleter(complete).completer_func) + ret = input(question_full + ": ") + + # Stop completing (question is answered) + if complete: + readline.set_completer(None) + if lowercase_answer: ret = ret.lower() if ret == "": diff --git a/test/test_questions.py b/test/test_questions.py index f6b0f021..7dcde17c 100644 --- a/test/test_questions.py +++ b/test/test_questions.py @@ -34,7 +34,7 @@ def fake_answers(monkeypatch, answers): the second question with "n" and so on. """ def fake_ask(args, question="Continue?", choices=["y", "n"], default="n", - lowercase_answer=True, validation_regex=None): + lowercase_answer=True, validation_regex=None, complete=None): answer = answers.pop(0) logging.info("pmb.helpers.cli.ask() fake answer: " + answer) return answer