mirror of
https://github.com/YunoHost/moulinette.git
synced 2024-09-03 20:06:31 +02:00
Merge pull request #303 from YunoHost/rework-prompt-again
Rework cli prompt() again
This commit is contained in:
commit
ce3ec1025c
6 changed files with 51 additions and 27 deletions
4
debian/control
vendored
4
debian/control
vendored
|
@ -14,7 +14,9 @@ Depends: ${misc:Depends}, ${python3:Depends},
|
||||||
python3-gevent-websocket,
|
python3-gevent-websocket,
|
||||||
python3-toml,
|
python3-toml,
|
||||||
python3-psutil,
|
python3-psutil,
|
||||||
python3-tz
|
python3-tz,
|
||||||
|
python3-prompt-toolkit,
|
||||||
|
python3-pygments
|
||||||
Breaks: yunohost (<< 4.1)
|
Breaks: yunohost (<< 4.1)
|
||||||
Description: prototype interfaces with ease in Python
|
Description: prototype interfaces with ease in Python
|
||||||
Quickly and easily prototype interfaces for your application.
|
Quickly and easily prototype interfaces for your application.
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"argument_required": "Argument '{argument}' is required",
|
"argument_required": "Argument '{argument}' is required",
|
||||||
"authentication_required": "Authentication required",
|
"authentication_required": "Authentication required",
|
||||||
"colon": "{}: ",
|
|
||||||
"confirm": "Confirm {prompt}",
|
"confirm": "Confirm {prompt}",
|
||||||
"deprecated_command": "'{prog} {command}' is deprecated and will be removed in the future",
|
"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",
|
"deprecated_command_alias": "'{prog} {old}' is deprecated and will be removed in the future, use '{prog} {new}' instead",
|
||||||
|
|
|
@ -2,12 +2,10 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import getpass
|
|
||||||
import locale
|
import locale
|
||||||
import logging
|
import logging
|
||||||
import argparse
|
import argparse
|
||||||
import tempfile
|
import tempfile
|
||||||
from readline import insert_text, set_startup_hook
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
from subprocess import call
|
from subprocess import call
|
||||||
|
@ -533,6 +531,8 @@ class Interface:
|
||||||
color="blue",
|
color="blue",
|
||||||
prefill="",
|
prefill="",
|
||||||
is_multiline=False,
|
is_multiline=False,
|
||||||
|
autocomplete=[],
|
||||||
|
help=None,
|
||||||
):
|
):
|
||||||
"""Prompt for a value
|
"""Prompt for a value
|
||||||
|
|
||||||
|
@ -547,16 +547,37 @@ class Interface:
|
||||||
|
|
||||||
def _prompt(message):
|
def _prompt(message):
|
||||||
|
|
||||||
if is_password:
|
if not is_multiline:
|
||||||
return getpass.getpass(colorize(m18n.g("colon", message), color))
|
|
||||||
elif not is_multiline:
|
import prompt_toolkit
|
||||||
print(colorize(m18n.g("colon", message), color), end="")
|
from prompt_toolkit.contrib.completers import WordCompleter
|
||||||
set_startup_hook(lambda: insert_text(prefill))
|
from pygments.token import Token
|
||||||
try:
|
|
||||||
value = input()
|
autocomplete_ = WordCompleter(autocomplete)
|
||||||
finally:
|
style = prompt_toolkit.styles.style_from_dict({
|
||||||
set_startup_hook()
|
Token.Message: f'#ansi{color} bold',
|
||||||
return value
|
})
|
||||||
|
|
||||||
|
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:
|
else:
|
||||||
while True:
|
while True:
|
||||||
value = input(
|
value = input(
|
||||||
|
|
|
@ -15,7 +15,7 @@ from moulinette.core import MoulinetteError
|
||||||
# Files & directories --------------------------------------------------
|
# Files & directories --------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def read_file(file_path):
|
def read_file(file_path, file_mode="r"):
|
||||||
"""
|
"""
|
||||||
Read a regular text file
|
Read a regular text file
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ def read_file(file_path):
|
||||||
|
|
||||||
# Open file and read content
|
# Open file and read content
|
||||||
try:
|
try:
|
||||||
with open(file_path, "r") as f:
|
with open(file_path, file_mode) as f:
|
||||||
file_content = f.read()
|
file_content = f.read()
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
raise MoulinetteError("cannot_open_file", file=file_path, error=str(e))
|
raise MoulinetteError("cannot_open_file", file=file_path, error=str(e))
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -33,6 +33,8 @@ install_deps = [
|
||||||
"toml",
|
"toml",
|
||||||
"gevent-websocket",
|
"gevent-websocket",
|
||||||
"bottle",
|
"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 = [
|
test_deps = [
|
||||||
|
|
|
@ -188,7 +188,7 @@ class TestAuthAPI:
|
||||||
class TestAuthCLI:
|
class TestAuthCLI:
|
||||||
def test_login(self, moulinette_cli, capsys, mocker):
|
def test_login(self, moulinette_cli, capsys, mocker):
|
||||||
mocker.patch("os.isatty", return_value=True)
|
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")
|
moulinette_cli.run(["testauth", "default"], output_as="plain")
|
||||||
message = capsys.readouterr()
|
message = capsys.readouterr()
|
||||||
|
|
||||||
|
@ -201,25 +201,25 @@ class TestAuthCLI:
|
||||||
|
|
||||||
def test_login_bad_password(self, moulinette_cli, capsys, mocker):
|
def test_login_bad_password(self, moulinette_cli, capsys, mocker):
|
||||||
mocker.patch("os.isatty", return_value=True)
|
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):
|
with pytest.raises(MoulinetteError):
|
||||||
moulinette_cli.run(["testauth", "default"], output_as="plain")
|
moulinette_cli.run(["testauth", "default"], output_as="plain")
|
||||||
|
|
||||||
mocker.patch("os.isatty", return_value=True)
|
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):
|
with pytest.raises(MoulinetteError):
|
||||||
moulinette_cli.run(["testauth", "default"], output_as="plain")
|
moulinette_cli.run(["testauth", "default"], output_as="plain")
|
||||||
|
|
||||||
def test_login_wrong_profile(self, moulinette_cli, mocker):
|
def test_login_wrong_profile(self, moulinette_cli, mocker):
|
||||||
mocker.patch("os.isatty", return_value=True)
|
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:
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
moulinette_cli.run(["testauth", "other-profile"], output_as="none")
|
moulinette_cli.run(["testauth", "other-profile"], output_as="none")
|
||||||
|
|
||||||
assert "invalid_password" in str(exception)
|
assert "invalid_password" in str(exception)
|
||||||
|
|
||||||
mocker.patch("os.isatty", return_value=True)
|
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:
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
moulinette_cli.run(["testauth", "default"], output_as="none")
|
moulinette_cli.run(["testauth", "default"], output_as="none")
|
||||||
|
|
||||||
|
@ -239,7 +239,7 @@ class TestAuthCLI:
|
||||||
|
|
||||||
def test_request_only_cli(self, capsys, moulinette_cli, mocker):
|
def test_request_only_cli(self, capsys, moulinette_cli, mocker):
|
||||||
mocker.patch("os.isatty", return_value=True)
|
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")
|
moulinette_cli.run(["testauth", "only-cli"], output_as="plain")
|
||||||
|
|
||||||
message = capsys.readouterr()
|
message = capsys.readouterr()
|
||||||
|
@ -248,7 +248,7 @@ class TestAuthCLI:
|
||||||
|
|
||||||
def test_request_not_logged_only_cli(self, capsys, moulinette_cli, mocker):
|
def test_request_not_logged_only_cli(self, capsys, moulinette_cli, mocker):
|
||||||
mocker.patch("os.isatty", return_value=True)
|
mocker.patch("os.isatty", return_value=True)
|
||||||
mocker.patch("getpass.getpass")
|
mocker.patch("prompt_toolkit.prompt")
|
||||||
with pytest.raises(MoulinetteError) as exception:
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
moulinette_cli.run(["testauth", "only-cli"], output_as="plain")
|
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):
|
def test_request_with_callback(self, moulinette_cli, capsys, mocker):
|
||||||
mocker.patch("os.isatty", return_value=True)
|
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")
|
moulinette_cli.run(["--version"], output_as="plain")
|
||||||
message = capsys.readouterr()
|
message = capsys.readouterr()
|
||||||
|
|
||||||
|
@ -278,7 +278,7 @@ class TestAuthCLI:
|
||||||
|
|
||||||
def test_request_with_arg(self, moulinette_cli, capsys, mocker):
|
def test_request_with_arg(self, moulinette_cli, capsys, mocker):
|
||||||
mocker.patch("os.isatty", return_value=True)
|
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")
|
moulinette_cli.run(["testauth", "with_arg", "yoloswag"], output_as="plain")
|
||||||
message = capsys.readouterr()
|
message = capsys.readouterr()
|
||||||
|
|
||||||
|
@ -286,7 +286,7 @@ class TestAuthCLI:
|
||||||
|
|
||||||
def test_request_arg_with_extra(self, moulinette_cli, capsys, mocker):
|
def test_request_arg_with_extra(self, moulinette_cli, capsys, mocker):
|
||||||
mocker.patch("os.isatty", return_value=True)
|
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(
|
moulinette_cli.run(
|
||||||
["testauth", "with_extra_str_only", "YoLoSwAg"], output_as="plain"
|
["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):
|
def test_request_arg_with_type(self, moulinette_cli, capsys, mocker):
|
||||||
mocker.patch("os.isatty", return_value=True)
|
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")
|
moulinette_cli.run(["testauth", "with_type_int", "12345"], output_as="plain")
|
||||||
message = capsys.readouterr()
|
message = capsys.readouterr()
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue