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

Adding descriptions for each option #114

Merged
merged 23 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4899b48
Update __init__.py
iamalisalehi Mar 26, 2024
912da47
An example to show descriptions.
iamalisalehi Mar 26, 2024
82a75ea
Update __init__.py
iamalisalehi Mar 26, 2024
71ab424
1) changed the main program to parse dictionaries for options with de…
iamalisalehi Mar 28, 2024
58dc2e7
Delete .idea directory
iamalisalehi Mar 31, 2024
d05493f
Merge branch 'master' into master
iamalisalehi Apr 1, 2024
678c7c0
added optional attribute to the class
iamalisalehi Apr 1, 2024
2819f15
add to
iamalisalehi Apr 1, 2024
1103de8
trying to fix some commit messages
iamalisalehi Apr 1, 2024
0fbcfdf
Delete .idea directory
iamalisalehi Apr 1, 2024
95acccb
Fixed types and refactored examples
iamalisalehi Apr 2, 2024
cffdf81
changed test_option()
iamalisalehi Apr 2, 2024
187f047
fixed type checks for the eddited test
iamalisalehi Apr 2, 2024
f327cc3
revert back type hints grammer
iamalisalehi Apr 2, 2024
d501f20
fixed an error caused ny find/replace all
iamalisalehi Apr 2, 2024
388969c
Applied early return principle and fixed some minor stuff
iamalisalehi Apr 3, 2024
9098b2c
Resolving widescreen bug
iamalisalehi Apr 4, 2024
fb98b9f
revert back support for dictionary input
iamalisalehi Apr 7, 2024
9d085dc
update examples
iamalisalehi Apr 7, 2024
cb4b44a
update tests
iamalisalehi Apr 7, 2024
b946530
revert List to Sequence for options type
iamalisalehi Apr 7, 2024
be993c5
Update src/pick/__init__.py
iamalisalehi Apr 8, 2024
87540ac
If no option present, use the whole line
iamalisalehi Apr 8, 2024
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ __pycache__/
# due to using tox and pytest
.tox
.cache
.idea/
2 changes: 1 addition & 1 deletion example/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
title = "Please choose your favorite programming language: "
options = ["Java", "JavaScript", "Python", "PHP", "C++", "Erlang", "Haskell"]
option, index = pick(options, title, indicator="=>", default_index=2)
print(f"You choosed {option} at index {index}")
print(f"You chose {option} at index {index}")
14 changes: 14 additions & 0 deletions example/description_dict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from pick import pick

title = "Please choose your favorite programming language: "
options = {
"Java": "Java is a high-level, class-based, object-oriented programming language that is designed to have as few implementation dependencies as possible. It is a general-purpose programming language intended to let programmers write once, run anywhere (WORA), meaning that compiled Java code can run on all platforms that support Java without the need to recompile.",
"JavaScript": "JavaScript, often abbreviated as JS, is a programming language and core technology of the Web, alongside HTML and CSS. Almost every website uses JavaScript on the client side for webpage behavior.",
"Python": "Python is a high-level, general-purpose programming language. Its design philosophy emphasizes code readability with the use of significant indentation.",
"PHP": "PHP is a general-purpose scripting language geared towards web development.",
"C++": "C++ is a high-level, general-purpose programming language created by Danish computer scientist Bjarne Stroustrup, first released in 1985 as an extension of the C programming language.",
"Erlang": "",
"Haskell": "",
}
option, index = pick(options, title, indicator="=>")
print(f"You chose {option} at index {index}")
10 changes: 6 additions & 4 deletions example/option.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

title = "Please choose your favorite programming language: "
options = [
Option("Java", ".java"),
Option("Python", ".py"),
Option("Python", ".py", "Python is a high-level, general-purpose programming language. Its design philosophy emphasizes code readability with the use of significant indentation."),
Option("Java", description="Java is a high-level, class-based, object-oriented programming language that is designed to have as few implementation dependencies as possible. It is a general-purpose programming language intended to let programmers write once, run anywhere (WORA), meaning that compiled Java code can run on all platforms that support Java without the need to recompile."),
Option("JavaScript", ".js"),
Option("C++"),
"PHP"
]
option, index = pick(options, title)
print(f"You choosed {option} at index {index}")
option, index = pick(options, title, indicator="=>")
print(f"You chose {option} at index {index}")
57 changes: 49 additions & 8 deletions src/pick/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import curses
from dataclasses import dataclass, field
from typing import Any, List, Optional, Sequence, Tuple, TypeVar, Union, Generic
from typing import Any, Generic, List, Optional, Tuple, TypeVar, Union

__all__ = ["Picker", "pick", "Option"]


@dataclass
class Option:
label: str
value: Any
value: Any = None
description: Optional[str] = None


KEYS_ENTER = (curses.KEY_ENTER, ord("\n"), ord("\r"))
Expand All @@ -19,13 +20,12 @@ class Option:
SYMBOL_CIRCLE_FILLED = "(x)"
SYMBOL_CIRCLE_EMPTY = "( )"

OPTION_T = TypeVar("OPTION_T", str, Option)
OPTION_T = TypeVar('OPTION_T', str, Option)
iamalisalehi marked this conversation as resolved.
Show resolved Hide resolved
PICK_RETURN_T = Tuple[OPTION_T, int]


@dataclass
class Picker(Generic[OPTION_T]):
options: Sequence[OPTION_T]
options: Any
title: Optional[str] = None
indicator: str = "*"
default_index: int = 0
Expand All @@ -36,6 +36,8 @@ class Picker(Generic[OPTION_T]):
screen: Optional["curses._CursesWindow"] = None

def __post_init__(self) -> None:
self.parse_options()

if len(self.options) == 0:
raise ValueError("options should not be an empty list")

Expand All @@ -49,6 +51,19 @@ def __post_init__(self) -> None:

self.index = self.default_index


def parse_options(self) -> None:
if not isinstance(self.options, dict):
return

options: List[Option] = []
for option, description in self.options.items():
if description == "":
options.append(Option(option))
else:
options.append(Option(option, description=description))
self.options = options

def move_up(self) -> None:
self.index -= 1
if self.index < 0:
Expand Down Expand Up @@ -104,13 +119,32 @@ def get_option_lines(self) -> List[str]:

return lines

def get_lines(self) -> Tuple[List, int]:
def get_lines(self) -> Tuple[List[str], int]:
title_lines = self.get_title_lines()
option_lines = self.get_option_lines()
lines = title_lines + option_lines
current_line = self.index + len(title_lines) + 1
return lines, current_line

def get_description_lines(self, description: str, length: int) -> List[str]:
description_words = description.split(" ")
description_lines: List[str] = []

line = ""
for i, word in enumerate(description_words):
if len(line + " " + word) <= length:
if i == 0:
line += word
else:
line += " " + word
else:
description_lines.append(line)
line = word

description_lines.append(line)

return description_lines

def draw(self, screen: "curses._CursesWindow") -> None:
"""draw the curses ui on the screen, handle scroll if needed"""
screen.clear()
Expand All @@ -129,9 +163,16 @@ def draw(self, screen: "curses._CursesWindow") -> None:
lines_to_draw = lines[scroll_top : scroll_top + max_rows]

for line in lines_to_draw:
screen.addnstr(y, x, line, max_x - 2)
screen.addnstr(y, x, line, max_x // 2 - 2)
aisk marked this conversation as resolved.
Show resolved Hide resolved
y += 1

option = self.options[self.index]
if isinstance(option, Option) and option.description is not None:
description_lines = self.get_description_lines(option.description, max_x // 2)

for i, line in enumerate(description_lines):
screen.addstr(i + 3, max_x // 2, line, 2 * max_x // 2 - 2)
iamalisalehi marked this conversation as resolved.
Show resolved Hide resolved

screen.refresh()

def run_loop(
Expand Down Expand Up @@ -181,7 +222,7 @@ def start(self):


def pick(
options: Sequence[OPTION_T],
options: Any,
title: Optional[str] = None,
indicator: str = "*",
default_index: int = 0,
Expand Down
28 changes: 26 additions & 2 deletions tests/test_pick.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,34 @@ def test_multi_select():


def test_option():
options = [Option("option1", 101), Option("option2", 102), Option("option3", 103)]
options = [Option("option1", 101, "description1"), Option("option2", 102),
iamalisalehi marked this conversation as resolved.
Show resolved Hide resolved
Option("option3", description="description3"), Option("option4"), "option5"]
picker = Picker(options, multiselect=True)
for _ in range(4):
picker.mark_index()
picker.move_down()
selected_options = picker.get_selected()
for option in selected_options:
assert isinstance(option[0], Option)
option = selected_options[0]
assert option[0].label == "option1"
assert option[0].value == 101
assert option[0].description == "description1"
picker.mark_index()
selected_options = picker.get_selected()
option = selected_options[-1]
assert isinstance(option, tuple)
assert isinstance(option[0], str)
assert option[0] == "option5"


def test_parse_options():
options = {"option1": "description1", "option2": ""}
picker = Picker(options)
option, _ = picker.get_selected()
assert isinstance(option, Option)
assert option.description == "description1"
picker.move_down()
option, index = picker.get_selected()
assert index == 1
assert isinstance(option, Option)
assert option.value == 102
Loading