Skip to content

Add KeyedList question type along with render, configuration, and tests. #165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,38 @@ List questions can take one extra argument :code:`carousel=False`. If set to tru
|inquirer list|


Keyed List
----

Shows a list of choices, and allows the selection of one of them using a corresponding key.

Example:

.. code:: python


import inquirer
questions = [
inquirer.KeyedList('size',
message="What size do you need?",
choices=['Jumbo', 'Large', 'Standard', 'Medium', 'Small', 'Micro'],
),
]
answers = inquirer.prompt(questions)

Keyed List choices use the first letter (lower case) or number of the provided choice by default. If multiple choices
share the same key, repeated presses of the same key will cycle through the matching entries.
Keyed Lists are otherwise similar to List but have one more additional argument,
:code:`auto_confirm=False`. If set to True, a key press with a matching entry will cause that entry to be instantly selected.

Keys can explicitly be set by providing the label, value and key as a tuple:
:code:`choices=[('Jumbo', 'Jumbo', 'u'), ('Large', 'Large', 'a'), ...]`



|inquirer list|


Checkbox
--------

Expand Down
144 changes: 137 additions & 7 deletions poetry.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ python = ">=3.7"
blessed = ">=1.19.0"
readchar = ">=2.0.1"
python-editor = ">=1.0.4"
pytest = "^7.1.2"

[tool.poetry.dev-dependencies]
pexpect = ">=4.8.0"
Expand Down
37 changes: 36 additions & 1 deletion src/inquirer/questions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import errno
import json
import os
import re
import sys

import inquirer.errors as errors
Expand All @@ -29,6 +30,25 @@ def __ne__(self, other):
return not self.__eq__(other)


class KeyedValue(TaggedValue):
def __init__(self, label, value=None, key=None):
self.label = label
self.value = value or label
self.key = key or self._get_key(label)

def _get_key(self, label):
_label = str(label)
k = re.search(r'([a-zA-Z\d])', _label)
if k:
return str(k.group(0)).lower()
return _label[0]

def __str__(self):
return str(self.label)

def __repr__(self):
return str(self.value)

class Question:
kind = "base question"

Expand Down Expand Up @@ -118,6 +138,21 @@ def __init__(self, name, message="", choices=None, default=None, ignore=False, v
self.carousel = carousel


class KeyedList(Question):
kind = "keyed_list"

def __init__(self, name, message="", choices=None, default=None, ignore=False, validate=True, carousel=False,auto_confirm=False):
super().__init__(name, message, choices, default, ignore, validate)
self.carousel = carousel
self.auto_confirm = auto_confirm # Auto Confirm selection on keypress


@property
def choices_generator(self):
for choice in self._solve(self._choices):
yield (KeyedValue(*choice) if isinstance(choice, (list, tuple, set)) and len(choice) >= 2 else KeyedValue(choice))


class Checkbox(Question):
kind = "checkbox"

Expand Down Expand Up @@ -227,7 +262,7 @@ def normalize_value(self, value):


def question_factory(kind, *args, **kwargs):
for cl in (Text, Editor, Password, Confirm, List, Checkbox, Path):
for cl in (Text, Editor, Password, Confirm, List, KeyedList, Checkbox, Path):
if cl.kind == kind:
return cl(*args, **kwargs)
raise errors.UnknownQuestionTypeError()
Expand Down
2 changes: 2 additions & 0 deletions src/inquirer/render/console/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from inquirer.render.console._password import Password
from inquirer.render.console._confirm import Confirm
from inquirer.render.console._list import List
from inquirer.render.console._keyed_list import KeyedList
from inquirer.render.console._checkbox import Checkbox
from inquirer.render.console._path import Path

Expand Down Expand Up @@ -146,6 +147,7 @@ def render_factory(self, question_type):
"password": Password,
"confirm": Confirm,
"list": List,
"keyed_list": KeyedList,
"checkbox": Checkbox,
"path": Path,
}
Expand Down
34 changes: 34 additions & 0 deletions src/inquirer/render/console/_keyed_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from readchar import key

from inquirer import errors
from inquirer.render.console.base import MAX_OPTIONS_DISPLAYED_AT_ONCE
from inquirer.render.console.base import BaseConsoleRender
from inquirer.render.console.base import half_options
from inquirer.render.console._list import List


class KeyedList(List):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.current = self._current_index()

def process_input(self, pressed):
super().process_input(pressed)

keys = [choice.key for choice in self.question.choices]

if pressed in keys:
self.current = self.get_next(keys, pressed)

if self.question.auto_confirm:
value = self.question.choices[self.current]

raise errors.EndOfInput(getattr(value, "value", value))

def get_next(self, keys, pressed):
try:
# Multiple entries with the same key? Get the 'next' one.
return keys.index(pressed, self.current + 1)
except ValueError:
# There isn't a next one, so get the first.
return keys.index(pressed)
6 changes: 6 additions & 0 deletions src/inquirer/shortcuts.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,9 @@ def path(message, render=None, **kwargs):
render = render or ConsoleRender()
question = questions.Path(name="", message=message, **kwargs)
return render.render(question)


def keyed_list_input(message, render=None, **kwargs):
render = render or ConsoleRender()
question = questions.KeyedList(name="", message=message, **kwargs)
return render.render(question)
Loading