Skip to content
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

Add additional styling #14

Merged
merged 12 commits into from Aug 25, 2019
71 changes: 45 additions & 26 deletions README.md
Expand Up @@ -12,7 +12,7 @@ Python library to build pretty command line user prompts ✨

You need input from a user, e.g. how an output file should be named or if he really wants to execute that dangerous operation? This library will help you make the input prompts easy to read and answer for the user.

Used and Supported by:
Used and Supported by:

[<img src="https://rasa.com/docs/_static/rasa_logo.svg" width="60">](https://github.com/RasaHQ/rasa)

Expand Down Expand Up @@ -49,9 +49,9 @@ This will create the following list, allowing the user to choose an option:
### Different question types

<details><summary>text</summary>
A free text input for the user.

A free text input for the user.

```python
questionary.text("What's your first name").ask()
```
Expand All @@ -61,31 +61,31 @@ This will create the following list, allowing the user to choose an option:
<details><summary>password</summary>

A free text input for the user where the input is not
shown but replaced with `***`.
shown but replaced with `***`.

```python
questionary.password("What's your secret?").ask()
```

<img src="docs/images/password.png" width="500">

</details>
<details><summary>confirm</summary>

A yes or no question. The user can either confirm or deny.
A yes or no question. The user can either confirm or deny.

```python
questionary.confirm("Are you amazed?").ask()
```

<img src="docs/images/confirm.png" width="500">

</details>
<details><summary>select</summary>

A list of items to select a choice from. The user can pick
one option and confirm it.

```python
questionary.select(
"What do you want to do?",
Expand All @@ -95,7 +95,7 @@ This will create the following list, allowing the user to choose an option:
"Ask for opening hours"
]).ask()
```

<img src="docs/images/select.png" width="500">

</details>
Expand Down Expand Up @@ -140,8 +140,8 @@ This will create the following list, allowing the user to choose an option:
<details><summary>Skipping questions using conditions</summary>

Sometimes it is helpfull to e.g. provide a command line flag to your app
to skip any prompts, to avoid the need for an if around any question you
can pass that flag when you create the question:
to skip any prompts, to avoid the need for an if around any question you
can pass that flag when you create the question:

```python
DISABLED = True
Expand All @@ -150,13 +150,13 @@ response = questionary.confirm("Are you amazed?").skip_if(DISABLED, default=True
```

If the condition (in this case `DISABLED`) is `True`, the question will be
skipped and the default value gets returned, otherwise the user will be
skipped and the default value gets returned, otherwise the user will be
prompted as usual and the default value will be ignored.
</details>

<details><summary>Alterative style to create questions using a configuration dictionary</summary>

Instead of creating questions using the python functions, you can also create them using a configuration dictionary.
Instead of creating questions using the python functions, you can also create them using a configuration dictionary.
```python
questions = [
{
Expand Down Expand Up @@ -185,20 +185,39 @@ You can customize all the colors used for the prompts. Every part of the prompt
from prompt_toolkit.styles import Style

custom_style_fancy = Style([
('qmark', 'fg:#673ab7 bold'), # token in front of the question
('question', 'bold'), # question text
('answer', 'fg:#f44336 bold'), # submitted answer text behind the question
('pointer', 'fg:#673ab7 bold'), # pointer used in select and checkbox prompts
('selected', 'fg:#cc5454'), # style for a selected item of a checkbox
('separator', 'fg:#cc5454'), # separator in lists
('instruction', '') # user instructions for select, rawselect, checkbox
('qmark', 'fg:#673ab7 bold'), # token in front of the question
('question', 'bold'), # question text
('answer', 'fg:#f44336 bold'), # submitted answer text behind the question
('pointer', 'fg:#673ab7 bold'), # pointer used in select and checkbox prompts
('highlighted', 'fg:#673ab7 bold'), # pointed-at choice in select and checkbox prompts if use_pointer=False
('selected', 'fg:#cc5454'), # style for a selected item of a checkbox
('separator', 'fg:#cc5454'), # separator in lists
('instruction', ''), # user instructions for select, rawselect, checkbox
('text', ''), # plain text
('disabled', 'fg:#858585 italic') # disabled choices for select and checkbox prompts
])
```

To use our custom style, we need to pass it to the question type:
```python
questionary.text("What's your phone number", style=custom_style_fancy).ask()
```

It is also possible to use a list of token tuples as a `Choice` title. This
example assumes there is a style token named `bold` in the custom style you are
using:
```python
Choice(
title=[
('class:text', 'plain text '),
('class:bold', 'bold text')
]
)
```
As you can see it is possible to use custom style tokens for this purpose as
well. Note that Choices with token tuple titles will not be styled by the
`selected` or `highlighted` tokens. If not provided, the `value` of the Choice
will be the text concatenated (`'plain text bold text'` in the above example).
</details>

## How to Contribute
Expand All @@ -215,12 +234,12 @@ questionary.text("What's your phone number", style=custom_style_fancy).ask()
works as expected.
4. Send a pull request and bug the maintainer until it gets merged and
published. 🙂

## Contributors

`questionary` is written and maintained by Tom Bocklisch.
`questionary` is written and maintained by Tom Bocklisch.

It is based on the great work of [Oyetoke Toby](https://github.com/CITGuru/PyInquirer) as well as the work from [Mark Fink](https://github.com/finklabs/whaaaaat).
It is based on the great work of [Oyetoke Toby](https://github.com/CITGuru/PyInquirer) as well as the work from [Mark Fink](https://github.com/finklabs/whaaaaat).

## Changelog

Expand Down
3 changes: 3 additions & 0 deletions examples/__init__.py
Expand Up @@ -6,7 +6,10 @@
('question', ''),
('selected', 'fg:#cc5454'),
('pointer', 'fg:#673ab7 bold'),
('highlighted', 'fg:#673ab7 bold'),
('answer', 'fg:#f44336 bold'),
('text', ''),
('disabled', 'fg:#858585 italic'),
])

custom_style_dope = Style([
Expand Down
18 changes: 14 additions & 4 deletions questionary/prompts/checkbox.py
Expand Up @@ -25,6 +25,7 @@ def checkbox(message: Text,
default: Optional[Text] = None,
qmark: Text = DEFAULT_QUESTION_PREFIX,
style: Optional[Style] = None,
use_pointer: bool = True,
**kwargs: Any) -> Question:
"""Ask the user to select from a list of items.

Expand All @@ -48,13 +49,17 @@ def checkbox(message: Text,
style: A custom color and style for the question parts. You can
configure colors as well as font types for different elements.

use_pointer: Flag to enable the pointer in front of the currently
highlighted element.

Returns:
Question: Question instance, ready to be prompted (using `.ask()`).
"""

merged_style = merge_styles([DEFAULT_STYLE, style])

ic = InquirerControl(choices, default)
ic = InquirerControl(choices, default,
use_pointer=use_pointer)

def get_prompt_tokens():
tokens = []
Expand All @@ -66,9 +71,14 @@ def get_prompt_tokens():
if nbr_selected == 0:
tokens.append(("class:answer", ' done'))
elif nbr_selected == 1:
tokens.append(("class:answer",
' [{}]'.format(
ic.get_selected_values()[0].title)))
if isinstance(ic.get_selected_values()[0].title, list):
tokens.append(("class:answer",
"".join([token[1] for token in
ic.get_selected_values()[0].title])))
else:
tokens.append(("class:answer",
' [{}]'.format(
ic.get_selected_values()[0].title)))
else:
tokens.append(("class:answer",
' done ({} selections)'.format(
Expand Down
64 changes: 49 additions & 15 deletions questionary/prompts/common.py
Expand Up @@ -39,10 +39,16 @@ def __init__(self,
"""

self.disabled = disabled
self.value = value if value is not None else title
self.title = title
self.checked = checked

if value is not None:
self.value = value
elif isinstance(title, list):
self.value = "".join([token[1] for token in title])
else:
self.value = title

if shortcut_key is not None:
self.shortcut_key = str(shortcut_key)
else:
Expand Down Expand Up @@ -91,10 +97,12 @@ def __init__(self,
default: Optional[Any] = None,
use_indicator: bool = True,
use_shortcuts: bool = False,
use_pointer: bool = True,
**kwargs):

self.use_indicator = use_indicator
self.use_shortcuts = use_shortcuts
self.use_pointer = use_pointer
self.default = default

self.pointed_at = None
Expand Down Expand Up @@ -166,18 +174,33 @@ def append(index, choice):
selected = (choice.value in self.selected_options)

if index == self.pointed_at:
tokens.append(("class:pointer",
" {} ".format(SELECTED_POINTER)))
if self.use_pointer:
tokens.append(("class:pointer",
" {} ".format(SELECTED_POINTER)))
else:
tokens.append(("class:text", " "))

tokens.append(("[SetCursorPosition]", ""))
else:
tokens.append(("", " "))
tokens.append(("class:text", " "))

if isinstance(choice, Separator):
tokens.append(("class:separator", "{}".format(choice.title)))
elif choice.disabled: # disabled
tokens.append(("class:selected" if selected else "",
"- {} ({})".format(choice.title,
choice.disabled)))
if isinstance(choice.title, list):
tokens.append(("class:selected" if selected
else "class:disabled", "- "))
tokens.extend(choice.title)
else:
tokens.append(("class:selected" if selected
else "class:disabled",
"- {}".format(choice.title)))

tokens.append(("class:selected" if selected
else "class:disabled",
"{}".format(
"" if isinstance(choice.disabled, bool)
else " ({})".format(choice.disabled))))
else:
if self.use_shortcuts and choice.shortcut_key is not None:
shortcut = "{}) ".format(choice.shortcut_key)
Expand All @@ -191,19 +214,30 @@ def append(index, choice):
indicator = ""

tokens.append(("class:selected",
"{}{}{}".format(indicator,
shortcut,
choice.title)))
"{}".format(indicator)))
else:
if self.use_indicator:
indicator = INDICATOR_UNSELECTED + " "
else:
indicator = ""

tokens.append(("",
"{}{}{}".format(indicator,
shortcut,
choice.title)))
tokens.append(("class:text",
"{}".format(indicator)))

if isinstance(choice.title, list):
tokens.extend(choice.title)
elif index == self.pointed_at and not self.use_pointer:
tokens.append(("class:highlighted",
"{}{}".format(shortcut,
choice.title)))
elif selected:
tokens.append(("class:selected",
"{}{}".format(shortcut,
choice.title)))
else:
tokens.append(("class:text",
"{}{}".format(shortcut,
choice.title)))

tokens.append(("", "\n"))

Expand All @@ -212,7 +246,7 @@ def append(index, choice):
append(i, c)

if self.use_shortcuts:
tokens.append(("",
tokens.append(("class:text",
' Answer: {}'
''.format(self.get_pointed_at().shortcut_key)))
else:
Expand Down
14 changes: 12 additions & 2 deletions questionary/prompts/select.py
Expand Up @@ -29,6 +29,7 @@ def select(message: Text,
style: Optional[Style] = None,
use_shortcuts: bool = False,
use_indicator: bool = False,
use_pointer: bool = True,
**kwargs: Any) -> Question:
"""Prompt the user to select one item from the list of choices.

Expand Down Expand Up @@ -57,6 +58,9 @@ def select(message: Text,
use_shortcuts: Allow the user to select items from the list using
shortcuts. The shortcuts will be displayed in front of
the list items.

use_pointer: Flag to enable the pointer in front of the currently
highlighted element.
Returns:
Question: Question instance, ready to be prompted (using `.ask()`).
"""
Expand All @@ -75,15 +79,21 @@ def select(message: Text,

ic = InquirerControl(choices, default,
use_indicator=use_indicator,
use_shortcuts=use_shortcuts)
use_shortcuts=use_shortcuts,
use_pointer=use_pointer)

def get_prompt_tokens():
# noinspection PyListCreation
tokens = [("class:qmark", qmark),
("class:question", ' {} '.format(message))]

if ic.is_answered:
tokens.append(("class:answer", ' ' + ic.get_pointed_at().title))
if isinstance(ic.get_pointed_at().title, list):
tokens.append(("class:answer",
"".join([token[1] for token in
ic.get_pointed_at().title])))
else:
tokens.append(("class:answer", ' ' + ic.get_pointed_at().title))
else:
if use_shortcuts:
tokens.append(("class:instruction", ' (Use shortcuts)'))
Expand Down