From bba92b49e7eef9a07b2a11635d06c855ba9e9bf5 Mon Sep 17 00:00:00 2001 From: "Vinicius D. Cerutti" <51954708+viniciusdc@users.noreply.github.com> Date: Thu, 11 Jan 2024 00:50:51 -0300 Subject: [PATCH] Adds the `description` and `show_description` options to `Choice` and `InquirerControl` (#330) * add description and show_description options * show description by default, but only if set * add docs * add test * add show_description to checkbox * add more docs * trigger CI * fix newline removal logic --------- Co-authored-by: Philip Meier --- questionary/prompts/checkbox.py | 9 +++++++- questionary/prompts/common.py | 25 ++++++++++++++++++--- questionary/prompts/select.py | 4 ++++ tests/prompts/test_common.py | 40 +++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/questionary/prompts/checkbox.py b/questionary/prompts/checkbox.py index 95234652..25161e42 100644 --- a/questionary/prompts/checkbox.py +++ b/questionary/prompts/checkbox.py @@ -38,6 +38,7 @@ def checkbox( use_jk_keys: bool = True, use_emacs_keys: bool = True, instruction: Optional[str] = None, + show_description: bool = True, **kwargs: Any, ) -> Question: """Ask the user to select from a list of items. @@ -106,6 +107,8 @@ def checkbox( `Ctrl+N` (down) and `Ctrl+P` (up) keys. instruction: A message describing how to navigate the menu. + show_description: Display description of current selection if available. + Returns: :class:`Question`: Question instance, ready to be prompted (using ``.ask()``). """ @@ -130,7 +133,11 @@ def checkbox( raise ValueError("validate must be callable") ic = InquirerControl( - choices, default, pointer=pointer, initial_choice=initial_choice + choices, + default, + pointer=pointer, + initial_choice=initial_choice, + show_description=show_description, ) def get_prompt_tokens() -> List[Tuple[str, str]]: diff --git a/questionary/prompts/common.py b/questionary/prompts/common.py index e682f482..827629db 100644 --- a/questionary/prompts/common.py +++ b/questionary/prompts/common.py @@ -53,6 +53,8 @@ class Choice: checked: Preselect this choice when displaying the options. shortcut_key: Key shortcut used to select this item. + + description: Optional description of the item that can be displayed. """ title: FormattedText @@ -70,6 +72,9 @@ class Choice: shortcut_key: Optional[str] """A shortcut key for the choice""" + description: Optional[str] + """Choice description""" + def __init__( self, title: FormattedText, @@ -77,10 +82,12 @@ def __init__( disabled: Optional[str] = None, checked: Optional[bool] = False, shortcut_key: Optional[Union[str, bool]] = True, + description: Optional[str] = None, ) -> None: self.disabled = disabled self.title = title self.checked = checked if checked is not None else False + self.description = description if value is not None: self.value = value @@ -124,6 +131,7 @@ def build(c: Union[str, "Choice", Dict[str, Any]]) -> "Choice": c.get("disabled", None), c.get("checked"), c.get("key"), + c.get("description", None), ) def get_shortcut_title(self): @@ -202,6 +210,7 @@ class InquirerControl(FormattedTextControl): pointer: Optional[str] pointed_at: int is_answered: bool + show_description: bool def __init__( self, @@ -211,6 +220,7 @@ def __init__( use_indicator: bool = True, use_shortcuts: bool = False, show_selected: bool = False, + show_description: bool = True, use_arrow_keys: bool = True, initial_choice: Optional[Union[str, Choice, Dict[str, Any]]] = None, **kwargs: Any, @@ -218,6 +228,7 @@ def __init__( self.use_indicator = use_indicator self.use_shortcuts = use_shortcuts self.show_selected = show_selected + self.show_description = show_description self.use_arrow_keys = use_arrow_keys self.default = default self.pointer = pointer @@ -417,9 +428,9 @@ def append(index: int, choice: Choice): for i, c in enumerate(self.choices): append(i, c) - if self.show_selected: - current = self.get_pointed_at() + current = self.get_pointed_at() + if self.show_selected: answer = current.get_shortcut_title() if self.use_shortcuts else "" answer += ( @@ -427,8 +438,16 @@ def append(index: int, choice: Choice): ) tokens.append(("class:text", " Answer: {}".format(answer))) - else: + + show_description = self.show_description and current.description is not None + if show_description: + tokens.append( + ("class:text", " Description: {}".format(current.description)) + ) + + if not (self.show_selected or show_description): tokens.pop() # Remove last newline. + return tokens def is_selection_a_separator(self) -> bool: diff --git a/questionary/prompts/select.py b/questionary/prompts/select.py index 042d9f7d..402acbf6 100644 --- a/questionary/prompts/select.py +++ b/questionary/prompts/select.py @@ -35,6 +35,7 @@ def select( use_jk_keys: bool = True, use_emacs_keys: bool = True, show_selected: bool = False, + show_description: bool = True, instruction: Optional[str] = None, **kwargs: Any, ) -> Question: @@ -110,6 +111,8 @@ def select( show_selected: Display current selection choice at the bottom of list. + show_description: Display description of current selection if available. + Returns: :class:`Question`: Question instance, ready to be prompted (using ``.ask()``). """ @@ -150,6 +153,7 @@ def select( use_indicator=use_indicator, use_shortcuts=use_shortcuts, show_selected=show_selected, + show_description=show_description, use_arrow_keys=use_arrow_keys, initial_choice=default, ) diff --git a/tests/prompts/test_common.py b/tests/prompts/test_common.py index d6ce51e2..3250079a 100644 --- a/tests/prompts/test_common.py +++ b/tests/prompts/test_common.py @@ -222,3 +222,43 @@ def test_print_with_style(monkeypatch): assert mock.method_calls[1][0] == "write" assert mock.method_calls[1][1][0] == "Hello World" + + +def test_prompt_show_description(): + ic = InquirerControl( + ["a", Choice("b", description="B")], + show_selected=True, + show_description=True, + ) + + expected_tokens = [ + ("class:pointer", " » "), + ("[SetCursorPosition]", ""), + ("class:text", "○ "), + ("class:highlighted", "a"), + ("", "\n"), + ("class:text", " "), + ("class:text", "○ "), + ("class:text", "b"), + ("", "\n"), + ("class:text", " Answer: a"), + ] + assert ic.pointed_at == 0 + assert ic._get_choice_tokens() == expected_tokens + + ic.select_next() + expected_tokens = [ + ("class:text", " "), + ("class:text", "○ "), + ("class:text", "a"), + ("", "\n"), + ("class:pointer", " » "), + ("[SetCursorPosition]", ""), + ("class:text", "○ "), + ("class:highlighted", "b"), + ("", "\n"), + ("class:text", " Answer: b"), + ("class:text", " Description: B"), + ] + assert ic.pointed_at == 1 + assert ic._get_choice_tokens() == expected_tokens