Merge pull request #303 from YunoHost/rework-prompt-again

Rework cli prompt() again
This commit is contained in:
Alexandre Aubin 2021-09-23 21:47:23 +02:00 committed by GitHub
commit ce3ec1025c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 51 additions and 27 deletions

4
debian/control vendored
View file

@ -14,7 +14,9 @@ Depends: ${misc:Depends}, ${python3:Depends},
python3-gevent-websocket,
python3-toml,
python3-psutil,
python3-tz
python3-tz,
python3-prompt-toolkit,
python3-pygments
Breaks: yunohost (<< 4.1)
Description: prototype interfaces with ease in Python
Quickly and easily prototype interfaces for your application.

View file

@ -1,7 +1,6 @@
{
"argument_required": "Argument '{argument}' is required",
"authentication_required": "Authentication required",
"colon": "{}: ",
"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",

View file

@ -2,12 +2,10 @@
import os
import sys
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
@ -533,6 +531,8 @@ class Interface:
color="blue",
prefill="",
is_multiline=False,
autocomplete=[],
help=None,
):
"""Prompt for a value
@ -547,16 +547,37 @@ class Interface:
def _prompt(message):
if is_password:
return getpass.getpass(colorize(m18n.g("colon", message), color))
elif not is_multiline:
print(colorize(m18n.g("colon", message), color), end="")
set_startup_hook(lambda: insert_text(prefill))
try:
value = input()
finally:
set_startup_hook()
return value
if not is_multiline:
import prompt_toolkit
from prompt_toolkit.contrib.completers import WordCompleter
from pygments.token import Token
autocomplete_ = WordCompleter(autocomplete)
style = prompt_toolkit.styles.style_from_dict({
Token.Message: f'#ansi{color} bold',
})
def get_bottom_toolbar_tokens(cli):
if help:
return [(Token, help)]
else:
return []
def get_tokens(cli):
return [
(Token.Message, message),
(Token, ': '),
]
return prompt_toolkit.prompt(get_prompt_tokens=get_tokens,
get_bottom_toolbar_tokens=get_bottom_toolbar_tokens,
style=style,
default=prefill,
true_color=True,
completer=autocomplete_,
is_password=is_password)
else:
while True:
value = input(

View file

@ -15,7 +15,7 @@ from moulinette.core import MoulinetteError
# Files & directories --------------------------------------------------
def read_file(file_path):
def read_file(file_path, file_mode="r"):
"""
Read a regular text file
@ -35,7 +35,7 @@ def read_file(file_path):
# Open file and read content
try:
with open(file_path, "r") as f:
with open(file_path, file_mode) as f:
file_content = f.read()
except IOError as e:
raise MoulinetteError("cannot_open_file", file=file_path, error=str(e))

View file

@ -33,6 +33,8 @@ install_deps = [
"toml",
"gevent-websocket",
"bottle",
"prompt-toolkit==1.0.15", # To be bumped to debian version once we're on bullseye (+ need tweaks in cli.py)
"pygments",
]
test_deps = [

View file

@ -188,7 +188,7 @@ class TestAuthAPI:
class TestAuthCLI:
def test_login(self, moulinette_cli, capsys, mocker):
mocker.patch("os.isatty", return_value=True)
mocker.patch("getpass.getpass", return_value="dummy")
mocker.patch("prompt_toolkit.prompt", return_value="dummy")
moulinette_cli.run(["testauth", "default"], output_as="plain")
message = capsys.readouterr()
@ -201,25 +201,25 @@ class TestAuthCLI:
def test_login_bad_password(self, moulinette_cli, capsys, mocker):
mocker.patch("os.isatty", return_value=True)
mocker.patch("getpass.getpass", return_value="Bad Password")
mocker.patch("prompt_toolkit.prompt", return_value="Bad Password")
with pytest.raises(MoulinetteError):
moulinette_cli.run(["testauth", "default"], output_as="plain")
mocker.patch("os.isatty", return_value=True)
mocker.patch("getpass.getpass", return_value="Bad Password")
mocker.patch("prompt_toolkit.prompt", return_value="Bad Password")
with pytest.raises(MoulinetteError):
moulinette_cli.run(["testauth", "default"], output_as="plain")
def test_login_wrong_profile(self, moulinette_cli, mocker):
mocker.patch("os.isatty", return_value=True)
mocker.patch("getpass.getpass", return_value="dummy")
mocker.patch("prompt_toolkit.prompt", return_value="dummy")
with pytest.raises(MoulinetteError) as exception:
moulinette_cli.run(["testauth", "other-profile"], output_as="none")
assert "invalid_password" in str(exception)
mocker.patch("os.isatty", return_value=True)
mocker.patch("getpass.getpass", return_value="yoloswag")
mocker.patch("prompt_toolkit.prompt", return_value="yoloswag")
with pytest.raises(MoulinetteError) as exception:
moulinette_cli.run(["testauth", "default"], output_as="none")
@ -239,7 +239,7 @@ class TestAuthCLI:
def test_request_only_cli(self, capsys, moulinette_cli, mocker):
mocker.patch("os.isatty", return_value=True)
mocker.patch("getpass.getpass", return_value="dummy")
mocker.patch("prompt_toolkit.prompt", return_value="dummy")
moulinette_cli.run(["testauth", "only-cli"], output_as="plain")
message = capsys.readouterr()
@ -248,7 +248,7 @@ class TestAuthCLI:
def test_request_not_logged_only_cli(self, capsys, moulinette_cli, mocker):
mocker.patch("os.isatty", return_value=True)
mocker.patch("getpass.getpass")
mocker.patch("prompt_toolkit.prompt")
with pytest.raises(MoulinetteError) as exception:
moulinette_cli.run(["testauth", "only-cli"], output_as="plain")
@ -259,7 +259,7 @@ class TestAuthCLI:
def test_request_with_callback(self, moulinette_cli, capsys, mocker):
mocker.patch("os.isatty", return_value=True)
mocker.patch("getpass.getpass", return_value="dummy")
mocker.patch("prompt_toolkit.prompt", return_value="dummy")
moulinette_cli.run(["--version"], output_as="plain")
message = capsys.readouterr()
@ -278,7 +278,7 @@ class TestAuthCLI:
def test_request_with_arg(self, moulinette_cli, capsys, mocker):
mocker.patch("os.isatty", return_value=True)
mocker.patch("getpass.getpass", return_value="dummy")
mocker.patch("prompt_toolkit.prompt", return_value="dummy")
moulinette_cli.run(["testauth", "with_arg", "yoloswag"], output_as="plain")
message = capsys.readouterr()
@ -286,7 +286,7 @@ class TestAuthCLI:
def test_request_arg_with_extra(self, moulinette_cli, capsys, mocker):
mocker.patch("os.isatty", return_value=True)
mocker.patch("getpass.getpass", return_value="dummy")
mocker.patch("prompt_toolkit.prompt", return_value="dummy")
moulinette_cli.run(
["testauth", "with_extra_str_only", "YoLoSwAg"], output_as="plain"
)
@ -306,7 +306,7 @@ class TestAuthCLI:
def test_request_arg_with_type(self, moulinette_cli, capsys, mocker):
mocker.patch("os.isatty", return_value=True)
mocker.patch("getpass.getpass", return_value="dummy")
mocker.patch("prompt_toolkit.prompt", return_value="dummy")
moulinette_cli.run(["testauth", "with_type_int", "12345"], output_as="plain")
message = capsys.readouterr()