From 882caf478668e99a61f8bf7797b9b684a0334cdf Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 30 Aug 2021 14:43:14 +0200 Subject: [PATCH 1/4] [enh] Add prefill and multiline in prompt --- locales/en.json | 1 + moulinette/interfaces/cli.py | 35 ++++++++++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/locales/en.json b/locales/en.json index 68aa640a..358f326f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -5,6 +5,7 @@ "confirm": "Confirm {prompt}", "deprecated_command": "'{prog} {command}' is deprecated and will be removed in the future", "deprecated_command_alias": "'{prog} {old}' is deprecated and will be removed in the future, use '{prog} {new}' instead", + "edit_text_question": "{}. Edit this text ? [yN]: ", "error": "Error:", "file_not_exist": "File does not exist: '{path}'", "folder_exists": "Folder already exists: '{path}'", diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index c75b8d60..e6552b73 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -6,8 +6,11 @@ import getpass import locale import logging import argparse +import tempfile +from readline import insert_text, set_startup_hook from collections import OrderedDict from datetime import date, datetime +from subprocess import call from moulinette import m18n, Moulinette from moulinette.actionsmap import ActionsMap @@ -522,7 +525,7 @@ class Interface: credentials = self.prompt(msg, True, False, color="yellow") return authenticator.authenticate_credentials(credentials=credentials) - def prompt(self, message, is_password=False, confirm=False, color="blue"): + def prompt(self, message, is_password=False, confirm=False, color="blue", prefill="", is_multiline=False): """Prompt for a value Keyword arguments: @@ -533,11 +536,33 @@ class Interface: raise MoulinetteError( "Not a tty, can't do interactive prompts", raw_msg=True ) + def prompt(message): + if is_password: + return getpass.getpass(colorize(m18n.g("colon", message), color)) + elif is_multiline: + while True: + value = input(colorize(m18n.g("edit_text_question", message), color)) + if value in ["", "n", "N", "no", "NO"]: + return prefill + elif value in ['y', 'yes', 'Y', 'YES']: + break - if is_password: - prompt = lambda m: getpass.getpass(colorize(m18n.g("colon", m), color)) - else: - prompt = lambda m: input(colorize(m18n.g("colon", m), color)) + initial_message = prefill.encode('utf-8') + + with tempfile.NamedTemporaryFile(suffix=".tmp") as tf: + tf.write(initial_message) + tf.flush() + call(["editor", tf.name]) + tf.seek(0) + edited_message = tf.read() + return edited_message.decode("utf-8") + else: + set_startup_hook(lambda: insert_text(prefill)) + try: + value = input(colorize(m18n.g("colon", message), color)) + finally: + set_startup_hook() + return value value = prompt(message) if confirm: From bd557468d380695879ce272e01972756f811a1f0 Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 2 Sep 2021 02:28:51 +0200 Subject: [PATCH 2/4] [enh] read_yaml support stream --- moulinette/utils/filesystem.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/moulinette/utils/filesystem.py b/moulinette/utils/filesystem.py index 4844dd1f..9b1456d9 100644 --- a/moulinette/utils/filesystem.py +++ b/moulinette/utils/filesystem.py @@ -67,16 +67,17 @@ def read_json(file_path): return loaded_json -def read_yaml(file_path): +def read_yaml(file_): """ Safely read a yaml file Keyword argument: - file_path -- Path to the yaml file + file -- Path or stream to the yaml file """ # Read file - file_content = read_file(file_path) + file_path = file_ if isinstance(file_, str) else file_.name + file_content = read_file(file_) if isinstance(file_, str) else file_ # Try to load yaml to check if it's syntaxically correct try: From e08d0139f64b47f733e49634434b80f0a708fab2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 3 Sep 2021 15:30:35 +0200 Subject: [PATCH 3/4] cli promt: improve coe for yes/no parsing --- moulinette/interfaces/cli.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index e6552b73..0830a84d 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -542,9 +542,10 @@ class Interface: elif is_multiline: while True: value = input(colorize(m18n.g("edit_text_question", message), color)) - if value in ["", "n", "N", "no", "NO"]: + value = value.lower().strip() + if value in ["", "n", "no"]: return prefill - elif value in ['y', 'yes', 'Y', 'YES']: + elif value in ['y', 'yes']: break initial_message = prefill.encode('utf-8') From 8909577b9172bd98f2537f751ef3221acf31c1b6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 3 Sep 2021 15:49:36 +0200 Subject: [PATCH 4/4] cli prompt: misc code improvements --- moulinette/interfaces/cli.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index 0830a84d..7802b8d1 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -536,10 +536,19 @@ class Interface: raise MoulinetteError( "Not a tty, can't do interactive prompts", raw_msg=True ) - def prompt(message): + + def _prompt(message): + if is_password: return getpass.getpass(colorize(m18n.g("colon", message), color)) - elif is_multiline: + elif not is_multiline: + set_startup_hook(lambda: insert_text(prefill)) + try: + value = input(colorize(m18n.g("colon", message), color)) + finally: + set_startup_hook() + return value + else: while True: value = input(colorize(m18n.g("edit_text_question", message), color)) value = value.lower().strip() @@ -557,18 +566,12 @@ class Interface: tf.seek(0) edited_message = tf.read() return edited_message.decode("utf-8") - else: - set_startup_hook(lambda: insert_text(prefill)) - try: - value = input(colorize(m18n.g("colon", message), color)) - finally: - set_startup_hook() - return value - value = prompt(message) + + value = _prompt(message) if confirm: m = message[0].lower() + message[1:] - if prompt(m18n.g("confirm", prompt=m)) != value: + if _prompt(m18n.g("confirm", prompt=m)) != value: raise MoulinetteValidationError("values_mismatch") return value