Skip to content

Commit

Permalink
Adding descriptions for each option (#114)
Browse files Browse the repository at this point in the history
* Update __init__.py

Added the option to show description messages for each option by moving the cursor on to them.

* An example to show descriptions.

* Update __init__.py

Fixed some screen size math

* 1) changed the main program to parse dictionaries for options with descriptions.
2) changed the example to work with the new way of using the code.
3) wrote a new test for descriptions

* Delete .idea directory

* added optional  attribute to the  class

* add  to

* trying to fix some commit messages

* Delete .idea directory

* Fixed types and refactored examples

* changed test_option()

* fixed type checks for the eddited test

* revert back type hints grammer

* fixed an error caused ny find/replace all

* Applied early return principle and fixed some minor stuff

* Resolving widescreen bug

Co-authored-by: AN Long <[email protected]>

* revert back support for dictionary input

* update examples

* update tests

* revert List to Sequence for options type

* Update src/pick/__init__.py

Co-authored-by: AN Long <[email protected]>

* If no option present, use the whole line

---------

Co-authored-by: AN Long <[email protected]>
  • Loading branch information
iamalisalehi and aisk authored Apr 11, 2024
1 parent 783bc3f commit d1a8e6b
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 17 deletions.
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}")
9 changes: 5 additions & 4 deletions example/option.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

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++")
]
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}")
45 changes: 40 additions & 5 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, Sequence, 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 @@ -22,7 +23,6 @@ class Option:
OPTION_T = TypeVar("OPTION_T", str, Option)
PICK_RETURN_T = Tuple[OPTION_T, int]


@dataclass
class Picker(Generic[OPTION_T]):
options: Sequence[OPTION_T]
Expand Down Expand Up @@ -104,13 +104,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 @@ -128,10 +147,26 @@ def draw(self, screen: "curses._CursesWindow") -> None:

lines_to_draw = lines[scroll_top : scroll_top + max_rows]

description_present = False
for option in self.options:
if isinstance(option, str) or option.description is not None:
description_present = True
break

for line in lines_to_draw:
screen.addnstr(y, x, line, max_x - 2)
if description_present:
screen.addnstr(y, x, line, max_x // 2 - 2)
else:
screen.addnstr(y, x, line, max_x - 2)
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.addnstr(i + 3, max_x // 2, line, 2 * max_x // 2 - 2)

screen.refresh()

def run_loop(
Expand Down
20 changes: 13 additions & 7 deletions tests/test_pick.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,16 @@ def test_multi_select():


def test_option():
options = [Option("option1", 101), Option("option2", 102), Option("option3", 103)]
picker = Picker(options)
picker.move_down()
option, index = picker.get_selected()
assert index == 1
assert isinstance(option, Option)
assert option.value == 102
options = [Option("option1", 101, "description1"), Option("option2", 102),
Option("option3", description="description3"), Option("option4")]
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"

0 comments on commit d1a8e6b

Please sign in to comment.