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 2022-07-26 19:44:07 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pick (Old) and /work/SRC/openSUSE:Factory/.python-pick.new.1533 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pick" Tue Jul 26 19:44:07 2022 rev:6 rq:990887 version:1.4.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pick/python-pick.changes 2020-03-17 13:08:53.889741508 +0100 +++ /work/SRC/openSUSE:Factory/.python-pick.new.1533/python-pick.changes 2022-07-26 19:44:34.522405409 +0200 @@ -1,0 +2,20 @@ +Sun Jul 24 14:10:38 UTC 2022 - Luigi Baldoni <[email protected]> + +- Update to version 1.4.0 + * Drop support for Python 3.6 + * Add pre-commit config + * pick returns single tuple when multiselect=False + version 1.3.1: + * Bump version + version 1.3.0: + Add type hints + version 1.2.0: + * Fixed support for Python 3.6 + version 1.1.1: + * Bump version + version 1.1.0: + * Fixes missing `screen` on call to `self.draw` + version 1.0.0: + * Drop python2 support + +------------------------------------------------------------------- Old: ---- pick-0.6.7.tar.gz New: ---- pick-1.4.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pick.spec ++++++ --- /var/tmp/diff_new_pack.vPnDWh/_old 2022-07-26 19:44:34.914344845 +0200 +++ /var/tmp/diff_new_pack.vPnDWh/_new 2022-07-26 19:44:34.918344227 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-pick # -# Copyright (c) 2020 SUSE LLC +# Copyright (c) 2022 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,14 +17,14 @@ Name: python-pick -Version: 0.6.7 +Version: 1.4.0 Release: 0 Summary: Curses-based interactive selection list module License: MIT URL: https://github.com/wong2/pick -# https://github.com/wong2/pick/issues/28 Source0: https://github.com/wong2/pick/archive/v%{version}.tar.gz#/pick-%{version}.tar.gz -BuildRequires: %{python_module setuptools} +BuildRequires: %{python_module pip} +BuildRequires: %{python_module poetry} BuildRequires: fdupes BuildRequires: python-rpm-macros BuildArch: noarch @@ -42,10 +42,10 @@ %setup -q -n pick-%{version} %build -%python_build +%pyproject_wheel %install -%python_install +%pyproject_install %python_expand %fdupes %{buildroot}%{$python_sitelib} %check ++++++ pick-0.6.7.tar.gz -> pick-1.4.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-0.6.7/.github/workflows/ci.yml new/pick-1.4.0/.github/workflows/ci.yml --- old/pick-0.6.7/.github/workflows/ci.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/pick-1.4.0/.github/workflows/ci.yml 2022-07-24 07:38:13.000000000 +0200 @@ -0,0 +1,22 @@ +name: ci +on: [push, pull_request] + +jobs: + ci: + strategy: + fail-fast: false + matrix: + python-version: [3.7, 3.8, 3.9, "3.10"] + os: [ubuntu-18.04, ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Run image + uses: abatilo/[email protected] + - name: Install dependencies + run: poetry install + - name: Run test + run: poetry run pytest diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-0.6.7/.pre-commit-config.yaml new/pick-1.4.0/.pre-commit-config.yaml --- old/pick-0.6.7/.pre-commit-config.yaml 1970-01-01 01:00:00.000000000 +0100 +++ new/pick-1.4.0/.pre-commit-config.yaml 2022-07-24 07:38:13.000000000 +0200 @@ -0,0 +1,6 @@ +repos: + - repo: https://github.com/psf/black + rev: 22.6.0 + hooks: + - id: black + language_version: python3.10 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-0.6.7/.travis.yml new/pick-1.4.0/.travis.yml --- old/pick-0.6.7/.travis.yml 2020-02-02 08:07:31.000000000 +0100 +++ new/pick-1.4.0/.travis.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,20 +0,0 @@ -language: python - -cache: pip - -python: - - "2.7" - - "3.4" - - "3.5" - - "3.6" - - "3.7" - - "3.8" - -install: - - python setup.py install - -script: - - nosetests - -after_success: - - bash <(curl -s https://codecov.io/bash) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-0.6.7/MANIFEST.in new/pick-1.4.0/MANIFEST.in --- old/pick-0.6.7/MANIFEST.in 2020-02-02 08:07:31.000000000 +0100 +++ new/pick-1.4.0/MANIFEST.in 1970-01-01 01:00:00.000000000 +0100 @@ -1,2 +0,0 @@ -include README.rst -include LICENSE diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-0.6.7/Makefile new/pick-1.4.0/Makefile --- old/pick-0.6.7/Makefile 2020-02-02 08:07:31.000000000 +0100 +++ new/pick-1.4.0/Makefile 2022-07-24 07:38:13.000000000 +0200 @@ -1,5 +1,13 @@ +.PHONY=publish publish: - python setup.py sdist - python setup.py bdist_wheel - twine upload dist/* + poetry build + poetry publish + +.PHONY=test +test: + poetry run mypy . + poetry run pytest + +.PHONY=clean +clean: rm -fr build dist pick.egg-info diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-0.6.7/README.rst new/pick-1.4.0/README.rst --- old/pick-0.6.7/README.rst 2020-02-02 08:07:31.000000000 +0100 +++ new/pick-1.4.0/README.rst 2022-07-24 07:38:13.000000000 +0200 @@ -1,17 +1,22 @@ pick ==== -.. image:: https://travis-ci.org/wong2/pick.svg?branch=master - :alt: Build Status - :target: https://travis-ci.org/wong2/pick +.. image:: https://github.com/wong2/pick/actions/workflows/ci.yml/badge.svg + :target: https://github.com/wong2/pick/actions/workflows/ci.yml .. image:: https://img.shields.io/pypi/v/pick.svg :alt: PyPI :target: https://pypi.python.org/pypi/pick + +.. image:: https://img.shields.io/pypi/dm/pick + :alt: PyPI + :target: https://pypi.python.org/pypi/pick + +| -**pick** is a small python library to help you create curses based interactive selection list in the terminal. See it in action: +**pick** is a small python library to help you create curses based interactive selection list in the terminal. -.. image:: example/basic.gif?raw=true +.. image:: https://github.com/wong2/pick/raw/master/example/basic.gif :alt: Demo Installation @@ -21,8 +26,6 @@ $ pip install pick -note for Windows users: run ``pip install windows-curses`` to enable curses module support. - Usage ----- @@ -113,3 +116,8 @@ **outputs**:: >>> ({ 'label': 'option1' }, 0) + +Community Projects +-------------------- + +`pickpack <https://github.com/anafvana/pickpack>`_: A fork of `pick` to select tree data. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-0.6.7/example/basic.py new/pick-1.4.0/example/basic.py --- old/pick-0.6.7/example/basic.py 2020-02-02 08:07:31.000000000 +0100 +++ new/pick-1.4.0/example/basic.py 2022-07-24 07:38:13.000000000 +0200 @@ -1,10 +1,8 @@ -#-*-coding:utf-8-*- - -from __future__ import print_function - from pick import pick -title = 'Please choose your favorite programming language: ' -options = ['Java', 'JavaScript', 'Python', 'PHP', 'C++', 'Erlang', 'Haskell'] -option, index = pick(options, title, indicator='=>', default_index=2) +title = "Please choose your favorite programming language: " +options = ["Java", "JavaScript", "Python", "PHP", "C++", "Erlang", "Haskell"] +selection = pick(options, title, indicator="=>", default_index=2) +assert len(selection) == 1 +option, index = selection[0] print(option, index) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-0.6.7/example/custom.py new/pick-1.4.0/example/custom.py --- old/pick-0.6.7/example/custom.py 2020-02-02 08:07:31.000000000 +0100 +++ new/pick-1.4.0/example/custom.py 2022-07-24 07:38:13.000000000 +0200 @@ -1,17 +1,32 @@ -#-*-coding:utf-8-*- - -from __future__ import print_function - import curses from pick import Picker +from typing import Tuple + + +def print_selection(selection): + if isinstance(selection, list): + assert len(selection) == 1 + option, index = selection[0] + print(option, index) + else: + print("KEY_LEFT pressed!") + def go_back(picker): return (None, -1) -title = 'Please choose your favorite programming language: ' -options = ['Java', 'JavaScript', 'Python', 'PHP', 'C++', 'Erlang', 'Haskell'] -picker = Picker(options, title) +title = "Please choose your favorite programming language: " +options = ["Java", "JavaScript", "Python", "PHP", "C++", "Erlang", "Haskell"] + +# with type annotation +picker: Picker[Tuple[None, int], str] = Picker(options, title) picker.register_custom_handler(curses.KEY_LEFT, go_back) -option, index = picker.start() -print(option, index) +selection = picker.start() +print_selection(selection) + +# with type warning suppression +unannotated_picker = Picker(options, title) # type: ignore[var-annotated] +unannotated_picker.register_custom_handler(curses.KEY_LEFT, go_back) +selection = picker.start() +print_selection(selection) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-0.6.7/example/options_map_func.py new/pick-1.4.0/example/options_map_func.py --- old/pick-0.6.7/example/options_map_func.py 2020-02-02 08:07:31.000000000 +0100 +++ new/pick-1.4.0/example/options_map_func.py 2022-07-24 07:38:13.000000000 +0200 @@ -1,21 +1,22 @@ -#-*-coding:utf-8-*- - -from __future__ import print_function - from pick import pick -title = 'Please choose your favorite fruit: ' +title = "Please choose your favorite fruit: " options = [ - { 'name': 'Apples', 'grow_on': 'trees' }, - { 'name': 'Oranges', 'grow_on': 'trees' }, - { 'name': 'Strawberries', 'grow_on': 'vines' }, - { 'name': 'Grapes', 'grow_on': 'vines' }, + {"name": "Apples", "grow_on": "trees"}, + {"name": "Oranges", "grow_on": "trees"}, + {"name": "Strawberries", "grow_on": "vines"}, + {"name": "Grapes", "grow_on": "vines"}, ] + def get_description_for_display(option): # format the option data for display - return '{0} (grow on {1})'.format(option.get('name'), option.get('grow_on')) + return "{0} (grow on {1})".format(option.get("name"), option.get("grow_on")) -option, index = pick(options, title, indicator='=>', options_map_func=get_description_for_display) +selection = pick( + options, title, indicator="=>", options_map_func=get_description_for_display +) +assert len(selection) == 1 +option, index = selection[0] print(option, index) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-0.6.7/example/scroll.py new/pick-1.4.0/example/scroll.py --- old/pick-0.6.7/example/scroll.py 2020-02-02 08:07:31.000000000 +0100 +++ new/pick-1.4.0/example/scroll.py 2022-07-24 07:38:13.000000000 +0200 @@ -1,10 +1,8 @@ -#-*-coding:utf-8-*- - -from __future__ import print_function - from pick import pick -title = 'Select:' -options = ['foo.bar%s.baz' % x for x in range(1, 71)] -option, index = pick(options, title) +title = "Select:" +options = ["foo.bar%s.baz" % x for x in range(1, 71)] +selection = pick(options, title) +assert len(selection) == 1 +option, index = selection[0] print(option, index) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-0.6.7/pick/__init__.py new/pick-1.4.0/pick/__init__.py --- old/pick-0.6.7/pick/__init__.py 2020-02-02 08:07:31.000000000 +0100 +++ new/pick-1.4.0/pick/__init__.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,195 +0,0 @@ -#-*-coding:utf-8-*- - -import curses - -__all__ = ['Picker', 'pick'] - - -KEYS_ENTER = (curses.KEY_ENTER, ord('\n'), ord('\r')) -KEYS_UP = (curses.KEY_UP, ord('k')) -KEYS_DOWN = (curses.KEY_DOWN, ord('j')) -KEYS_SELECT = (curses.KEY_RIGHT, ord(' ')) - -class Picker(object): - """The :class:`Picker <Picker>` object - - :param options: a list of options to choose from - :param title: (optional) a title above options list - :param multiselect: (optional) if true its possible to select multiple values by hitting SPACE, defaults to False - :param indicator: (optional) custom the selection indicator - :param default_index: (optional) set this if the default selected option is not the first one - :param options_map_func: (optional) a mapping function to pass each option through before displaying - """ - - def __init__(self, options, title=None, indicator='*', default_index=0, multiselect=False, multi_select=False, min_selection_count=0, options_map_func=None): - - if len(options) == 0: - raise ValueError('options should not be an empty list') - - self.options = options - self.title = title - self.indicator = indicator - self.multiselect = multiselect or multi_select - self.min_selection_count = min_selection_count - self.options_map_func = options_map_func - self.all_selected = [] - - if default_index >= len(options): - raise ValueError('default_index should be less than the length of options') - - if multiselect and min_selection_count > len(options): - raise ValueError('min_selection_count is bigger than the available options, you will not be able to make any selection') - - if options_map_func is not None and not callable(options_map_func): - raise ValueError('options_map_func must be a callable function') - - self.index = default_index - self.custom_handlers = {} - - def register_custom_handler(self, key, func): - self.custom_handlers[key] = func - - def move_up(self): - self.index -= 1 - if self.index < 0: - self.index = len(self.options) - 1 - - def move_down(self): - self.index += 1 - if self.index >= len(self.options): - self.index = 0 - - def mark_index(self): - if self.multiselect: - if self.index in self.all_selected: - self.all_selected.remove(self.index) - else: - self.all_selected.append(self.index) - - def get_selected(self): - """return the current selected option as a tuple: (option, index) - or as a list of tuples (in case multiselect==True) - """ - if self.multiselect: - return_tuples = [] - for selected in self.all_selected: - return_tuples.append((self.options[selected], selected)) - return return_tuples - else: - return self.options[self.index], self.index - - def get_title_lines(self): - if self.title: - return self.title.split('\n') + [''] - return [] - - def get_option_lines(self): - lines = [] - for index, option in enumerate(self.options): - # pass the option through the options map of one was passed in - if self.options_map_func: - option = self.options_map_func(option) - - if index == self.index: - prefix = self.indicator - else: - prefix = len(self.indicator) * ' ' - - if self.multiselect and index in self.all_selected: - format = curses.color_pair(1) - line = ('{0} {1}'.format(prefix, option), format) - else: - line = '{0} {1}'.format(prefix, option) - lines.append(line) - - return lines - - def get_lines(self): - 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 draw(self): - """draw the curses ui on the screen, handle scroll if needed""" - self.screen.clear() - - x, y = 1, 1 # start point - max_y, max_x = self.screen.getmaxyx() - max_rows = max_y - y # the max rows we can draw - - lines, current_line = self.get_lines() - - # calculate how many lines we should scroll, relative to the top - scroll_top = getattr(self, 'scroll_top', 0) - if current_line <= scroll_top: - scroll_top = 0 - elif current_line - scroll_top > max_rows: - scroll_top = current_line - max_rows - self.scroll_top = scroll_top - - lines_to_draw = lines[scroll_top:scroll_top+max_rows] - - for line in lines_to_draw: - if type(line) is tuple: - self.screen.addnstr(y, x, line[0], max_x-2, line[1]) - else: - self.screen.addnstr(y, x, line, max_x-2) - y += 1 - - self.screen.refresh() - - def run_loop(self): - while True: - self.draw() - c = self.screen.getch() - if c in KEYS_UP: - self.move_up() - elif c in KEYS_DOWN: - self.move_down() - elif c in KEYS_ENTER: - if self.multiselect and len(self.all_selected) < self.min_selection_count: - continue - return self.get_selected() - elif c in KEYS_SELECT and self.multiselect: - self.mark_index() - elif c in self.custom_handlers: - ret = self.custom_handlers[c](self) - if ret: - return ret - - def config_curses(self): - try: - # use the default colors of the terminal - curses.use_default_colors() - # hide the cursor - curses.curs_set(0) - # add some color for multi_select - # @todo make colors configurable - curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_WHITE) - except: - # Curses failed to initialize color support, eg. when TERM=vt100 - curses.initscr() - - def _start(self, screen): - self.screen = screen - self.config_curses() - return self.run_loop() - - def start(self): - return curses.wrapper(self._start) - - -def pick(*args, **kwargs): - """Construct and start a :class:`Picker <Picker>`. - - Usage:: - - >>> from pick import pick - >>> title = 'Please choose an option: ' - >>> options = ['option1', 'option2', 'option3'] - >>> option, index = pick(options, title) - """ - picker = Picker(*args, **kwargs) - return picker.start() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-0.6.7/poetry.lock new/pick-1.4.0/poetry.lock --- old/pick-0.6.7/poetry.lock 1970-01-01 01:00:00.000000000 +0100 +++ new/pick-1.4.0/poetry.lock 2022-07-24 07:38:13.000000000 +0200 @@ -0,0 +1,346 @@ +[[package]] +name = "atomicwrites" +version = "1.4.1" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "21.4.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] + +[[package]] +name = "cfgv" +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "colorama" +version = "0.4.5" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "distlib" +version = "0.3.5" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "filelock" +version = "3.7.1" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] +testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] + +[[package]] +name = "identify" +version = "2.5.2" +description = "File identification library for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "importlib-metadata" +version = "4.8.3" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +perf = ["ipython"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "mypy" +version = "0.961" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +mypy-extensions = ">=0.4.3" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "nodeenv" +version = "1.7.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "platformdirs" +version = "2.5.2" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] +test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pre-commit" +version = "2.20.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +toml = "*" +virtualenv = ">=20.0.8" + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pyparsing" +version = "3.0.7" +description = "Python parsing module" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "6.2.5" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "1.2.3" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "typed-ast" +version = "1.5.4" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "typing-extensions" +version = "4.1.1" +description = "Backported and Experimental Type Hints for Python 3.6+" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "virtualenv" +version = "20.15.1" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +distlib = ">=0.3.1,<1" +filelock = ">=3.2,<4" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +platformdirs = ">=2,<3" +six = ">=1.9.0,<2" + +[package.extras] +docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] + +[[package]] +name = "windows-curses" +version = "2.3.0" +description = "Support for the standard curses module on Windows" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "zipp" +version = "3.6.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] + +[metadata] +lock-version = "1.1" +python-versions = ">=3.7" +content-hash = "e568d233095e1d85550e7047fd32b8a47bb52bfcc8b1beb1d7145c35ccd82bbd" + +[metadata.files] +atomicwrites = [] +attrs = [] +cfgv = [] +colorama = [] +distlib = [] +filelock = [] +identify = [] +importlib-metadata = [] +iniconfig = [] +mypy = [] +mypy-extensions = [] +nodeenv = [] +packaging = [] +platformdirs = [] +pluggy = [] +pre-commit = [] +py = [] +pyparsing = [] +pytest = [] +pyyaml = [] +six = [] +toml = [] +tomli = [] +typed-ast = [] +typing-extensions = [] +virtualenv = [] +windows-curses = [] +zipp = [] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-0.6.7/pyproject.toml new/pick-1.4.0/pyproject.toml --- old/pick-0.6.7/pyproject.toml 1970-01-01 01:00:00.000000000 +0100 +++ new/pick-1.4.0/pyproject.toml 2022-07-24 07:38:13.000000000 +0200 @@ -0,0 +1,23 @@ +[tool.poetry] +name = "pick" +version = "1.4.0" +description = "Pick an option in the terminal with a simple GUI" +authors = ["wong2 <[email protected]>"] +license = "MIT" +readme = "README.rst" +repository = "https://github.com/wong2/pick" +homepage = "https://github.com/wong2/pick" +keywords = ["terminal", "gui"] + +[tool.poetry.dependencies] +python = ">=3.7" +windows-curses = {version = "^2.2.0", platform = "win32"} + +[tool.poetry.dev-dependencies] +pytest = "^6.2.5" +mypy = "^0.961" +pre-commit = "^2.20.0" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-0.6.7/setup.cfg new/pick-1.4.0/setup.cfg --- old/pick-0.6.7/setup.cfg 2020-02-02 08:07:31.000000000 +0100 +++ new/pick-1.4.0/setup.cfg 2022-07-24 07:38:13.000000000 +0200 @@ -1,2 +1,5 @@ -[bdist_wheel] -universal=1 +[mypy] +check_untyped_defs = True +warn_return_any = True +warn_unreachable = True +warn_unused_ignores = True diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-0.6.7/setup.py new/pick-1.4.0/setup.py --- old/pick-0.6.7/setup.py 2020-02-02 08:07:31.000000000 +0100 +++ new/pick-1.4.0/setup.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,34 +0,0 @@ -#-*-coding:utf-8-*- - -import os -from setuptools import setup - -def fread(fname): - filepath = os.path.join(os.path.dirname(__file__), fname) - with open(filepath, 'r') as fp: - return fp.read() - -setup( - name='pick', - version='0.6.7', - description='pick an option in the terminal with a simple GUI', - long_description=fread('README.rst'), - keywords='terminal gui', - url='https://github.com/wong2/pick', - author='wong2', - author_email='[email protected]', - license='MIT', - packages=['pick'], - tests_require=['nose'], - classifiers=[ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5' - ] -) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-0.6.7/src/pick/__init__.py new/pick-1.4.0/src/pick/__init__.py --- old/pick-0.6.7/src/pick/__init__.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pick-1.4.0/src/pick/__init__.py 2022-07-24 07:38:13.000000000 +0200 @@ -0,0 +1,231 @@ +import curses +from dataclasses import dataclass, field +from typing import Generic, Callable, List, Optional, Dict, Union, Tuple, TypeVar + +__all__ = ["Picker", "pick"] + + +KEYS_ENTER = (curses.KEY_ENTER, ord("\n"), ord("\r")) +KEYS_UP = (curses.KEY_UP, ord("k")) +KEYS_DOWN = (curses.KEY_DOWN, ord("j")) +KEYS_SELECT = (curses.KEY_RIGHT, ord(" ")) + +CUSTOM_HANDLER_RETURN_T = TypeVar("CUSTOM_HANDLER_RETURN_T") +KEY_T = int +OPTIONS_MAP_VALUE_T = TypeVar("OPTIONS_MAP_VALUE_T") +PICK_RETURN_T = Tuple[OPTIONS_MAP_VALUE_T, int] + + +@dataclass +class Picker(Generic[CUSTOM_HANDLER_RETURN_T, OPTIONS_MAP_VALUE_T]): + """The :class:`Picker <Picker>` object + + :param options: a list of options to choose from + :param title: (optional) a title above options list + :param multiselect: (optional) if true its possible to select multiple values by hitting SPACE, defaults to False + :param indicator: (optional) custom the selection indicator + :param default_index: (optional) set this if the default selected option is not the first one + :param options_map_func: (optional) a mapping function to pass each option through before displaying + """ + + options: List[OPTIONS_MAP_VALUE_T] + title: Optional[str] = None + indicator: str = "*" + default_index: int = 0 + multiselect: bool = False + min_selection_count: int = 0 + options_map_func: Callable[[OPTIONS_MAP_VALUE_T], Optional[str]] = str + all_selected: List[int] = field(init=False, default_factory=list) + custom_handlers: Dict[KEY_T, Callable[["Picker"], CUSTOM_HANDLER_RETURN_T]] = field( + init=False, default_factory=dict + ) + index: int = field(init=False, default=0) + scroll_top: int = field(init=False, default=0) + + def __post_init__(self) -> None: + if len(self.options) == 0: + raise ValueError("options should not be an empty list") + + if self.default_index >= len(self.options): + raise ValueError("default_index should be less than the length of options") + + if self.multiselect and self.min_selection_count > len(self.options): + raise ValueError( + "min_selection_count is bigger than the available options, you will not be able to make any selection" + ) + + if not callable(self.options_map_func): + raise ValueError("options_map_func must be a callable function") + + self.index = self.default_index + + def register_custom_handler( + self, key: KEY_T, func: Callable[["Picker"], CUSTOM_HANDLER_RETURN_T] + ) -> None: + self.custom_handlers[key] = func + + def move_up(self) -> None: + self.index -= 1 + if self.index < 0: + self.index = len(self.options) - 1 + + def move_down(self) -> None: + self.index += 1 + if self.index >= len(self.options): + self.index = 0 + + def mark_index(self) -> None: + if self.multiselect: + if self.index in self.all_selected: + self.all_selected.remove(self.index) + else: + self.all_selected.append(self.index) + + def get_selected(self) -> Union[List[PICK_RETURN_T], PICK_RETURN_T]: + """return the current selected option as a tuple: (option, index) + or as a list of tuples (in case multiselect==True) + """ + if self.multiselect: + return_tuples = [] + for selected in self.all_selected: + return_tuples.append((self.options[selected], selected)) + return return_tuples + else: + return self.options[self.index], self.index + + def get_title_lines(self) -> List[str]: + if self.title: + return self.title.split("\n") + [""] + return [] + + def get_option_lines(self) -> Union[List[str], List[Tuple[str, int]]]: + lines: Union[List[str], List[Tuple[str, int]]] = [] # type: ignore[assignment] + for index, option in enumerate(self.options): + option_as_str = self.options_map_func(option) + + if index == self.index: + prefix = self.indicator + else: + prefix = len(self.indicator) * " " + + line: Union[Tuple[str, int], str] + if self.multiselect and index in self.all_selected: + format = curses.color_pair(1) + line = ("{0} {1}".format(prefix, option_as_str), format) + else: + line = "{0} {1}".format(prefix, option_as_str) + lines.append(line) # type: ignore[arg-type] + + return lines + + def get_lines(self) -> Tuple[List, int]: + title_lines = self.get_title_lines() + option_lines = self.get_option_lines() + lines = title_lines + option_lines # type: ignore[operator] + current_line = self.index + len(title_lines) + 1 + return lines, current_line + + def draw(self, screen) -> None: + """draw the curses ui on the screen, handle scroll if needed""" + screen.clear() + + x, y = 1, 1 # start point + max_y, max_x = screen.getmaxyx() + max_rows = max_y - y # the max rows we can draw + + lines, current_line = self.get_lines() + + # calculate how many lines we should scroll, relative to the top + if current_line <= self.scroll_top: + self.scroll_top = 0 + elif current_line - self.scroll_top > max_rows: + self.scroll_top = current_line - max_rows + + lines_to_draw = lines[self.scroll_top : self.scroll_top + max_rows] + + for line in lines_to_draw: + if type(line) is tuple: + screen.addnstr(y, x, line[0], max_x - 2, line[1]) + else: + screen.addnstr(y, x, line, max_x - 2) + y += 1 + + screen.refresh() + + def run_loop( + self, screen + ) -> Union[List[PICK_RETURN_T], PICK_RETURN_T, CUSTOM_HANDLER_RETURN_T]: + while True: + self.draw(screen) + c = screen.getch() + if c in KEYS_UP: + self.move_up() + elif c in KEYS_DOWN: + self.move_down() + elif c in KEYS_ENTER: + if ( + self.multiselect + and len(self.all_selected) < self.min_selection_count + ): + continue + return self.get_selected() + elif c in KEYS_SELECT and self.multiselect: + self.mark_index() + elif c in self.custom_handlers: + ret = self.custom_handlers[c](self) + if ret: + return ret + + def config_curses(self) -> None: + try: + # use the default colors of the terminal + curses.use_default_colors() + # hide the cursor + curses.curs_set(0) + # add some color for multi_select + # @todo make colors configurable + curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_WHITE) + except: + # Curses failed to initialize color support, eg. when TERM=vt100 + curses.initscr() + + def _start( + self, screen + ) -> Union[List[PICK_RETURN_T], PICK_RETURN_T, CUSTOM_HANDLER_RETURN_T]: + self.config_curses() + return self.run_loop(screen) + + def start( + self, + ) -> Union[List[PICK_RETURN_T], PICK_RETURN_T, CUSTOM_HANDLER_RETURN_T]: + return curses.wrapper(self._start) + + +def pick( + options: List[OPTIONS_MAP_VALUE_T], + title: Optional[str] = None, + indicator: str = "*", + default_index: int = 0, + multiselect: bool = False, + min_selection_count: int = 0, + options_map_func: Callable[[OPTIONS_MAP_VALUE_T], Optional[str]] = str, +) -> Union[List[PICK_RETURN_T], PICK_RETURN_T]: + """Construct and start a :class:`Picker <Picker>`. + + Usage:: + + >>> from pick import pick + >>> title = 'Please choose an option: ' + >>> options = ['option1', 'option2', 'option3'] + >>> option, index = pick(options, title) + """ + picker = Picker( + options, + title, + indicator, + default_index, + multiselect, + min_selection_count, + options_map_func, + ) + return picker.start() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pick-0.6.7/tests/test_pick.py new/pick-1.4.0/tests/test_pick.py --- old/pick-0.6.7/tests/test_pick.py 2020-02-02 08:07:31.000000000 +0100 +++ new/pick-1.4.0/tests/test_pick.py 2022-07-24 07:38:13.000000000 +0200 @@ -1,63 +1,66 @@ -#-*-coding:utf-8-*- +from typing import Dict, Optional -import unittest from pick import Picker -class TestPick(unittest.TestCase): - - def test_move_up_down(self): - title = 'Please choose an option: ' - options = ['option1', 'option2', 'option3'] - picker = Picker(options, title) - picker.move_up() - assert picker.get_selected() == ('option3', 2) - picker.move_down() - picker.move_down() - assert picker.get_selected() == ('option2', 1) - - def test_default_index(self): - title = 'Please choose an option: ' - options = ['option1', 'option2', 'option3'] - picker = Picker(options, title, default_index=1) - assert picker.get_selected() == ('option2', 1) - - def test_get_lines(self): - title = 'Please choose an option: ' - options = ['option1', 'option2', 'option3'] - picker = Picker(options, title, indicator='*') - lines, current_line = picker.get_lines() - assert lines == [title, '', '* option1', ' option2', ' option3'] - assert current_line == 3 - - def test_no_title(self): - options = ['option1', 'option2', 'option3'] - picker = Picker(options) - lines, current_line = picker.get_lines() - assert current_line == 1 - - def test_multi_select(self): - title = 'Please choose an option: ' - options = ['option1', 'option2', 'option3'] - picker = Picker(options, title, multi_select=True, min_selection_count=1) - assert picker.get_selected() == [] - picker.mark_index() - assert picker.get_selected() == [('option1', 0)] - picker.move_down() - picker.mark_index() - assert picker.get_selected() == [('option1', 0), ('option2', 1)] - - def test_options_map_func(self): - title = 'Please choose an option: ' - options = [{'label': 'option1'}, {'label': 'option2'}, {'label': 'option3'}] - - def get_label(option): return option.get('label') - - picker = Picker(options, title, indicator='*', options_map_func=get_label) - lines, current_line = picker.get_lines() - assert lines == [title, '', '* option1', ' option2', ' option3'] - assert picker.get_selected() == ({ 'label': 'option1' }, 0) - - -if __name__ == '__main__': - unittest.main() +def test_move_up_down(): + title = "Please choose an option: " + options = ["option1", "option2", "option3"] + picker: Picker[str, str] = Picker(options, title) + picker.move_up() + assert picker.get_selected() == ("option3", 2) + picker.move_down() + picker.move_down() + assert picker.get_selected() == ("option2", 1) + + +def test_default_index(): + title = "Please choose an option: " + options = ["option1", "option2", "option3"] + picker: Picker[str, str] = Picker(options, title, default_index=1) + assert picker.get_selected() == ("option2", 1) + + +def test_get_lines(): + title = "Please choose an option: " + options = ["option1", "option2", "option3"] + picker: Picker[str, str] = Picker(options, title, indicator="*") + lines, current_line = picker.get_lines() + assert lines == [title, "", "* option1", " option2", " option3"] + assert current_line == 3 + + +def test_no_title(): + options = ["option1", "option2", "option3"] + picker: Picker[str, str] = Picker(options) + lines, current_line = picker.get_lines() + assert current_line == 1 + + +def test_multi_select(): + title = "Please choose an option: " + options = ["option1", "option2", "option3"] + picker: Picker[str, str] = Picker( + options, title, multiselect=True, min_selection_count=1 + ) + assert picker.get_selected() == [] + picker.mark_index() + assert picker.get_selected() == [("option1", 0)] + picker.move_down() + picker.mark_index() + assert picker.get_selected() == [("option1", 0), ("option2", 1)] + + +def test_options_map_func(): + title = "Please choose an option: " + options = [{"label": "option1"}, {"label": "option2"}, {"label": "option3"}] + + def get_label(option: Dict[str, str]) -> Optional[str]: + return option.get("label") + + picker: Picker[str, Dict[str, str]] = Picker( + options, title, indicator="*", options_map_func=get_label + ) + lines, current_line = picker.get_lines() + assert lines == [title, "", "* option1", " option2", " option3"] + assert picker.get_selected() == ({"label": "option1"}, 0)
