Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-pick for openSUSE:Factory checked in at 2024-09-16 17:42:47 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pick (Old) and /work/SRC/openSUSE:Factory/.python-pick.new.29891 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pick" Mon Sep 16 17:42:47 2024 rev:12 rq:1201277 version:2.4.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pick/python-pick.changes 2024-05-01 14:58:17.681919263 +0200 +++ /work/SRC/openSUSE:Factory/.python-pick.new.29891/python-pick.changes 2024-09-16 17:43:52.699047432 +0200 @@ -1,0 +2,12 @@ +Sun Sep 15 16:52:14 UTC 2024 - Luigi Baldoni <aloi...@gmx.com> + +- Update to version 2.4.0 + * Bump windows-curses from 2.3.2 to 2.3.3 + * Use textwrap to get description lines + * Fixed long title wrapping + * Added ability to quit early + * Bump version 2.4.0b1 + * Excluded indexes + * Bump version 2.4.0 + +------------------------------------------------------------------- Old: ---- pick-2.3.2.tar.gz New: ---- pick-2.4.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pick.spec ++++++ --- /var/tmp/diff_new_pack.uW4rGk/_old 2024-09-16 17:43:53.175067118 +0200 +++ /var/tmp/diff_new_pack.uW4rGk/_new 2024-09-16 17:43:53.179067283 +0200 @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-pick -Version: 2.3.2 +Version: 2.4.0 Release: 0 Summary: Curses-based interactive selection list module License: MIT ++++++ pick-2.3.2.tar.gz -> pick-2.4.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-2.3.2/README.md new/pick-2.4.0/README.md --- old/pick-2.3.2/README.md 2024-04-25 12:08:53.000000000 +0200 +++ new/pick-2.4.0/README.md 2024-09-15 16:01:50.000000000 +0200 @@ -56,8 +56,10 @@ multiple items by hitting SPACE - `min_selection_count`: (optional) for multi select feature to dictate a minimum of selected items before continuing -- `screen`: (optional), if you are using `pick` within an existing curses application set this to your existing `screen` object. It is assumed this has initialised in the standard way (e.g. via `curses.wrapper()`, or `curses.noecho(); curses.cbreak(); screen.kepad(True)`) +- `screen`: (optional), if you are using `pick` within an existing curses application set this to your existing `screen` object. It is assumed this has initialised in the standard way (e.g. via `curses.wrapper()`, or `curses.noecho(); curses.cbreak(); screen.kepad(True)`) - `position`: (optional), if you are using `pick` within an existing curses application use this to set the first position to write to. e.g., `position=pick.Position(y=1, x=1)` +- `quit_keys`: (optional), if you want to quit early, you can pass a key codes. + If the corresponding key are pressed, it will quit the menu. ## Community Projects diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-2.3.2/example/basic.py new/pick-2.4.0/example/basic.py --- old/pick-2.3.2/example/basic.py 2024-04-25 12:08:53.000000000 +0200 +++ new/pick-2.4.0/example/basic.py 2024-09-15 16:01:50.000000000 +0200 @@ -1,6 +1,12 @@ from pick import pick +KEY_CTRL_C = 3 +KEY_ESCAPE = 27 +QUIT_KEYS = (KEY_CTRL_C, KEY_ESCAPE, ord("q")) + title = "Please choose your favorite programming language: " options = ["Java", "JavaScript", "Python", "PHP", "C++", "Erlang", "Haskell"] -option, index = pick(options, title, indicator="=>", default_index=2) +option, index = pick( + options, title, indicator="=>", default_index=2, quit_keys=QUIT_KEYS +) print(f"You chose {option} at index {index}") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-2.3.2/example/disabled.py new/pick-2.4.0/example/disabled.py --- old/pick-2.3.2/example/disabled.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pick-2.4.0/example/disabled.py 2024-09-15 16:01:50.000000000 +0200 @@ -0,0 +1,12 @@ +from pick import pick, Option + + +title = "Please choose an option: " +options = [ + Option("Option 1", description="All options are `enabled` by default."), + Option("Option 2", description="You can change that by changing the `enabled` attribute of the `Option` object to `False`."), + Option("Option 3", description="This option is disabled!", enabled=False), + Option("Option 4", description="Moving up and down, skips over the disabled options.") +] +option, index = pick(options, title, indicator="=>") +print(f"You chose {option} at index {index}") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-2.3.2/poetry.lock new/pick-2.4.0/poetry.lock --- old/pick-2.3.2/poetry.lock 2024-04-25 12:08:53.000000000 +0200 +++ new/pick-2.4.0/poetry.lock 2024-09-15 16:01:50.000000000 +0200 @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "cfgv" @@ -345,18 +345,18 @@ [[package]] name = "setuptools" -version = "65.5.1" +version = "68.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "setuptools-65.5.1-py3-none-any.whl", hash = "sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31"}, - {file = "setuptools-65.5.1.tar.gz", hash = "sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f"}, + {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, + {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] @@ -460,41 +460,41 @@ [[package]] name = "windows-curses" -version = "2.3.2" +version = "2.3.3" description = "Support for the standard curses module on Windows" optional = false python-versions = "*" files = [ - {file = "windows_curses-2.3.2-cp310-cp310-win32.whl", hash = "sha256:0286d35c9a2589731af3cf2b1251635a400f4b61aef2b9c081f6c98e7887a170"}, - {file = "windows_curses-2.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:b5e68520c7e92dff72914e4126cadb5b8eb3b6c968d955de6d35ff42306da8c0"}, - {file = "windows_curses-2.3.2-cp311-cp311-win32.whl", hash = "sha256:95d2a288af6172270da5ca9225aa99eeae98595c6e90f3574aa9b9f2fc1d2619"}, - {file = "windows_curses-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:9b7ecd0f21f643e22a979effff25f62200626bb9853ea8b9aacf0bbcaab0950d"}, - {file = "windows_curses-2.3.2-cp312-cp312-win32.whl", hash = "sha256:4546122f5bec2fb46c1706c020d971bcfc4d9a5158372f25ba7472a834b0f165"}, - {file = "windows_curses-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:e3f27939f39143c513f444a8c0419b2737e8de55fbe5d63d765992512125366d"}, - {file = "windows_curses-2.3.2-cp36-cp36m-win32.whl", hash = "sha256:a570f744a62108d024a7775b3b156b2ae2380fc971237c3bd2742341e7151f22"}, - {file = "windows_curses-2.3.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f81294465a67e43ddc50c1b52711b100b002fa1238f87d84d0cf94b785c4fe75"}, - {file = "windows_curses-2.3.2-cp37-cp37m-win32.whl", hash = "sha256:72ff5d8963fbb3aa662bfced2c5ea22dc3ed58bac827a3bff74a1de5eacdbe57"}, - {file = "windows_curses-2.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:70d8cb4ddad43f695a266f79c7a31d40ac8aee2a17cf8e06ecfd4a71589ad30d"}, - {file = "windows_curses-2.3.2-cp38-cp38-win32.whl", hash = "sha256:6bc698058081408685975256f46f570c32f8d7e1f4f82f9d6c66c300c6daff89"}, - {file = "windows_curses-2.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:f73bfd283f86d3ac0a72b74307ccc99ea0fd008a732d80db95d31fbafeee3c66"}, - {file = "windows_curses-2.3.2-cp39-cp39-win32.whl", hash = "sha256:e4ec5245f0c00ede45b033a885511eea80d5928c9bd3ceb523fbfb086370a4df"}, - {file = "windows_curses-2.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:00d5d000b68db38bd97b6a5b90ee4b4c915a7cef7492fc5aa9ecc7794ee2ca93"}, + {file = "windows_curses-2.3.3-cp310-cp310-win32.whl", hash = "sha256:859011e77d7d9d9eaabb4fe081760cec966454bdea18a9f3a98e6d10e802fb61"}, + {file = "windows_curses-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:cb0eaf41b90c0b7e33269a6cf34900d8b0d7f3fcf2c1f9dd8191bac11955b711"}, + {file = "windows_curses-2.3.3-cp311-cp311-win32.whl", hash = "sha256:3b3d01720724532794e81f0496d18c1284791b6e5b1fa7a59ec878d1c5134dde"}, + {file = "windows_curses-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:a5d90950656fc0d85aa3d395ec7f454cc7136add512d40a9c5e8ff29a0ce4905"}, + {file = "windows_curses-2.3.3-cp312-cp312-win32.whl", hash = "sha256:e24b68a1c029d3ff521cf9325ba0f533b303e22ed148952a5a5e6396f29f536c"}, + {file = "windows_curses-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:eaea51f169d9940a9bad1edda1759f63795daeb2174b2a45c895148058970689"}, + {file = "windows_curses-2.3.3-cp36-cp36m-win32.whl", hash = "sha256:0d7fb1d799ce3458c9e7215ffa2e8c5e347dabeef1227350b62db6030ebd4c24"}, + {file = "windows_curses-2.3.3-cp36-cp36m-win_amd64.whl", hash = "sha256:1db4246cc019d31614d29e0a26bb8792b0a1a9ba4fbb82ceaf85e5607c7a2857"}, + {file = "windows_curses-2.3.3-cp37-cp37m-win32.whl", hash = "sha256:cb654716c400b24c6410e09415ea682307cf874ee76b109d9c6587a42ac7ed62"}, + {file = "windows_curses-2.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:ba4a6a0d2d56ad834a1241fb5ce83186322cca87295ef4a768ede870ad33d597"}, + {file = "windows_curses-2.3.3-cp38-cp38-win32.whl", hash = "sha256:a3604136cc0284e2284c7172bb81ba6ff914f820c2eb156e5dde71dafd98fff9"}, + {file = "windows_curses-2.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:15d148658e8967fd3826c907c4ad094d35a7a3605eb0e2d1bbad0b5c81b9df9f"}, + {file = "windows_curses-2.3.3-cp39-cp39-win32.whl", hash = "sha256:b61d1d949b508de4e73e8b4848e243c5d9de691c242b2c8d458a991e5e80ad18"}, + {file = "windows_curses-2.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:23b71f3a44cc5a5b92e12d3a519e840e54f8252ff3ef867b630963d5fe7124dd"}, ] [[package]] name = "zipp" -version = "3.6.0" +version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, - {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, + {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, + {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, ] [package.extras] -docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] -testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "2.0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-2.3.2/pyproject.toml new/pick-2.4.0/pyproject.toml --- old/pick-2.3.2/pyproject.toml 2024-04-25 12:08:53.000000000 +0200 +++ new/pick-2.4.0/pyproject.toml 2024-09-15 16:01:50.000000000 +0200 @@ -1,6 +1,6 @@ [tool.poetry] name = "pick" -version = "2.3.2" +version = "2.4.0" description = "Pick an option in the terminal with a simple GUI" authors = ["wong2 <wonderf...@gmail.com>", "AN Long <aisk1...@gmail.com>"] license = "MIT" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-2.3.2/src/pick/__init__.py new/pick-2.4.0/src/pick/__init__.py --- old/pick-2.3.2/src/pick/__init__.py 2024-04-25 12:08:53.000000000 +0200 +++ new/pick-2.4.0/src/pick/__init__.py 2024-09-15 16:01:50.000000000 +0200 @@ -1,7 +1,8 @@ import curses +import textwrap from collections import namedtuple from dataclasses import dataclass, field -from typing import Any, Generic, List, Optional, Sequence, Tuple, TypeVar, Union +from typing import Any, Container, Generic, Iterable, List, Optional, Sequence, Tuple, TypeVar, Union __all__ = ["Picker", "pick", "Option"] @@ -11,6 +12,7 @@ label: str value: Any = None description: Optional[str] = None + enabled: bool = True KEYS_ENTER = (curses.KEY_ENTER, ord("\n"), ord("\r")) @@ -39,6 +41,7 @@ screen: Optional["curses._CursesWindow"] = None position: Position = Position(0, 0) clear_screen: bool = True + quit_keys: Optional[Union[Container[int], Iterable[int]]] = None def __post_init__(self) -> None: if len(self.options) == 0: @@ -52,17 +55,33 @@ "min_selection_count is bigger than the available options, you will not be able to make any selection" ) + if all(isinstance(option, Option) and not option.enabled for option in self.options): + raise ValueError( + "all given options are disabled, you must at least have one enabled option." + ) + self.index = self.default_index + option = self.options[self.index] + if isinstance(option, Option) and not option.enabled: + self.move_down() def move_up(self) -> None: - self.index -= 1 - if self.index < 0: - self.index = len(self.options) - 1 + while True: + self.index -= 1 + if self.index < 0: + self.index = len(self.options) - 1 + option = self.options[self.index] + if not isinstance(option, Option) or option.enabled: + break def move_down(self) -> None: - self.index += 1 - if self.index >= len(self.options): - self.index = 0 + while True: + self.index += 1 + if self.index >= len(self.options): + self.index = 0 + option = self.options[self.index] + if not isinstance(option, Option) or option.enabled: + break def mark_index(self) -> None: if self.multiselect: @@ -83,9 +102,9 @@ else: return self.options[self.index], self.index - def get_title_lines(self) -> List[str]: + def get_title_lines(self, *, max_width: int = 80) -> List[str]: if self.title: - return self.title.split("\n") + [""] + return textwrap.fill(self.title, max_width - 2, drop_whitespace=False).split("\n") + [""] return [] def get_option_lines(self) -> List[str]: @@ -109,32 +128,13 @@ return lines - def get_lines(self) -> Tuple[List[str], int]: - title_lines = self.get_title_lines() + def get_lines(self, *, max_width: int = 80) -> Tuple[List[str], int]: + title_lines = self.get_title_lines(max_width=max_width) 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""" if self.clear_screen: @@ -145,7 +145,7 @@ max_y, max_x = screen.getmaxyx() max_rows = max_y - y # the max rows we can draw - lines, current_line = self.get_lines() + lines, current_line = self.get_lines(max_width=max_x) # calculate how many lines we should scroll, relative to the top scroll_top = 0 @@ -156,12 +156,14 @@ description_present = False for option in self.options: - if not isinstance(option, Option) or option.description is not None: + if isinstance(option, Option) and option.description is not None: description_present = True break - for line in lines_to_draw: - if description_present: + title_length = len(self.get_title_lines(max_width=max_x)) + + for i, line in enumerate(lines_to_draw): + if description_present and i > title_length: screen.addnstr(y, x, line, max_x // 2 - 2) else: screen.addnstr(y, x, line, max_x - 2) @@ -169,10 +171,10 @@ 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) + description_lines = textwrap.fill(option.description, max_x // 2 - 2).split('\n') for i, line in enumerate(description_lines): - screen.addnstr(i + 3, max_x // 2, line, 2 * max_x // 2 - 2) + screen.addnstr(i + title_length, max_x // 2, line, max_x - 2) screen.refresh() @@ -182,7 +184,12 @@ while True: self.draw(screen) c = screen.getch() - if c in KEYS_UP: + if self.quit_keys is not None and c in self.quit_keys: + if self.multiselect: + return [] + else: + return None, -1 + elif c in KEYS_UP: self.move_up() elif c in KEYS_DOWN: self.move_down() @@ -231,7 +238,8 @@ min_selection_count: int = 0, screen: Optional["curses._CursesWindow"] = None, position: Position = Position(0, 0), - clear_screen = True, + clear_screen: bool = True, + quit_keys: Optional[Union[Container[int], Iterable[int]]] = None, ): picker: Picker = Picker( options, @@ -243,5 +251,6 @@ screen, position, clear_screen, + quit_keys, ) return picker.start() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-2.3.2/tests/test_pick.py new/pick-2.4.0/tests/test_pick.py --- old/pick-2.3.2/tests/test_pick.py 2024-04-25 12:08:53.000000000 +0200 +++ new/pick-2.4.0/tests/test_pick.py 2024-09-15 16:01:50.000000000 +0200 @@ -69,3 +69,10 @@ assert option[0].label == "option1" assert option[0].value == 101 assert option[0].description == "description1" + +def test_disabled_option(): + options = [Option("option1"), Option("option2", enabled=False), Option("option3")] + picker = Picker(options) + assert picker.get_selected() == (Option("option1"), 0) + picker.move_down() + assert picker.get_selected() == (Option("option3"), 2)