form: rename Questions to Options

This commit is contained in:
axolotle 2023-04-07 16:25:48 +02:00
parent 9a585f03c6
commit f9fd379997
6 changed files with 143 additions and 143 deletions

View file

@ -50,8 +50,8 @@ from moulinette.utils.filesystem import (
from yunohost.utils.configpanel import ConfigPanel, ask_questions_and_parse_answers
from yunohost.utils.form import (
DomainQuestion,
PathQuestion,
DomainOption,
WebPathOption,
hydrate_questions_with_choices,
)
from yunohost.utils.i18n import _value_for_locale
@ -430,10 +430,10 @@ def app_change_url(operation_logger, app, domain, path):
# Normalize path and domain format
domain = DomainQuestion.normalize(domain)
old_domain = DomainQuestion.normalize(old_domain)
path = PathQuestion.normalize(path)
old_path = PathQuestion.normalize(old_path)
domain = DomainOption.normalize(domain)
old_domain = DomainOption.normalize(old_domain)
path = WebPathOption.normalize(path)
old_path = WebPathOption.normalize(old_path)
if (domain, path) == (old_domain, old_path):
raise YunohostValidationError(
@ -1660,8 +1660,8 @@ def app_register_url(app, domain, path):
permission_sync_to_user,
)
domain = DomainQuestion.normalize(domain)
path = PathQuestion.normalize(path)
domain = DomainOption.normalize(domain)
path = WebPathOption.normalize(path)
# We cannot change the url of an app already installed simply by changing
# the settings...
@ -2853,8 +2853,8 @@ def _get_conflicting_apps(domain, path, ignore_app=None):
from yunohost.domain import _assert_domain_exists
domain = DomainQuestion.normalize(domain)
path = PathQuestion.normalize(path)
domain = DomainOption.normalize(domain)
path = WebPathOption.normalize(path)
# Abort if domain is unknown
_assert_domain_exists(domain)

View file

@ -34,7 +34,7 @@ from yunohost.app import (
)
from yunohost.regenconf import regen_conf, _force_clear_hashes, _process_regen_conf
from yunohost.utils.configpanel import ConfigPanel
from yunohost.utils.form import Question
from yunohost.utils.form import BaseOption
from yunohost.utils.error import YunohostError, YunohostValidationError
from yunohost.log import is_unit_operation
@ -528,7 +528,7 @@ def domain_config_set(
"""
Apply a new domain configuration
"""
Question.operation_logger = operation_logger
BaseOption.operation_logger = operation_logger
config = DomainConfigPanel(domain)
return config.set(key, value, args, args_file, operation_logger=operation_logger)

View file

@ -22,7 +22,7 @@ import subprocess
from moulinette import m18n
from yunohost.utils.error import YunohostError, YunohostValidationError
from yunohost.utils.configpanel import ConfigPanel
from yunohost.utils.form import Question
from yunohost.utils.form import BaseOption
from moulinette.utils.log import getActionLogger
from yunohost.regenconf import regen_conf
from yunohost.firewall import firewall_reload
@ -82,7 +82,7 @@ def settings_set(operation_logger, key=None, value=None, args=None, args_file=No
value -- New value
"""
Question.operation_logger = operation_logger
BaseOption.operation_logger = operation_logger
settings = SettingsConfigPanel()
key = translate_legacy_settings_to_configpanel_settings(key)
return settings.set(key, value, args, args_file, operation_logger=operation_logger)
@ -231,7 +231,7 @@ class SettingsConfigPanel(ConfigPanel):
# Replace all values with default values
self.values = self._get_default_values()
Question.operation_logger = operation_logger
BaseOption.operation_logger = operation_logger
if operation_logger:
operation_logger.start()

View file

@ -17,12 +17,12 @@ from yunohost import app, domain, user
from yunohost.utils.form import (
ARGUMENTS_TYPE_PARSERS,
ask_questions_and_parse_answers,
DisplayTextQuestion,
PasswordQuestion,
DomainQuestion,
PathQuestion,
BooleanQuestion,
FileQuestion,
DisplayTextOption,
PasswordOption,
DomainOption,
WebPathOption,
BooleanOption,
FileOption,
evaluate_simple_js_expression,
)
from yunohost.utils.error import YunohostError, YunohostValidationError
@ -438,7 +438,7 @@ class BaseTest:
id_ = raw_option["id"]
option, value = _fill_or_prompt_one_option(raw_option, None)
is_special_readonly_option = isinstance(option, DisplayTextQuestion)
is_special_readonly_option = isinstance(option, DisplayTextOption)
assert isinstance(option, ARGUMENTS_TYPE_PARSERS[raw_option["type"]])
assert option.type == raw_option["type"]
@ -734,7 +734,7 @@ class TestPassword(BaseTest):
], reason="Should output exactly the same"),
("s3cr3t!!", "s3cr3t!!"),
("secret", FAIL),
*[("supersecret" + char, FAIL) for char in PasswordQuestion.forbidden_chars], # FIXME maybe add ` \n` to the list?
*[("supersecret" + char, FAIL) for char in PasswordOption.forbidden_chars], # FIXME maybe add ` \n` to the list?
# readonly
*xpass(scenarios=[
("s3cr3t!!", "s3cr3t!!", {"readonly": True}),
@ -1225,9 +1225,9 @@ class TestUrl(BaseTest):
@pytest.fixture
def file_clean():
FileQuestion.clean_upload_dirs()
FileOption.clean_upload_dirs()
yield
FileQuestion.clean_upload_dirs()
FileOption.clean_upload_dirs()
@contextmanager
@ -1263,7 +1263,7 @@ def _test_file_intake_may_fail(raw_option, intake, expected_output):
with open(value) as f:
assert f.read() == expected_output
FileQuestion.clean_upload_dirs()
FileOption.clean_upload_dirs()
assert not os.path.exists(value)
@ -2138,88 +2138,88 @@ def test_question_number_input_test_ask_with_example():
def test_normalize_boolean_nominal():
assert BooleanQuestion.normalize("yes") == 1
assert BooleanQuestion.normalize("Yes") == 1
assert BooleanQuestion.normalize(" yes ") == 1
assert BooleanQuestion.normalize("y") == 1
assert BooleanQuestion.normalize("true") == 1
assert BooleanQuestion.normalize("True") == 1
assert BooleanQuestion.normalize("on") == 1
assert BooleanQuestion.normalize("1") == 1
assert BooleanQuestion.normalize(1) == 1
assert BooleanOption.normalize("yes") == 1
assert BooleanOption.normalize("Yes") == 1
assert BooleanOption.normalize(" yes ") == 1
assert BooleanOption.normalize("y") == 1
assert BooleanOption.normalize("true") == 1
assert BooleanOption.normalize("True") == 1
assert BooleanOption.normalize("on") == 1
assert BooleanOption.normalize("1") == 1
assert BooleanOption.normalize(1) == 1
assert BooleanQuestion.normalize("no") == 0
assert BooleanQuestion.normalize("No") == 0
assert BooleanQuestion.normalize(" no ") == 0
assert BooleanQuestion.normalize("n") == 0
assert BooleanQuestion.normalize("false") == 0
assert BooleanQuestion.normalize("False") == 0
assert BooleanQuestion.normalize("off") == 0
assert BooleanQuestion.normalize("0") == 0
assert BooleanQuestion.normalize(0) == 0
assert BooleanOption.normalize("no") == 0
assert BooleanOption.normalize("No") == 0
assert BooleanOption.normalize(" no ") == 0
assert BooleanOption.normalize("n") == 0
assert BooleanOption.normalize("false") == 0
assert BooleanOption.normalize("False") == 0
assert BooleanOption.normalize("off") == 0
assert BooleanOption.normalize("0") == 0
assert BooleanOption.normalize(0) == 0
assert BooleanQuestion.normalize("") is None
assert BooleanQuestion.normalize(" ") is None
assert BooleanQuestion.normalize(" none ") is None
assert BooleanQuestion.normalize("None") is None
assert BooleanQuestion.normalize("noNe") is None
assert BooleanQuestion.normalize(None) is None
assert BooleanOption.normalize("") is None
assert BooleanOption.normalize(" ") is None
assert BooleanOption.normalize(" none ") is None
assert BooleanOption.normalize("None") is None
assert BooleanOption.normalize("noNe") is None
assert BooleanOption.normalize(None) is None
def test_normalize_boolean_humanize():
assert BooleanQuestion.humanize("yes") == "yes"
assert BooleanQuestion.humanize("true") == "yes"
assert BooleanQuestion.humanize("on") == "yes"
assert BooleanOption.humanize("yes") == "yes"
assert BooleanOption.humanize("true") == "yes"
assert BooleanOption.humanize("on") == "yes"
assert BooleanQuestion.humanize("no") == "no"
assert BooleanQuestion.humanize("false") == "no"
assert BooleanQuestion.humanize("off") == "no"
assert BooleanOption.humanize("no") == "no"
assert BooleanOption.humanize("false") == "no"
assert BooleanOption.humanize("off") == "no"
def test_normalize_boolean_invalid():
with pytest.raises(YunohostValidationError):
BooleanQuestion.normalize("yesno")
BooleanOption.normalize("yesno")
with pytest.raises(YunohostValidationError):
BooleanQuestion.normalize("foobar")
BooleanOption.normalize("foobar")
with pytest.raises(YunohostValidationError):
BooleanQuestion.normalize("enabled")
BooleanOption.normalize("enabled")
def test_normalize_boolean_special_yesno():
customyesno = {"yes": "enabled", "no": "disabled"}
assert BooleanQuestion.normalize("yes", customyesno) == "enabled"
assert BooleanQuestion.normalize("true", customyesno) == "enabled"
assert BooleanQuestion.normalize("enabled", customyesno) == "enabled"
assert BooleanQuestion.humanize("yes", customyesno) == "yes"
assert BooleanQuestion.humanize("true", customyesno) == "yes"
assert BooleanQuestion.humanize("enabled", customyesno) == "yes"
assert BooleanOption.normalize("yes", customyesno) == "enabled"
assert BooleanOption.normalize("true", customyesno) == "enabled"
assert BooleanOption.normalize("enabled", customyesno) == "enabled"
assert BooleanOption.humanize("yes", customyesno) == "yes"
assert BooleanOption.humanize("true", customyesno) == "yes"
assert BooleanOption.humanize("enabled", customyesno) == "yes"
assert BooleanQuestion.normalize("no", customyesno) == "disabled"
assert BooleanQuestion.normalize("false", customyesno) == "disabled"
assert BooleanQuestion.normalize("disabled", customyesno) == "disabled"
assert BooleanQuestion.humanize("no", customyesno) == "no"
assert BooleanQuestion.humanize("false", customyesno) == "no"
assert BooleanQuestion.humanize("disabled", customyesno) == "no"
assert BooleanOption.normalize("no", customyesno) == "disabled"
assert BooleanOption.normalize("false", customyesno) == "disabled"
assert BooleanOption.normalize("disabled", customyesno) == "disabled"
assert BooleanOption.humanize("no", customyesno) == "no"
assert BooleanOption.humanize("false", customyesno) == "no"
assert BooleanOption.humanize("disabled", customyesno) == "no"
def test_normalize_domain():
assert DomainQuestion.normalize("https://yolo.swag/") == "yolo.swag"
assert DomainQuestion.normalize("http://yolo.swag") == "yolo.swag"
assert DomainQuestion.normalize("yolo.swag/") == "yolo.swag"
assert DomainOption.normalize("https://yolo.swag/") == "yolo.swag"
assert DomainOption.normalize("http://yolo.swag") == "yolo.swag"
assert DomainOption.normalize("yolo.swag/") == "yolo.swag"
def test_normalize_path():
assert PathQuestion.normalize("") == "/"
assert PathQuestion.normalize("") == "/"
assert PathQuestion.normalize("macnuggets") == "/macnuggets"
assert PathQuestion.normalize("/macnuggets") == "/macnuggets"
assert PathQuestion.normalize(" /macnuggets ") == "/macnuggets"
assert PathQuestion.normalize("/macnuggets") == "/macnuggets"
assert PathQuestion.normalize("mac/nuggets") == "/mac/nuggets"
assert PathQuestion.normalize("/macnuggets/") == "/macnuggets"
assert PathQuestion.normalize("macnuggets/") == "/macnuggets"
assert PathQuestion.normalize("////macnuggets///") == "/macnuggets"
assert WebPathOption.normalize("") == "/"
assert WebPathOption.normalize("") == "/"
assert WebPathOption.normalize("macnuggets") == "/macnuggets"
assert WebPathOption.normalize("/macnuggets") == "/macnuggets"
assert WebPathOption.normalize(" /macnuggets ") == "/macnuggets"
assert WebPathOption.normalize("/macnuggets") == "/macnuggets"
assert WebPathOption.normalize("mac/nuggets") == "/mac/nuggets"
assert WebPathOption.normalize("/macnuggets/") == "/macnuggets"
assert WebPathOption.normalize("macnuggets/") == "/macnuggets"
assert WebPathOption.normalize("////macnuggets///") == "/macnuggets"
def test_simple_evaluate():

View file

@ -37,8 +37,8 @@ from yunohost.utils.i18n import _value_for_locale
from yunohost.utils.error import YunohostError, YunohostValidationError
from yunohost.utils.form import (
ARGUMENTS_TYPE_PARSERS,
FileQuestion,
Question,
FileOption,
BaseOption,
ask_questions_and_parse_answers,
evaluate_simple_js_expression,
)
@ -213,7 +213,7 @@ class ConfigPanel:
# Read or get values and hydrate the config
self._load_current_values()
self._hydrate()
Question.operation_logger = operation_logger
BaseOption.operation_logger = operation_logger
self._ask(action=action_id)
# FIXME: here, we could want to check constrains on
@ -244,7 +244,7 @@ class ConfigPanel:
# FIXME : this is currently done in the context of config panels,
# but could also happen in the context of app install ... (or anywhere else
# where we may parse args etc...)
FileQuestion.clean_upload_dirs()
FileOption.clean_upload_dirs()
# FIXME: i18n
logger.success(f"Action {action_id} successful")
@ -277,7 +277,7 @@ class ConfigPanel:
# Read or get values and hydrate the config
self._load_current_values()
self._hydrate()
Question.operation_logger = operation_logger
BaseOption.operation_logger = operation_logger
self._ask()
if operation_logger:
@ -305,7 +305,7 @@ class ConfigPanel:
# FIXME : this is currently done in the context of config panels,
# but could also happen in the context of app install ... (or anywhere else
# where we may parse args etc...)
FileQuestion.clean_upload_dirs()
FileOption.clean_upload_dirs()
self._reload_services()

View file

@ -183,7 +183,7 @@ def evaluate_simple_js_expression(expr, context={}):
return evaluate_simple_ast(node, context)
class Question:
class BaseOption:
hide_user_input_in_prompt = False
pattern: Optional[Dict] = None
@ -377,26 +377,26 @@ class Question:
return self.value
class StringQuestion(Question):
class StringOption(BaseOption):
argument_type = "string"
default_value = ""
class EmailQuestion(StringQuestion):
class EmailOption(StringOption):
pattern = {
"regexp": r"^.+@.+",
"error": "config_validate_email", # i18n: config_validate_email
}
class URLQuestion(StringQuestion):
class URLOption(StringOption):
pattern = {
"regexp": r"^https?://.*$",
"error": "config_validate_url", # i18n: config_validate_url
}
class DateQuestion(StringQuestion):
class DateOption(StringOption):
pattern = {
"regexp": r"^\d{4}-\d\d-\d\d$",
"error": "config_validate_date", # i18n: config_validate_date
@ -414,21 +414,21 @@ class DateQuestion(StringQuestion):
raise YunohostValidationError("config_validate_date")
class TimeQuestion(StringQuestion):
class TimeOption(StringOption):
pattern = {
"regexp": r"^(?:\d|[01]\d|2[0-3]):[0-5]\d$",
"error": "config_validate_time", # i18n: config_validate_time
}
class ColorQuestion(StringQuestion):
class ColorOption(StringOption):
pattern = {
"regexp": r"^#[ABCDEFabcdef\d]{3,6}$",
"error": "config_validate_color", # i18n: config_validate_color
}
class TagsQuestion(Question):
class TagsOption(BaseOption):
argument_type = "tags"
default_value = ""
@ -478,7 +478,7 @@ class TagsQuestion(Question):
return super()._post_parse_value()
class PasswordQuestion(Question):
class PasswordOption(BaseOption):
hide_user_input_in_prompt = True
argument_type = "password"
default_value = ""
@ -509,13 +509,13 @@ class PasswordQuestion(Question):
assert_password_is_strong_enough("user", self.value)
class PathQuestion(Question):
class WebPathOption(BaseOption):
argument_type = "path"
default_value = ""
@staticmethod
def normalize(value, option={}):
option = option.__dict__ if isinstance(option, Question) else option
option = option.__dict__ if isinstance(option, BaseOption) else option
if not isinstance(value, str):
raise YunohostValidationError(
@ -528,19 +528,19 @@ class PathQuestion(Question):
if option.get("optional"):
return ""
# Hmpf here we could just have a "else" case
# but we also want PathQuestion.normalize("") to return "/"
# but we also want WebPathOption.normalize("") to return "/"
# (i.e. if no option is provided, hence .get("optional") is None
elif option.get("optional") is False:
raise YunohostValidationError(
"app_argument_invalid",
name=option.get("name"),
error="Question is mandatory",
error="Option is mandatory",
)
return "/" + value.strip().strip(" /")
class BooleanQuestion(Question):
class BooleanOption(BaseOption):
argument_type = "boolean"
default_value = 0
yes_answers = ["1", "yes", "y", "true", "t", "on"]
@ -548,12 +548,12 @@ class BooleanQuestion(Question):
@staticmethod
def humanize(value, option={}):
option = option.__dict__ if isinstance(option, Question) else option
option = option.__dict__ if isinstance(option, BaseOption) else option
yes = option.get("yes", 1)
no = option.get("no", 0)
value = BooleanQuestion.normalize(value, option)
value = BooleanOption.normalize(value, option)
if value == yes:
return "yes"
@ -571,7 +571,7 @@ class BooleanQuestion(Question):
@staticmethod
def normalize(value, option={}):
option = option.__dict__ if isinstance(option, Question) else option
option = option.__dict__ if isinstance(option, BaseOption) else option
if isinstance(value, str):
value = value.strip()
@ -579,8 +579,8 @@ class BooleanQuestion(Question):
technical_yes = option.get("yes", 1)
technical_no = option.get("no", 0)
no_answers = BooleanQuestion.no_answers
yes_answers = BooleanQuestion.yes_answers
no_answers = BooleanOption.no_answers
yes_answers = BooleanOption.yes_answers
assert (
str(technical_yes).lower() not in no_answers
@ -630,7 +630,7 @@ class BooleanQuestion(Question):
return getattr(self, key, default)
class DomainQuestion(Question):
class DomainOption(BaseOption):
argument_type = "domain"
def __init__(
@ -661,7 +661,7 @@ class DomainQuestion(Question):
return value
class AppQuestion(Question):
class AppOption(BaseOption):
argument_type = "app"
def __init__(
@ -688,7 +688,7 @@ class AppQuestion(Question):
self.choices.update({app["id"]: _app_display(app) for app in apps})
class UserQuestion(Question):
class UserOption(BaseOption):
argument_type = "user"
def __init__(
@ -721,7 +721,7 @@ class UserQuestion(Question):
break
class GroupQuestion(Question):
class GroupOption(BaseOption):
argument_type = "group"
def __init__(
@ -747,7 +747,7 @@ class GroupQuestion(Question):
self.default = "all_users"
class NumberQuestion(Question):
class NumberOption(BaseOption):
argument_type = "number"
default_value = None
@ -773,7 +773,7 @@ class NumberQuestion(Question):
if value in [None, ""]:
return None
option = option.__dict__ if isinstance(option, Question) else option
option = option.__dict__ if isinstance(option, BaseOption) else option
raise YunohostValidationError(
"app_argument_invalid",
name=option.get("name"),
@ -800,7 +800,7 @@ class NumberQuestion(Question):
)
class DisplayTextQuestion(Question):
class DisplayTextOption(BaseOption):
argument_type = "display_text"
def __init__(
@ -830,7 +830,7 @@ class DisplayTextQuestion(Question):
return text
class FileQuestion(Question):
class FileOption(BaseOption):
argument_type = "file"
upload_dirs: List[str] = []
@ -876,7 +876,7 @@ class FileQuestion(Question):
upload_dir = tempfile.mkdtemp(prefix="ynh_filequestion_")
_, file_path = tempfile.mkstemp(dir=upload_dir)
FileQuestion.upload_dirs += [upload_dir]
FileOption.upload_dirs += [upload_dir]
logger.debug(f"Saving file {self.name} for file question into {file_path}")
@ -895,7 +895,7 @@ class FileQuestion(Question):
return self.value
class ButtonQuestion(Question):
class ButtonOption(BaseOption):
argument_type = "button"
enabled = None
@ -907,29 +907,29 @@ class ButtonQuestion(Question):
ARGUMENTS_TYPE_PARSERS = {
"string": StringQuestion,
"text": StringQuestion,
"select": StringQuestion,
"tags": TagsQuestion,
"email": EmailQuestion,
"url": URLQuestion,
"date": DateQuestion,
"time": TimeQuestion,
"color": ColorQuestion,
"password": PasswordQuestion,
"path": PathQuestion,
"boolean": BooleanQuestion,
"domain": DomainQuestion,
"user": UserQuestion,
"group": GroupQuestion,
"number": NumberQuestion,
"range": NumberQuestion,
"display_text": DisplayTextQuestion,
"alert": DisplayTextQuestion,
"markdown": DisplayTextQuestion,
"file": FileQuestion,
"app": AppQuestion,
"button": ButtonQuestion,
"string": StringOption,
"text": StringOption,
"select": StringOption,
"tags": TagsOption,
"email": EmailOption,
"url": URLOption,
"date": DateOption,
"time": TimeOption,
"color": ColorOption,
"password": PasswordOption,
"path": WebPathOption,
"boolean": BooleanOption,
"domain": DomainOption,
"user": UserOption,
"group": GroupOption,
"number": NumberOption,
"range": NumberOption,
"display_text": DisplayTextOption,
"alert": DisplayTextOption,
"markdown": DisplayTextOption,
"file": FileOption,
"app": AppOption,
"button": ButtonOption,
}
@ -938,7 +938,7 @@ def ask_questions_and_parse_answers(
prefilled_answers: Union[str, Mapping[str, Any]] = {},
current_values: Mapping[str, Any] = {},
hooks: Dict[str, Callable[[], None]] = {},
) -> List[Question]:
) -> List[BaseOption]:
"""Parse arguments store in either manifest.json or actions.json or from a
config panel against the user answers when they are present.