Skip to content

Commit 35e9198

Browse files
authored
Merge pull request #433 from rizkyarlin/checkbox-hints
Add hints support for checkboxes
2 parents 0bd5fc8 + d0155e5 commit 35e9198

File tree

11 files changed

+186
-22
lines changed

11 files changed

+186
-22
lines changed

docs/usage.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,22 @@ If any of the list values is a pair, it should be a tuple like: `(label, value)`
117117

118118
As before, the `answers` is a `dict` containing the previous answers.
119119

120+
### hints
121+
122+
**Optional** for `Checkbox` and `List` questions; the rest of them do not have hints.
123+
124+
The hint for the selected choice will be shown above the first choice.
125+
126+
```python
127+
from inquirer import questions
128+
choices = {
129+
"foo": "Foo",
130+
"bar": "Bar",
131+
"bazz": "Bazz",
132+
}
133+
question = questions.Checkbox("foo", "Choose one:", choices=choices.keys(), hints=choices)
134+
```
135+
120136
### validate
121137

122138
Optional attribute that allows the program to check if the answer is valid or not. It requires a `boolean` value or a `function` with the signature:

examples/checkbox_hints.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from pprint import pprint
2+
3+
import inquirer # noqa
4+
5+
6+
choices_hints = {
7+
("Computers", "c"): "The really Geeky stuff",
8+
("Books", "b"): "Its just so cosy",
9+
("Science", "s"): "I want to know it all",
10+
("Nature", "n"): "Always outdoors",
11+
}
12+
13+
questions = [
14+
inquirer.Checkbox(
15+
"interests", message="What are you interested in?", choices=choices_hints.keys(), hints=choices_hints
16+
),
17+
]
18+
19+
answers = inquirer.prompt(questions)
20+
21+
pprint(answers)

examples/list_hints.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from pprint import pprint
2+
3+
import inquirer # noqa
4+
5+
choices_hints = {
6+
"Jumbo": "The biggest one we have",
7+
"Large": "If you need the extra kick",
8+
"Standard": "For your every day use",
9+
}
10+
11+
questions = [
12+
inquirer.List("size", message="What size do you need?", choices=choices_hints.keys(), hints=choices_hints),
13+
]
14+
15+
answers = inquirer.prompt(questions)
16+
17+
pprint(answers)

src/inquirer/questions.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,30 @@
1111

1212

1313
class TaggedValue:
14-
def __init__(self, label, value):
15-
self.label = label
16-
self.value = value
14+
def __init__(self, choice):
15+
self.label = choice[0]
16+
self.tag = choice[1]
17+
self._hash = hash(choice)
1718

1819
def __str__(self):
1920
return self.label
2021

2122
def __repr__(self):
22-
return self.value
23+
return repr(self.tag)
2324

2425
def __eq__(self, other):
2526
if isinstance(other, TaggedValue):
26-
return self.value == other.value
27-
return self.value == other
27+
return other.tag == self.tag
28+
if isinstance(other, tuple):
29+
return other == (self.label, self.tag)
30+
return other == self.tag
2831

2932
def __ne__(self, other):
3033
return not self.__eq__(other)
3134

35+
def __hash__(self) -> int:
36+
return self._hash
37+
3238

3339
class Question:
3440
kind = "base question"
@@ -42,6 +48,7 @@ def __init__(
4248
ignore=False,
4349
validate=True,
4450
show_default=False,
51+
hints=None,
4552
other=False,
4653
):
4754
self.name = name
@@ -52,6 +59,7 @@ def __init__(
5259
self._validate = validate
5360
self.answers = {}
5461
self.show_default = show_default
62+
self.hints = hints or {}
5563
self._other = other
5664

5765
if self._other:
@@ -84,7 +92,7 @@ def default(self):
8492
@property
8593
def choices_generator(self):
8694
for choice in self._solve(self._choices):
87-
yield (TaggedValue(*choice) if isinstance(choice, tuple) and len(choice) == 2 else choice)
95+
yield (TaggedValue(choice) if isinstance(choice, tuple) and len(choice) == 2 else choice)
8896

8997
@property
9098
def choices(self):
@@ -143,14 +151,15 @@ def __init__(
143151
name,
144152
message="",
145153
choices=None,
154+
hints=None,
146155
default=None,
147156
ignore=False,
148157
validate=True,
149158
carousel=False,
150159
other=False,
151160
autocomplete=None,
152161
):
153-
super().__init__(name, message, choices, default, ignore, validate, other=other)
162+
super().__init__(name, message, choices, default, ignore, validate, hints=hints, other=other)
154163
self.carousel = carousel
155164
self.autocomplete = autocomplete
156165

@@ -163,6 +172,7 @@ def __init__(
163172
name,
164173
message="",
165174
choices=None,
175+
hints=None,
166176
locked=None,
167177
default=None,
168178
ignore=False,
@@ -171,7 +181,7 @@ def __init__(
171181
other=False,
172182
autocomplete=None,
173183
):
174-
super().__init__(name, message, choices, default, ignore, validate, other=other)
184+
super().__init__(name, message, choices, default, ignore, validate, hints=hints, other=other)
175185
self.locked = locked
176186
self.carousel = carousel
177187
self.autocomplete = autocomplete

src/inquirer/render/console/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def _event_loop(self, render):
4646
self._print_status_bar(render)
4747

4848
self._print_header(render)
49+
self._print_hint(render)
4950
self._print_options(render)
5051

5152
self._process_input(render)
@@ -90,6 +91,15 @@ def _print_header(self, render):
9091
tq=self._theme.Question,
9192
)
9293

94+
def _print_hint(self, render):
95+
msg_template = "{t.move_up}{t.clear_eol}{color}{msg}"
96+
hint = render.get_hint()
97+
color = self._theme.Question.mark_color
98+
if hint:
99+
self.print_str(
100+
f"\n{msg_template}", msg=hint, color=color, lf=not render.title_inline, tq=self._theme.Question
101+
)
102+
93103
def _process_input(self, render):
94104
try:
95105
ev = self._event_gen.next()

src/inquirer/render/console/_checkbox.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ def __init__(self, *args, **kwargs):
1414
self.selection = [k for (k, v) in enumerate(self.question.choices) if v in self.default_choices()]
1515
self.current = 0
1616

17+
def get_hint(self):
18+
try:
19+
hint = self.question.hints[self.question.choices[self.current]]
20+
return hint or ""
21+
except KeyError:
22+
return ""
23+
1724
def default_choices(self):
1825
default = self.question.default or []
1926
return default + self.locked

src/inquirer/render/console/_list.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@ def is_long(self):
1717
choices = self.question.choices or []
1818
return len(choices) >= MAX_OPTIONS_DISPLAYED_AT_ONCE
1919

20+
def get_hint(self):
21+
try:
22+
choice = self.question.choices[self.current]
23+
hint = self.question.hints[choice]
24+
if hint:
25+
return f"{choice}: {hint}"
26+
else:
27+
return f"{choice}"
28+
except (KeyError, IndexError):
29+
return ""
30+
2031
def get_options(self):
2132
choices = self.question.choices or []
2233
if self.is_long:
@@ -87,9 +98,3 @@ def _current_index(self):
8798
return self.question.choices.index(self.question.default)
8899
except ValueError:
89100
return 0
90-
91-
def get_current_value(self):
92-
try:
93-
return self.question.choices[self.current]
94-
except IndexError:
95-
return ""

src/inquirer/render/console/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ def other_input(self):
2626
def get_header(self):
2727
return self.question.message
2828

29+
def get_hint(self):
30+
return ""
31+
2932
def get_current_value(self):
3033
return ""
3134

tests/integration/console_render/test_checkbox.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,3 +394,37 @@ def test_locked_with_default(self):
394394
result = sut.render(question)
395395

396396
assert result == ["bar"]
397+
398+
def test_first_hint_is_shown(self):
399+
stdin = helper.event_factory(key.ENTER)
400+
message = "Foo message"
401+
variable = "Bar variable"
402+
choices = {
403+
"foo": "Foo",
404+
"bar": "Bar",
405+
"bazz": "Bazz",
406+
}
407+
408+
question = questions.Checkbox(variable, message, choices=choices.keys(), hints=choices)
409+
410+
sut = ConsoleRender(event_generator=stdin)
411+
sut.render(question)
412+
413+
self.assertInStdout("Foo")
414+
415+
def test_second_hint_is_shown(self):
416+
stdin = helper.event_factory(key.DOWN, key.ENTER)
417+
message = "Foo message"
418+
variable = "Bar variable"
419+
choices = {
420+
"foo": "Foo",
421+
"bar": "Bar",
422+
"bazz": "Bazz",
423+
}
424+
425+
question = questions.Checkbox(variable, message, choices=choices.keys(), hints=choices)
426+
427+
sut = ConsoleRender(event_generator=stdin)
428+
sut.render(question)
429+
430+
self.assertInStdout("Bar")

tests/integration/console_render/test_list.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,37 @@ def test_ctrl_c_breaks_execution(self):
131131
sut = ConsoleRender(event_generator=stdin)
132132
with pytest.raises(KeyboardInterrupt):
133133
sut.render(question)
134+
135+
def test_first_hint_is_shown(self):
136+
stdin = helper.event_factory(key.ENTER)
137+
message = "Foo message"
138+
variable = "Bar variable"
139+
choices = {
140+
"foo": "Foo",
141+
"bar": "Bar",
142+
"bazz": "Bazz",
143+
}
144+
145+
question = questions.List(variable, message, choices=choices.keys(), hints=choices)
146+
147+
sut = ConsoleRender(event_generator=stdin)
148+
sut.render(question)
149+
150+
self.assertInStdout("Foo")
151+
152+
def test_second_hint_is_shown(self):
153+
stdin = helper.event_factory(key.DOWN, key.ENTER)
154+
message = "Foo message"
155+
variable = "Bar variable"
156+
choices = {
157+
"foo": "Foo",
158+
"bar": "Bar",
159+
"bazz": "Bazz",
160+
}
161+
162+
question = questions.List(variable, message, choices=choices.keys(), hints=choices)
163+
164+
sut = ConsoleRender(event_generator=stdin)
165+
sut.render(question)
166+
167+
self.assertInStdout("Bar")

0 commit comments

Comments
 (0)