From 20d9d1b780224c0c5dfee209f54cf750737fac74 Mon Sep 17 00:00:00 2001 From: Jan Wille Date: Sun, 3 Jul 2022 01:23:33 +0200 Subject: [PATCH 01/21] correct the list of suported python versions 2.7 was not supported or tested for a long time. Since mokypatch requires at least 3.6, this will be the first supporded version. --- README.rst | 2 +- setup.py | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 611cdac..fc8b33a 100644 --- a/README.rst +++ b/README.rst @@ -29,7 +29,7 @@ Installation pip install readchar -The :code:`readchar` library works with python 2.7, 3.4, 3.5, 3.6 and Pypy. +The :code:`readchar` library works with python 3.6, 3.7, 3.8, 3.9 and 3.10. Usage ----- diff --git a/setup.py b/setup.py index c5b46b4..3660d5d 100644 --- a/setup.py +++ b/setup.py @@ -53,15 +53,12 @@ def run_tests(self): "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development", "Topic :: Software Development :: User Interfaces", ], From a76858453130860cfe6f9a53a14f27358aad121f Mon Sep 17 00:00:00 2001 From: Jan Wille Date: Mon, 4 Jul 2022 13:25:20 +0200 Subject: [PATCH 02/21] Remove UTF-8 headers in Python files The `# -*- coding: utf-8 -*-` headers were useful for Python 2, and aren't needed for Python 3 where UTF-8 is the default. --- readchar/readchar.py | 1 - readchar/readchar_linux.py | 1 - readchar/readchar_windows.py | 1 - setup.py | 2 -- 4 files changed, 5 deletions(-) diff --git a/readchar/readchar.py b/readchar/readchar.py index 8aa352a..f5b0cb6 100644 --- a/readchar/readchar.py +++ b/readchar/readchar.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is based on this gist: # http://code.activestate.com/recipes/134892/ # So real authors are DannyYoo and company. diff --git a/readchar/readchar_linux.py b/readchar/readchar_linux.py index 6bcb4e2..3023c44 100644 --- a/readchar/readchar_linux.py +++ b/readchar/readchar_linux.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Initially taken from: # http://code.activestate.com/recipes/134892/ # Thanks to Danny Yoo diff --git a/readchar/readchar_windows.py b/readchar/readchar_windows.py index 3afb4f8..d83a22f 100644 --- a/readchar/readchar_windows.py +++ b/readchar/readchar_windows.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Initially taken from: # http://code.activestate.com/recipes/134892/#c9 # Thanks to Stephen Chappell diff --git a/setup.py b/setup.py index 3660d5d..6631445 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import os import sys from io import open From 7acfc9d52a207c4e23e08198ad862c791a67db0f Mon Sep 17 00:00:00 2001 From: Jan Wille Date: Sun, 10 Jul 2022 20:34:29 +0200 Subject: [PATCH 03/21] add LICENCE closes #44 --- LICENCE | 21 +++++++++++++++++++++ README.rst | 6 +++--- 2 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 LICENCE diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..66937d1 --- /dev/null +++ b/LICENCE @@ -0,0 +1,21 @@ +MIT Licence + +Copyright (c) 2022 Miguel Angel Garcia + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicence, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.rst b/README.rst index fc8b33a..3a5c62a 100644 --- a/README.rst +++ b/README.rst @@ -117,14 +117,14 @@ In order to develop or running the tests, you can do: Please, **Execute the tests before any pull-request**. This will avoid invalid builds. -License +Licence ======= Copyright (c) 2014-2021 Miguel Angel Garcia (`@magmax_en`_). Based on previous work on gist `getch()-like unbuffered character reading from stdin on both Windows and Unix (Python recipe)`_, started by `Danny Yoo`_. -Licensed under `the MIT license`_. +Licensed under `the MIT licence`_. .. |travis| image:: https://travis-ci.org/magmax/python-readchar.png @@ -150,6 +150,6 @@ Licensed under `the MIT license`_. .. _Coveralls: https://coveralls.io/r/magmax/python-readchar .. _@magmax_en: https://twitter.com/magmax_en -.. _the MIT license: http://opensource.org/licenses/MIT +.. _the MIT licence: http://opensource.org/licenses/MIT .. _getch()-like unbuffered character reading from stdin on both Windows and Unix (Python recipe): http://code.activestate.com/recipes/134892/ .. _Danny Yoo: http://code.activestate.com/recipes/users/98032/ From d77fe8bc9a49c8a62af0230bb7c6271f083c914c Mon Sep 17 00:00:00 2001 From: Jan Wille Date: Mon, 4 Jul 2022 13:18:33 +0200 Subject: [PATCH 04/21] general cleanup of config and setup files --- .coveragerc | 2 +- .flake8 | 11 ++- .github/workflows/run-tests.yml | 2 +- .gitignore | 18 +++-- .pre-commit-config.yaml | 7 +- .yamllint.yaml => .yamllint.yml | 6 +- Makefile | 12 ++-- README.rst | 14 ++-- pytest.ini | 3 + readchar/__init__.py | 5 ++ requirements-test.txt => requirements.txt | 4 +- setup.cfg | 60 +++++++++++++++-- setup.py | 82 ++--------------------- 13 files changed, 116 insertions(+), 110 deletions(-) rename .yamllint.yaml => .yamllint.yml (51%) create mode 100644 pytest.ini rename requirements-test.txt => requirements.txt (52%) diff --git a/.coveragerc b/.coveragerc index e7d592e..c712d25 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,2 @@ [run] -omit = */tests* +omit = tests/* diff --git a/.flake8 b/.flake8 index c057b0f..9a3ac51 100644 --- a/.flake8 +++ b/.flake8 @@ -1,2 +1,11 @@ [flake8] -max-line-length=119 +exclude = + __pycache__/ + .git/ + .venv/ + .pytest_cache/ +show-source = true +statistics = true +count = true +max-complexity = 12 +max-line-length = 88 diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 9f6ea51..7a0502e 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -37,7 +37,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install -r requirements-test.txt + python -m pip install -r requirements.txt - name: Test with pytest run: | pytest diff --git a/.gitignore b/.gitignore index 4301da0..047f31d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,20 @@ *~ \#* \.\#* + +# Python Files +/.venv/ +__pychache__/ *.pyc + +# Build files: +/build/ +/dist/ *.egg-info/ -build/ -dist/ -venv/ -.coverage -coverage.xml .eggs *.egg + +# Testing files: +/.pytest_cache/ +.coverage +coverage.xml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 45f221f..5193a08 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,12 @@ --- repos: + - repo: https://github.com/adrienverge/yamllint rev: v1.26.3 hooks: - name: check YAML format id: yamllint + - repo: https://github.com/psf/black rev: 22.1.0 hooks: @@ -23,8 +25,3 @@ repos: hooks: - name: check-format with flake8 id: flake8 - args: - - --show-source - - --statistics - - --count - - --max-complexity=12 diff --git a/.yamllint.yaml b/.yamllint.yml similarity index 51% rename from .yamllint.yaml rename to .yamllint.yml index e9147c4..b7ed691 100644 --- a/.yamllint.yaml +++ b/.yamllint.yml @@ -1,8 +1,8 @@ extends: default +ignore: | + .venv/* + rules: - truthy: - ignore: | - .github/workflows/* indentation: spaces: 2 diff --git a/Makefile b/Makefile index f5ea67e..db56984 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ -all: precommit test +all: test precommit pack test: - python setup.py test + @pytest -precommit:: - pre-commit run -a +precommit: + @pre-commit run -a -publish: - @python setup.py bdist_wheel upload +pack: + @python setup.py sdist bdist_wheel diff --git a/README.rst b/README.rst index 3a5c62a..c233d7e 100644 --- a/README.rst +++ b/README.rst @@ -93,21 +93,27 @@ In order to develop or running the tests, you can do: .. code:: bash - virtualenv venv + python -m venv .venv 3. Enter in the virtual environment .. code:: bash - source venv/bin/activate + source .venv/bin/activate 4. Install dependencies .. code:: bash - pip install -r requirements-test.txt + pip install -r requirements.txt -5. Run tests +5. Install the local version of readchar (in edit mode so it automatically reflects changes) + +.. code:: bash + + pip install -e . + +6. Run tests .. code:: bash diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..f43c620 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = tests +addopts = -r fEsxwX -s --cov=readchar diff --git a/readchar/__init__.py b/readchar/__init__.py index f4ffd5c..956d286 100644 --- a/readchar/__init__.py +++ b/readchar/__init__.py @@ -1,3 +1,8 @@ +"""Library to easily read single chars and key strokes""" + +__version__ = "0.0.0-dev" + + from . import key from .readchar import readchar, readkey diff --git a/requirements-test.txt b/requirements.txt similarity index 52% rename from requirements-test.txt rename to requirements.txt index 462fa01..f23d1f3 100644 --- a/requirements-test.txt +++ b/requirements.txt @@ -1,4 +1,6 @@ +wheel +black flake8 +pre-commit pytest pytest-cov - diff --git a/setup.cfg b/setup.cfg index c59270b..626b39e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,57 @@ -[tool:pytest] -norecursedirs = .git venv build dist *egg -addopts = -rfEsxwX --cov readchar +[metadata] +name = readchar +author_email = miguelangel.garcia@gmail.com +license = MIT +license_files = LICENSE +version = attr: readchar.__version__ -[flake8] -exclude = **/.*,venv/**,.eggs/** +description = Library to easily read single chars and key strokes +long_description = file: README.rst +long_description_content_type = text/x-rst +keywords = + charaters + keystrokes + stdin + command line + +classifiers = + Development Status :: 5 - Production/Stable + Environment :: Console + Intended Audience :: Developers + License :: OSI Approved :: MIT License + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: Implementation :: CPython + Topic :: Software Development + Topic :: Software Development :: User Interfaces + +url = https://github.com/magmax/python-readchar +project_urls = + Download = https://pypi.org/project/readchar/#files + Bug Tracker = https://github.com/magmax/python-readchar/issues + Source Code = https://github.com/magmax/python-readchar + + +[options] +packages = find: + +python_requires = >=3.6 + +install_requires = + setuptools + wheel + +test_suite = tests +tests_require = + pytest + +[options.packages.find] +exclude = + .venv/ + .git/ + .github/ + tests/ diff --git a/setup.py b/setup.py index 6631445..28b2495 100644 --- a/setup.py +++ b/setup.py @@ -1,81 +1,7 @@ -import os -import sys -from io import open - -from setuptools import find_packages, setup -from setuptools.command.test import test as TestCommand - -github_ref = os.getenv("GITHUB_REF") -if github_ref and github_ref.startswith("refs/tags"): - version = github_ref[10:] -else: - version = "0.0.0-local" - - -def read_description(): - try: - with open("README.rst", encoding="utf8") as fd: - return fd.read() - except: # noqa - return "Error found retrieving description" - - -class PyTest(TestCommand): - user_options = [("pytest-args=", "a", "Arguments to pass to py.test")] - - def initialize_options(self): - TestCommand.initialize_options(self) - self.pytest_args = ["--cov-report=term-missing"] - - def finalize_options(self): - TestCommand.finalize_options(self) - self.test_args = [] - self.test_suite = True - - def run_tests(self): - # import here, cause outside the eggs aren't loaded - import pytest - - errno = pytest.main(self.pytest_args) - sys.exit(errno) - +from setuptools import setup setup( - name="readchar", - version=version, - description="Utilities to read single characters and key-strokes", - long_description=read_description(), - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Environment :: Console", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: Implementation :: CPython", - "Topic :: Software Development", - "Topic :: Software Development :: User Interfaces", - ], - keywords="stdin,command line", - author="Miguel Ángel García", - author_email="miguelangel.garcia@gmail.com", - url="https://github.com/magmax/python-readchar", - license="MIT", - packages=find_packages(exclude=["tests", "venv"]), - include_package_data=True, - zip_safe=False, - cmdclass={"test": PyTest}, - tests_require=[ - "pexpect", - "coverage", - "pytest", - "pytest-cov", - "wheel", - ], - install_requires=[], - setup_requires=[], + # Author can not be read from setup.cfg as it causes encoding problems during + # windows installs + author="Miguel Ángel García" ) From 4f8a3880ebcbec4a8433477e74b1472c98841eae Mon Sep 17 00:00:00 2001 From: Klaas van Schelven Date: Sat, 8 Jan 2022 11:30:29 +0100 Subject: [PATCH 05/21] swap LF and CR Their values where swaped and have been corrected as per @vanschelven PR merges and closes #69 --- readchar/key.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readchar/key.py b/readchar/key.py index 865427f..378c707 100644 --- a/readchar/key.py +++ b/readchar/key.py @@ -1,6 +1,6 @@ # common -LF = "\x0d" -CR = "\x0a" +LF = "\x0a" +CR = "\x0d" ENTER = "\x0d" BACKSPACE = "\x08" SUPR = "" From 23cdbf94783318560fa5fd835502a8ad43af092d Mon Sep 17 00:00:00 2001 From: c0d3d3v Date: Mon, 4 Jul 2022 15:36:34 +0200 Subject: [PATCH 06/21] fix __all__ https://docs.python.org/3/tutorial/modules.html#importing-from-a-package --- readchar/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/readchar/__init__.py b/readchar/__init__.py index 956d286..4e44c2a 100644 --- a/readchar/__init__.py +++ b/readchar/__init__.py @@ -1,9 +1,8 @@ """Library to easily read single chars and key strokes""" __version__ = "0.0.0-dev" +__all__ = ["readchar", "readkey", "key"] from . import key from .readchar import readchar, readkey - -__all__ = [readchar, readkey, key] From 3920ebade6e7672f55caa479f458a1b99714ae4c Mon Sep 17 00:00:00 2001 From: Jan Wille Date: Sat, 2 Jul 2022 15:35:38 +0200 Subject: [PATCH 07/21] bump pre-commit hooks and add end-of-file-fixer --- .pre-commit-config.yaml | 22 ++++++++++++---------- .yamllint.yml | 2 ++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5193a08..2b45dc5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,27 +1,29 @@ --- repos: - - repo: https://github.com/adrienverge/yamllint - rev: v1.26.3 - hooks: - - name: check YAML format - id: yamllint - - repo: https://github.com/psf/black - rev: 22.1.0 + rev: 22.6.0 hooks: - name: re-format with black id: black language_version: python3 - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.3.0 hooks: - name: remove whitespaces id: trailing-whitespace + - name: add newline to end of files + id: end-of-file-fixer - - repo: https://gitlab.com/pycqa/flake8 - rev: 21d3c70d676007470908d39b73f0521d39b3b997 + - repo: https://github.com/pycqa/flake8 + rev: 4.0.1 hooks: - name: check-format with flake8 id: flake8 + + - repo: https://github.com/adrienverge/yamllint + rev: v1.27.1 + hooks: + - name: check YAML format + id: yamllint diff --git a/.yamllint.yml b/.yamllint.yml index b7ed691..f8ff608 100644 --- a/.yamllint.yml +++ b/.yamllint.yml @@ -6,3 +6,5 @@ ignore: | rules: indentation: spaces: 2 + new-lines: + type: platform From 2f6f404fe663e42c43e4c2cc65968e933d636ae5 Mon Sep 17 00:00:00 2001 From: Jan Wille Date: Sat, 2 Jul 2022 16:11:15 +0200 Subject: [PATCH 08/21] seperate platforms to allow for easier maintanace and proper separation. Every operating system gets a seperate set of files. They are exported by the module depending on `sys.platform` closes #66, closes #62, closes #57, closes #42, closes #10, closes #9, closes #8 --- readchar/__init__.py | 11 +++- readchar/key.py | 104 +++-------------------------------- readchar/posix_key.py | 71 ++++++++++++++++++++++++ readchar/posix_read.py | 55 ++++++++++++++++++ readchar/readchar.py | 95 -------------------------------- readchar/readchar_linux.py | 17 ------ readchar/readchar_windows.py | 26 --------- readchar/win_key.py | 83 ++++++++++++++++++++++++++++ readchar/win_read.py | 36 ++++++++++++ 9 files changed, 262 insertions(+), 236 deletions(-) create mode 100644 readchar/posix_key.py create mode 100644 readchar/posix_read.py delete mode 100644 readchar/readchar.py delete mode 100644 readchar/readchar_linux.py delete mode 100644 readchar/readchar_windows.py create mode 100644 readchar/win_key.py create mode 100644 readchar/win_read.py diff --git a/readchar/__init__.py b/readchar/__init__.py index 4e44c2a..5b9e2e5 100644 --- a/readchar/__init__.py +++ b/readchar/__init__.py @@ -4,5 +4,12 @@ __all__ = ["readchar", "readkey", "key"] -from . import key -from .readchar import readchar, readkey +from sys import platform + + +if platform.startswith("linux"): + from .posix_read import readchar, readkey +elif platform in ("win32", "cygwin"): + from .win_read import readchar, readkey +else: + raise NotImplementedError(f"The platform {platform} is not supported yet") diff --git a/readchar/key.py b/readchar/key.py index 378c707..683da1d 100644 --- a/readchar/key.py +++ b/readchar/key.py @@ -1,98 +1,10 @@ -# common -LF = "\x0a" -CR = "\x0d" -ENTER = "\x0d" -BACKSPACE = "\x08" -SUPR = "" -SPACE = "\x20" -ESC = "\x1b" +# flake8: noqa E401,E403 -# CTRL -CTRL_A = "\x01" -CTRL_B = "\x02" -CTRL_C = "\x03" -CTRL_D = "\x04" -CTRL_E = "\x05" -CTRL_F = "\x06" -CTRL_G = "\x07" -CTRL_H = "\x08" -CTRL_I = "\t" -CTRL_J = "\n" -CTRL_K = "\x0b" -CTRL_L = "\x0c" -CTRL_M = "\r" -CTRL_N = "\x0e" -CTRL_O = "\x0f" -CTRL_P = "\x10" -CTRL_Q = "\x11" -CTRL_R = "\x12" -CTRL_S = "\x13" -CTRL_T = "\x14" -CTRL_U = "\x15" -CTRL_V = "\x16" -CTRL_W = "\x17" -CTRL_X = "\x18" -CTRL_Y = "\x19" -CTRL_Z = "\x1a" +from . import platform -# ALT -ALT_A = "\x1b\x61" - -# CTRL + ALT -CTRL_ALT_A = "\x1b\x01" - -# cursors -UP = "\x1b\x5b\x41" -DOWN = "\x1b\x5b\x42" -LEFT = "\x1b\x5b\x44" -RIGHT = "\x1b\x5b\x43" - -CTRL_ALT_SUPR = "\x1b\x5b\x33\x5e" - -# other -F1 = "\x1b\x4f\x50" -F2 = "\x1b\x4f\x51" -F3 = "\x1b\x4f\x52" -F4 = "\x1b\x4f\x53" -F5 = "\x1b\x4f\x31\x35\x7e" -F6 = "\x1b\x4f\x31\x37\x7e" -F7 = "\x1b\x4f\x31\x38\x7e" -F8 = "\x1b\x4f\x31\x39\x7e" -F9 = "\x1b\x4f\x32\x30\x7e" -F10 = "\x1b\x4f\x32\x31\x7e" -F11 = "\x1b\x4f\x32\x33\x7e" -F12 = "\x1b\x4f\x32\x34\x7e" - -PAGE_UP = "\x1b\x5b\x35\x7e" -PAGE_DOWN = "\x1b\x5b\x36\x7e" -HOME = "\x1b\x5b\x48" -END = "\x1b\x5b\x46" - -INSERT = "\x1b\x5b\x32\x7e" -SUPR = "\x1b\x5b\x33\x7e" - - -ESCAPE_SEQUENCES = ( - ESC, - ESC + "\x5b", - ESC + "\x5b" + "\x31", - ESC + "\x5b" + "\x32", - ESC + "\x5b" + "\x33", - ESC + "\x5b" + "\x35", - ESC + "\x5b" + "\x36", - ESC + "\x5b" + "\x31" + "\x35", - ESC + "\x5b" + "\x31" + "\x36", - ESC + "\x5b" + "\x31" + "\x37", - ESC + "\x5b" + "\x31" + "\x38", - ESC + "\x5b" + "\x31" + "\x39", - ESC + "\x5b" + "\x32" + "\x30", - ESC + "\x5b" + "\x32" + "\x31", - ESC + "\x5b" + "\x32" + "\x32", - ESC + "\x5b" + "\x32" + "\x33", - ESC + "\x5b" + "\x32" + "\x34", - ESC + "\x4f", - ESC + ESC, - ESC + ESC + "\x5b", - ESC + ESC + "\x5b" + "\x32", - ESC + ESC + "\x5b" + "\x33", -) +if platform.startswith("linux"): + from .posix_key import * +elif platform in ("win32", "cygwin"): + from .win_key import * +else: + raise NotImplementedError(f"The platform {platform} is not supported yet") diff --git a/readchar/posix_key.py b/readchar/posix_key.py new file mode 100644 index 0000000..60a73f5 --- /dev/null +++ b/readchar/posix_key.py @@ -0,0 +1,71 @@ +# common +LF = "\x0a" +CR = "\x0d" +ENTER = LF +BACKSPACE = "\x7f" +SPACE = "\x20" +ESC = "\x1b" +TAB = "\x09" + +# CTRL +CTRL_A = "\x01" +CTRL_B = "\x02" +CTRL_C = "\x03" +CTRL_D = "\x04" +CTRL_E = "\x05" +CTRL_F = "\x06" +CTRL_G = "\x07" +CTRL_H = "\x08" +CTRL_I = TAB +CTRL_J = LF +CTRL_K = "\x0b" +CTRL_L = "\x0c" +CTRL_M = CR +CTRL_N = "\x0e" +CTRL_O = "\x0f" +CTRL_P = "\x10" +CTRL_Q = "\x11" +CTRL_R = "\x12" +CTRL_S = "\x13" +CTRL_T = "\x14" +CTRL_U = "\x15" +CTRL_V = "\x16" +CTRL_W = "\x17" +CTRL_X = "\x18" +CTRL_Y = "\x19" +CTRL_Z = "\x1a" + +# cursors +UP = "\x1b\x5b\x41" +DOWN = "\x1b\x5b\x42" +LEFT = "\x1b\x5b\x44" +RIGHT = "\x1b\x5b\x43" + +# navigation keys +INSERT = "\x1b\x5b\x32\x7e" +SUPR = "\x1b\x5b\x33\x7e" +HOME = "\x1b\x5b\x48" +END = "\x1b\x5b\x46" +PAGE_UP = "\x1b\x5b\x35\x7e" +PAGE_DOWN = "\x1b\x5b\x36\x7e" + +# funcion keys +F1 = "\x1b\x4f\x50" +F2 = "\x1b\x4f\x51" +F3 = "\x1b\x4f\x52" +F4 = "\x1b\x4f\x53" +F5 = "\x1b\x4f\x31\x35\x7e" +F6 = "\x1b\x4f\x31\x37\x7e" +F7 = "\x1b\x4f\x31\x38\x7e" +F8 = "\x1b\x4f\x31\x39\x7e" +F9 = "\x1b\x4f\x32\x30\x7e" +F10 = "\x1b\x4f\x32\x31\x7e" +F11 = "\x1b\x4f\x32\x33\x7e" +F12 = "\x1b\x4f\x32\x34\x7e" + +# ALT +ALT_A = "\x1b\x61" + +# CTRL + ALT +CTRL_ALT_A = "\x1b\x01" +CTRL_ALT_SUPR = "\x1b\x5b\x33\x5e" diff --git a/readchar/posix_read.py b/readchar/posix_read.py new file mode 100644 index 0000000..b4dde27 --- /dev/null +++ b/readchar/posix_read.py @@ -0,0 +1,55 @@ +import sys +import termios +import tty +import select + + +# idea from: +# https://repolinux.wordpress.com/2012/10/09/non-blocking-read-from-stdin-in-python/ +# Thanks to REPOLINUX +def kbhit(): + return sys.stdin in select.select([sys.stdin], [], [], 0)[0] + + +# Initially taken from: +# http://code.activestate.com/recipes/134892/ +# Thanks to Danny Yoo +def readchar(blocking=True): + """Reads a single character from the input stream. Retruns None if none is avalable. + If blocking=True the function waits for the next character.""" + + if not (blocking or kbhit()): + return None + + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(sys.stdin.fileno()) + ch = sys.stdin.read(1) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + return ch + + +def readkey(): + """Get a single keypress. If an escaped key is pressed, the + following characters are read as well (see key_linux.py).""" + + c1 = readchar() + + if c1 == "\x03": + raise KeyboardInterrupt + + if c1 != "\x1B": + return c1 + + c2 = readchar() + if c2 != "\x5B": + return c1 + c2 + + c3 = readchar() + if c3 != "\x33": + return c1 + c2 + c3 + + c4 = readchar() + return c1 + c2 + c3 + c4 diff --git a/readchar/readchar.py b/readchar/readchar.py deleted file mode 100644 index f5b0cb6..0000000 --- a/readchar/readchar.py +++ /dev/null @@ -1,95 +0,0 @@ -# This file is based on this gist: -# http://code.activestate.com/recipes/134892/ -# So real authors are DannyYoo and company. -import sys - -if sys.platform.startswith("linux"): - from .readchar_linux import readchar -elif sys.platform == "darwin": - from .readchar_linux import readchar -elif sys.platform in ("win32", "cygwin"): - import msvcrt - - from . import key - from .readchar_windows import readchar -else: - raise NotImplementedError("The platform %s is not supported yet" % sys.platform) - - -if sys.platform in ("win32", "cygwin"): - # - # Windows uses scan codes for extended characters. The ordinal returned is - # 256 * the scan code. This dictionary translates scan codes to the - # unicode sequences expected by readkey. - # - # for windows scan codes see: - # https://msdn.microsoft.com/en-us/library/aa299374 - # or - # http://www.quadibloc.com/comp/scan.htm - xlate_dict = { - 13: key.ENTER, - 27: key.ESC, - 15104: key.F1, - 15360: key.F2, - 15616: key.F3, - 15872: key.F4, - 16128: key.F5, - 16384: key.F6, - 16640: key.F7, - 16896: key.F8, - 17152: key.F9, - 17408: key.F10, - 22272: key.F11, - 34528: key.F12, - 7680: key.ALT_A, - # don't have table entries for... - # CTRL_ALT_A, # Ctrl-Alt-A, etc. - # CTRL_ALT_SUPR, - # CTRL-F1 - 21216: key.INSERT, - 21472: key.SUPR, # key.py uses SUPR, not DELETE - 18912: key.PAGE_UP, - 20960: key.PAGE_DOWN, - 18400: key.HOME, - 20448: key.END, - 18432: key.UP, # 72 * 256 - 20480: key.DOWN, # 80 * 256 - 19200: key.LEFT, # 75 * 256 - 19712: key.RIGHT, # 77 * 256 - } - - def readkey(getchar_fn=None): - # Get a single character on Windows. if an extended key is pressed, the - # Windows scan code is translated into a the unicode sequences readchar - # expects (see key.py). - while True: - if msvcrt.kbhit(): - ch = msvcrt.getch() - a = ord(ch) - if a == 0 or a == 224: - b = ord(msvcrt.getch()) - x = a + (b * 256) - - try: - return xlate_dict[x] - except KeyError: - return None - return x - else: - return ch.decode() - -else: - - def readkey(getchar_fn=None): - getchar = getchar_fn or readchar - c1 = getchar() - if ord(c1) != 0x1B: - return c1 - c2 = getchar() - if ord(c2) != 0x5B: - return c1 + c2 - c3 = getchar() - if ord(c3) != 0x33: - return c1 + c2 + c3 - c4 = getchar() - return c1 + c2 + c3 + c4 diff --git a/readchar/readchar_linux.py b/readchar/readchar_linux.py deleted file mode 100644 index 3023c44..0000000 --- a/readchar/readchar_linux.py +++ /dev/null @@ -1,17 +0,0 @@ -# Initially taken from: -# http://code.activestate.com/recipes/134892/ -# Thanks to Danny Yoo -import sys -import termios -import tty - - -def readchar(): - fd = sys.stdin.fileno() - old_settings = termios.tcgetattr(fd) - try: - tty.setraw(sys.stdin.fileno()) - ch = sys.stdin.read(1) - finally: - termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) - return ch diff --git a/readchar/readchar_windows.py b/readchar/readchar_windows.py deleted file mode 100644 index d83a22f..0000000 --- a/readchar/readchar_windows.py +++ /dev/null @@ -1,26 +0,0 @@ -# Initially taken from: -# http://code.activestate.com/recipes/134892/#c9 -# Thanks to Stephen Chappell -import msvcrt -import sys - -win_encoding = "mbcs" - - -XE0_OR_00 = "\x00\xe0" - - -def readchar(blocking=False): - "Get a single character on Windows." - - while msvcrt.kbhit(): - msvcrt.getch() - ch = msvcrt.getch() - # print('ch={}, type(ch)={}'.format(ch, type(ch))) - # while ch.decode(win_encoding) in unicode('\x00\xe0', win_encoding): - while ch.decode(win_encoding) in XE0_OR_00: - # print('found x00 or xe0') - msvcrt.getch() - ch = msvcrt.getch() - - return ch if sys.version_info.major > 2 else ch.decode(encoding=win_encoding) diff --git a/readchar/win_key.py b/readchar/win_key.py new file mode 100644 index 0000000..c02a36b --- /dev/null +++ b/readchar/win_key.py @@ -0,0 +1,83 @@ +# common +LF = "\x0a" +CR = "\x0d" +ENTER = CR +BACKSPACE = "\x08" +SPACE = "\x20" +ESC = "\x1b" +TAB = "\x09" + +# CTRL +CTRL_A = "\x01" +CTRL_B = "\x02" +CTRL_C = "\x03" +CTRL_D = "\x04" +CTRL_E = "\x05" +CTRL_F = "\x06" +CTRL_G = "\x07" +CTRL_H = BACKSPACE +CTRL_I = TAB +CTRL_J = LF +CTRL_K = "\x0b" +CTRL_L = "\x0c" +CTRL_M = CR +CTRL_N = "\x0e" +CTRL_O = "\x0f" +CTRL_P = "\x10" +CTRL_Q = "\x11" +CTRL_R = "\x12" +CTRL_S = "\x13" +CTRL_T = "\x14" +CTRL_U = "\x15" +CTRL_V = "\x16" +CTRL_W = "\x17" +CTRL_X = "\x18" +CTRL_Y = "\x19" +CTRL_Z = "\x1a" + +# Windows uses scan codes for extended characters. This dictionary +# translates the second half of the scan codes of special Keys +# into the corresponding variable used by readchar. +# +# for windows scan codes see: +# https://msdn.microsoft.com/en-us/library/aa299374 +# or +# https://www.freepascal.org/docs-html/rtl/keyboard/kbdscancode.html + +# cursors +UP = "\x00\x48" +DOWN = "\x00\x50" +LEFT = "\x00\x4b" +RIGHT = "\x00\x4d" + +# navigation keys +INSERT = "\x00\x52" +SUPR = "\x00\x53" +HOME = "\x00\x47" +END = "\x00\x4f" +PAGE_UP = "\x00\x49" +PAGE_DOWN = "\x00\x51" + +# funcion keys +F1 = "\x00\x3b" +F2 = "\x00\x3c" +F3 = "\x00\x3d" +F4 = "\x00\x3e" +F5 = "\x00\x3f" +F6 = "\x00\x40" +F7 = "\x00\x41" +F8 = "\x00\x42" +F9 = "\x00\x43" +F10 = "\x00\x44" +F11 = "\x00\x85" # only in second source +F12 = "\x00\x86" # only in second source + +# other +ESC_2 = "\x00\x01" +ENTER_2 = "\x00\x1c" + +# don't have table entries for... +# ALT_[A-Z] +# CTRL_ALT_A, # Ctrl-Alt-A, etc. +# CTRL_ALT_SUPR, +# CTRL-F1 diff --git a/readchar/win_read.py b/readchar/win_read.py new file mode 100644 index 0000000..64bde86 --- /dev/null +++ b/readchar/win_read.py @@ -0,0 +1,36 @@ +# This file is based on this gist: +# http://code.activestate.com/recipes/134892/ +# So real authors are DannyYoo and company. + +import msvcrt + + +def readchar(blocking=True): + """Reads a single character from the input stream. Retruns None if none is avalable. + If blocking=True the function waits for the next character.""" + + if not (blocking or msvcrt.kbhit()): + return None + + # manual byte decoding because some bytes in windows are not utf-8 encodable. + return chr(int.from_bytes(msvcrt.getch(), "big")) + + +def readkey(): + """Get a single character on Windows. If an escaped key is pressed, the + Windows scan code is translated into a the unicode sequences readchar + expects (see key_windows.py).""" + + ch = readchar() + + if ch == "\x03": + raise KeyboardInterrupt + + # if it is a normal character: + if ch not in "\x00\xe0": + return ch + + # if it is a scpeal key, read second half: + ch2 = readchar() + + return "\x00" + ch2 From 3889e1ef480c67846490a96afd30e4b47ed81778 Mon Sep 17 00:00:00 2001 From: Jan Wille Date: Fri, 29 Apr 2022 23:38:22 +0200 Subject: [PATCH 09/21] keys: DELETE in addition to SUPR --- readchar/posix_key.py | 1 + readchar/win_key.py | 1 + 2 files changed, 2 insertions(+) diff --git a/readchar/posix_key.py b/readchar/posix_key.py index 60a73f5..c21416f 100644 --- a/readchar/posix_key.py +++ b/readchar/posix_key.py @@ -44,6 +44,7 @@ # navigation keys INSERT = "\x1b\x5b\x32\x7e" SUPR = "\x1b\x5b\x33\x7e" +DELETE = SUPR HOME = "\x1b\x5b\x48" END = "\x1b\x5b\x46" PAGE_UP = "\x1b\x5b\x35\x7e" diff --git a/readchar/win_key.py b/readchar/win_key.py index c02a36b..eab9406 100644 --- a/readchar/win_key.py +++ b/readchar/win_key.py @@ -53,6 +53,7 @@ # navigation keys INSERT = "\x00\x52" SUPR = "\x00\x53" +DELETE = SUPR HOME = "\x00\x47" END = "\x00\x4f" PAGE_UP = "\x00\x49" From 30580720d1bbe284f113f308e1084104cbf283c8 Mon Sep 17 00:00:00 2001 From: Jan Wille Date: Sat, 2 Jul 2022 17:24:16 +0200 Subject: [PATCH 10/21] pytest setup for multiple platforms The new setup uses a seperate folder for every operation system that tests are provided for. Test for a different operating system than the one currently running will be ignored. --- tests/__init__.py | 0 tests/acceptance/__init__.py | 0 tests/posix/conftest.py | 34 ++++++++++++++++ tests/posix/test_import.py | 19 +++++++++ tests/posix/test_keys.py | 17 ++++++++ tests/posix/test_readchar.py | 64 ++++++++++++++++++++++++++++++ tests/posix/test_readkey.py | 69 +++++++++++++++++++++++++++++++++ tests/unit/__init__.py | 0 tests/unit/test_key.py | 17 -------- tests/unit/test_readkey.py | 54 -------------------------- tests/windows/conftest.py | 22 +++++++++++ tests/windows/test_import.py | 19 +++++++++ tests/windows/test_keys.py | 51 ++++++++++++++++++++++++ tests/windows/test_readchar.py | 62 +++++++++++++++++++++++++++++ tests/windows/test_readkey.py | 71 ++++++++++++++++++++++++++++++++++ 15 files changed, 428 insertions(+), 71 deletions(-) delete mode 100644 tests/__init__.py delete mode 100644 tests/acceptance/__init__.py create mode 100644 tests/posix/conftest.py create mode 100644 tests/posix/test_import.py create mode 100644 tests/posix/test_keys.py create mode 100644 tests/posix/test_readchar.py create mode 100644 tests/posix/test_readkey.py delete mode 100644 tests/unit/__init__.py delete mode 100644 tests/unit/test_key.py delete mode 100644 tests/unit/test_readkey.py create mode 100644 tests/windows/conftest.py create mode 100644 tests/windows/test_import.py create mode 100644 tests/windows/test_keys.py create mode 100644 tests/windows/test_readchar.py create mode 100644 tests/windows/test_readkey.py diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/acceptance/__init__.py b/tests/acceptance/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/posix/conftest.py b/tests/posix/conftest.py new file mode 100644 index 0000000..8f4c2ca --- /dev/null +++ b/tests/posix/conftest.py @@ -0,0 +1,34 @@ +import pytest +import sys +import select + + +# ignore all tests in this folder if not on linux +def pytest_ignore_collect(path, config): + if not sys.platform.startswith("linux"): + return True + + +@pytest.fixture +def patched_stdin(): + class mocked_stdin: + buffer = [] + + def push(self, string): + for c in string: + self.buffer.append(c) + + def read(self, n): + string = "" + for i in range(n): + string += self.buffer.pop(0) + return string + + def mock_select(a, b, c, d): + return [(sys.stdin)] + + mock = mocked_stdin() + with pytest.MonkeyPatch.context() as mp: + mp.setattr(sys.stdin, "read", mock.read) + mp.setattr(select, "select", mock_select) + yield mock diff --git a/tests/posix/test_import.py b/tests/posix/test_import.py new file mode 100644 index 0000000..4b73256 --- /dev/null +++ b/tests/posix/test_import.py @@ -0,0 +1,19 @@ +import readchar + + +def test_readcharImport(): + + assert readchar.readchar == readchar.posix_read.readchar + + +def test_readkeyImport(): + + assert readchar.readkey == readchar.posix_read.readkey + + +def test_keyImport(): + + a = {k: v for k, v in vars(readchar.key).items() if not k.startswith("__")} + del a["platform"] + b = {k: v for k, v in vars(readchar.posix_key).items() if not k.startswith("__")} + assert a == b diff --git a/tests/posix/test_keys.py b/tests/posix/test_keys.py new file mode 100644 index 0000000..dd3e212 --- /dev/null +++ b/tests/posix/test_keys.py @@ -0,0 +1,17 @@ +from readchar import key + + +def test_character_length_1(): + assert 1 == len(key.CTRL_A) + + +def test_character_length_2(): + assert 2 == len(key.ALT_A) + + +def test_character_length_3(): + assert 3 == len(key.UP) + + +def test_character_length_4(): + assert 4 == len(key.CTRL_ALT_SUPR) diff --git a/tests/posix/test_readchar.py b/tests/posix/test_readchar.py new file mode 100644 index 0000000..c88b116 --- /dev/null +++ b/tests/posix/test_readchar.py @@ -0,0 +1,64 @@ +import pytest +from string import printable +from readchar import readchar, key + + +@pytest.mark.skip(reason="These work localy, but not on GitHub...") +@pytest.mark.parametrize("c", printable) +def test_printableCharacters(patched_stdin, c): + patched_stdin.push(c) + assert c == readchar(blocking=True) + + +@pytest.mark.skip(reason="I have no idea why these dont work...") +@pytest.mark.parametrize( + ["seq", "key"], + [ + ("\x0a", key.LF), + ("\x0a", key.ENTER), + ("\x0d", key.CR), + ("\x08", key.BACKSPACE), + ("\x20", key.SPACE), + ("\x1b", key.ESC), + ("\x09", key.TAB), + ], +) +def test_controlCharacters(seq, key, patched_stdin): + patched_stdin.push(seq) + assert key == readchar() + + +@pytest.mark.parametrize( + ["seq", "key"], + [ + ("\x01", key.CTRL_A), + ("\x02", key.CTRL_B), + ("\x03", key.CTRL_C), + ("\x04", key.CTRL_D), + ("\x05", key.CTRL_E), + ("\x06", key.CTRL_F), + ("\x07", key.CTRL_G), + ("\x08", key.CTRL_H), + ("\x09", key.CTRL_I), + ("\x0a", key.CTRL_J), + ("\x0b", key.CTRL_K), + ("\x0c", key.CTRL_L), + ("\x0d", key.CTRL_M), + ("\x0e", key.CTRL_N), + ("\x0f", key.CTRL_O), + ("\x10", key.CTRL_P), + ("\x11", key.CTRL_Q), + ("\x12", key.CTRL_R), + ("\x13", key.CTRL_S), + ("\x14", key.CTRL_T), + ("\x15", key.CTRL_U), + ("\x16", key.CTRL_V), + ("\x17", key.CTRL_W), + ("\x18", key.CTRL_X), + ("\x19", key.CTRL_Y), + ("\x1a", key.CTRL_Z), + ], +) +def test_CTRL_Characters(seq, key, patched_stdin): + patched_stdin.push(seq) + assert key == readchar() diff --git a/tests/posix/test_readkey.py b/tests/posix/test_readkey.py new file mode 100644 index 0000000..56ee0df --- /dev/null +++ b/tests/posix/test_readkey.py @@ -0,0 +1,69 @@ +import pytest +from readchar import readkey, key + + +@pytest.mark.skip(reason="These work localy, but not on GitHub...") +def test_KeyboardInterrupt(patched_stdin): + patched_stdin.push("\x03") + with pytest.raises(KeyboardInterrupt): + readkey() + + +def test_singleCharacter(patched_stdin): + patched_stdin.push("a") + assert "a" == readkey() + + +@pytest.mark.skip(reason="I dont know enough about linux to make these work...") +@pytest.mark.parametrize( + ["seq", "key"], + [ + ("\x1b\x5b\x41", key.UP), + ("\x1b\x5b\x42", key.DOWN), + ("\x1b\x5b\x44", key.LEFT), + ("\x1b\x5b\x43", key.RIGHT), + ], +) +def test_arrowKeys(seq, key, patched_stdin): + patched_stdin.push(seq) + assert key == readkey() + + +@pytest.mark.skip(reason="I dont know enough about linux to make these work...") +@pytest.mark.parametrize( + ["seq", "key"], + [ + ("\x1b\x5b\x32\x7e", key.INSERT), + ("\x1b\x5b\x33\x7e", key.SUPR), + ("\x1b\x5b\x48", key.HOME), + ("\x1b\x5b\x46", key.END), + ("\x1b\x5b\x35\x7e", key.PAGE_UP), + ("\x1b\x5b\x36\x7e", key.PAGE_DOWN), + ], +) +def test_specialKeys(seq, key, patched_stdin): + patched_stdin.push(seq) + assert key == readkey() + + +@pytest.mark.skip(reason="I dont know enough about linux to make these work...") +@pytest.mark.parametrize( + ["seq", "key"], + [ + (key.F1, "\x1b\x4f\x50"), + (key.F2, "\x1b\x4f\x51"), + (key.F3, "\x1b\x4f\x52"), + (key.F4, "\x1b\x4f\x53"), + (key.F5, "\x1b\x4f\x31\x35\x7e"), + (key.F6, "\x1b\x4f\x31\x37\x7e"), + (key.F7, "\x1b\x4f\x31\x38\x7e"), + (key.F8, "\x1b\x4f\x31\x39\x7e"), + (key.F9, "\x1b\x4f\x32\x30\x7e"), + (key.F10, "\x1b\x4f\x32\x31\x7e"), + (key.F11, "\x1b\x4f\x32\x33\x7e"), + (key.F12, "\x1b\x4f\x32\x34\x7e"), + ], +) +def test_functionKeys(seq, key, patched_stdin): + patched_stdin.push(seq) + assert key == readkey() diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/unit/test_key.py b/tests/unit/test_key.py deleted file mode 100644 index ef605f3..0000000 --- a/tests/unit/test_key.py +++ /dev/null @@ -1,17 +0,0 @@ -import unittest - -from readchar import key - - -class KeyTest(unittest.TestCase): - def test_character_length_1(self): - self.assertEqual(1, len(key.CTRL_A)) - - def test_character_length_2(self): - self.assertEqual(2, len(key.ALT_A)) - - def test_character_length_3(self): - self.assertEqual(3, len(key.UP)) - - def test_character_length_4(self): - self.assertEqual(4, len(key.CTRL_ALT_SUPR)) diff --git a/tests/unit/test_readkey.py b/tests/unit/test_readkey.py deleted file mode 100644 index 5f52727..0000000 --- a/tests/unit/test_readkey.py +++ /dev/null @@ -1,54 +0,0 @@ -import unittest - -from readchar import readkey - - -def readchar_fn_factory(stream): - - v = [x for x in stream] - - def inner(): - return v.pop(0) - - return inner - - -class ReadKeyTest(unittest.TestCase): - def test_basic_character(self): - getchar_fn = readchar_fn_factory("a") - - result = readkey(getchar_fn) - - self.assertEqual("a", result) - - def test_string_instead_of_char(self): - char = "a" - getchar_fn = readchar_fn_factory(char + "bcde") - - result = readkey(getchar_fn) - - self.assertEqual(char, result) - - def test_special_combo_character(self): - char = "\x1b\x01" - getchar_fn = readchar_fn_factory(char + "foo") - - result = readkey(getchar_fn) - - self.assertEqual(char, result) - - def test_special_key(self): - char = "\x1b\x5b\x41" - getchar_fn = readchar_fn_factory(char + "foo") - - result = readkey(getchar_fn) - - self.assertEqual(char, result) - - def test_special_key_combo(self): - char = "\x1b\x5b\x33\x5e" - getchar_fn = readchar_fn_factory(char + "foo") - - result = readkey(getchar_fn) - - self.assertEqual(char, result) diff --git a/tests/windows/conftest.py b/tests/windows/conftest.py new file mode 100644 index 0000000..94299d4 --- /dev/null +++ b/tests/windows/conftest.py @@ -0,0 +1,22 @@ +import pytest +import sys + + +if sys.platform in ("win32", "cygwin"): + import msvcrt + + +# ignore all tests in this folder if not on windows +def pytest_ignore_collect(path, config): + if sys.platform not in ("win32", "cygwin"): + return True + + +@pytest.fixture +def patched_stdin(): + class mocked_stdin: + def push(self, string): + for c in string: + msvcrt.ungetch(ord(c).to_bytes(1, "big")) + + return mocked_stdin() diff --git a/tests/windows/test_import.py b/tests/windows/test_import.py new file mode 100644 index 0000000..0705825 --- /dev/null +++ b/tests/windows/test_import.py @@ -0,0 +1,19 @@ +import readchar + + +def test_readcharImport(): + + assert readchar.readchar == readchar.win_read.readchar + + +def test_readkeyImport(): + + assert readchar.readkey == readchar.win_read.readkey + + +def test_keyImport(): + + a = {k: v for k, v in vars(readchar.key).items() if not k.startswith("__")} + del a["platform"] + b = {k: v for k, v in vars(readchar.win_key).items() if not k.startswith("__")} + assert a == b diff --git a/tests/windows/test_keys.py b/tests/windows/test_keys.py new file mode 100644 index 0000000..55a8d4a --- /dev/null +++ b/tests/windows/test_keys.py @@ -0,0 +1,51 @@ +import pytest +from readchar import key as keys + + +defaultKeys = ["LF", "CR", "ENTER", "BACKSPACE", "SPACE", "ESC", "TAB"] + + +@pytest.mark.parametrize("key", defaultKeys) +def test_defaultKeysExists(key): + assert key in keys.__dict__ + + +@pytest.mark.parametrize("key", defaultKeys) +def test_defaultKeysLength(key): + assert 1 == len(keys.__dict__[key]) + + +specialKeys = [ + "INSERT", + "SUPR", + "PAGE_UP", + "PAGE_DOWN", + "HOME", + "END", + "UP", + "DOWN", + "LEFT", + "RIGHT", + "F1", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "F10", + "F11", + "F12", +] + + +@pytest.mark.parametrize("key", specialKeys) +def test_specialKeysExists(key): + assert key in keys.__dict__ + + +@pytest.mark.parametrize("key", specialKeys) +def test_specialKeysLength(key): + assert 2 == len(keys.__dict__[key]) diff --git a/tests/windows/test_readchar.py b/tests/windows/test_readchar.py new file mode 100644 index 0000000..b1fd7bd --- /dev/null +++ b/tests/windows/test_readchar.py @@ -0,0 +1,62 @@ +import pytest +from string import printable +from readchar import readchar, key + + +@pytest.mark.parametrize("c", printable) +def test_printableCharacters(patched_stdin, c): + patched_stdin.push(c) + assert c == readchar() + + +@pytest.mark.parametrize( + ["seq", "key"], + [ + ("\n", key.LF), + ("\r", key.ENTER), + ("\r", key.CR), + ("\x08", key.BACKSPACE), + ("\x20", key.SPACE), + ("\x1b", key.ESC), + ("\t", key.TAB), + ], +) +def test_spectialCharacters(seq, key, patched_stdin): + patched_stdin.push(seq) + assert key == readchar() + + +@pytest.mark.parametrize( + ["seq", "key"], + [ + ("\x01", key.CTRL_A), + ("\x02", key.CTRL_B), + ("\x03", key.CTRL_C), + ("\x04", key.CTRL_D), + ("\x05", key.CTRL_E), + ("\x06", key.CTRL_F), + ("\x07", key.CTRL_G), + ("\x08", key.CTRL_H), + ("\x09", key.CTRL_I), + ("\x0a", key.CTRL_J), + ("\x0b", key.CTRL_K), + ("\x0c", key.CTRL_L), + ("\x0d", key.CTRL_M), + ("\x0e", key.CTRL_N), + ("\x0f", key.CTRL_O), + ("\x10", key.CTRL_P), + ("\x11", key.CTRL_Q), + ("\x12", key.CTRL_R), + ("\x13", key.CTRL_S), + ("\x14", key.CTRL_T), + ("\x15", key.CTRL_U), + ("\x16", key.CTRL_V), + ("\x17", key.CTRL_W), + ("\x18", key.CTRL_X), + ("\x19", key.CTRL_Y), + ("\x1a", key.CTRL_Z), + ], +) +def test_CTRL_Characters(seq, key, patched_stdin): + patched_stdin.push(seq) + assert key == readchar() diff --git a/tests/windows/test_readkey.py b/tests/windows/test_readkey.py new file mode 100644 index 0000000..28fb48b --- /dev/null +++ b/tests/windows/test_readkey.py @@ -0,0 +1,71 @@ +import pytest +from readchar import readkey, key + + +def test_KeyboardInterrupt(patched_stdin): + patched_stdin.push("\x03") + with pytest.raises(KeyboardInterrupt): + readkey() + + +def test_singleCharacter(patched_stdin): + patched_stdin.push("a") + assert "a" == readkey() + + +# for windows scan codes see: +# https://msdn.microsoft.com/en-us/library/aa299374 +# or +# https://www.freepascal.org/docs-html/rtl/keyboard/kbdscancode.html + + +@pytest.mark.parametrize( + ["seq", "key"], + [ + ("\x00\x48", key.UP), + ("\x00\x50", key.DOWN), + ("\x00\x4b", key.LEFT), + ("\x00\x4d", key.RIGHT), + ], +) +def test_arrowKeys(seq, key, patched_stdin): + patched_stdin.push(seq) + assert key == readkey() + + +@pytest.mark.parametrize( + ["seq", "key"], + [ + ("\x00\x52", key.INSERT), + ("\x00\x53", key.SUPR), + ("\x00\x47", key.HOME), + ("\x00\x4f", key.END), + ("\x00\x49", key.PAGE_UP), + ("\x00\x51", key.PAGE_DOWN), + ], +) +def test_specialKeys(seq, key, patched_stdin): + patched_stdin.push(seq) + assert key == readkey() + + +@pytest.mark.parametrize( + ["seq", "key"], + [ + ("\x00\x3b", key.F1), + ("\x00\x3c", key.F2), + ("\x00\x3d", key.F3), + ("\x00\x3e", key.F4), + ("\x00\x3f", key.F5), + ("\x00\x40", key.F6), + ("\x00\x41", key.F7), + ("\x00\x42", key.F8), + ("\x00\x43", key.F9), + ("\x00\x44", key.F10), + ("\x00\x85", key.F11), + ("\x00\x86", key.F12), + ], +) +def test_functionKeys(seq, key, patched_stdin): + patched_stdin.push(seq) + assert key == readkey() From 7ab3b036366a2013b14f817bd3bf23372e46acfe Mon Sep 17 00:00:00 2001 From: c0d3d3v Date: Fri, 29 Apr 2022 22:53:18 +0200 Subject: [PATCH 11/21] linux: fix tests and allow readkey to read all keys --- readchar/posix_read.py | 10 +++++++--- tests/posix/conftest.py | 24 ++++++++++++++++++++---- tests/posix/test_readchar.py | 4 +--- tests/posix/test_readkey.py | 4 ---- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/readchar/posix_read.py b/readchar/posix_read.py index b4dde27..783d95c 100644 --- a/readchar/posix_read.py +++ b/readchar/posix_read.py @@ -44,12 +44,16 @@ def readkey(): return c1 c2 = readchar() - if c2 != "\x5B": + if c2 not in "\x4F\x5B": return c1 + c2 c3 = readchar() - if c3 != "\x33": + if c3 not in "\x31\x32\x33\x35\x36": return c1 + c2 + c3 c4 = readchar() - return c1 + c2 + c3 + c4 + if c2 != "\x4F" or c4 not in "\x30\x31\x33\x34\x35\x37\x38\x39": + return c1 + c2 + c3 + c4 + + c5 = readchar() + return c1 + c2 + c3 + c4 + c5 diff --git a/tests/posix/conftest.py b/tests/posix/conftest.py index 8f4c2ca..30e7ed8 100644 --- a/tests/posix/conftest.py +++ b/tests/posix/conftest.py @@ -1,6 +1,10 @@ import pytest import sys -import select + +if sys.platform.startswith("linux"): + import termios + import tty + import readchar.posix_read as linux_read # ignore all tests in this folder if not on linux @@ -24,11 +28,23 @@ def read(self, n): string += self.buffer.pop(0) return string - def mock_select(a, b, c, d): - return [(sys.stdin)] + def mock_tcgetattr(fd): + return None + + def mock_tcsetattr(fd, TCSADRAIN, old_settings): + return None + + def mock_setraw(fd): + return None + + def mock_kbhit(): + return True mock = mocked_stdin() with pytest.MonkeyPatch.context() as mp: mp.setattr(sys.stdin, "read", mock.read) - mp.setattr(select, "select", mock_select) + mp.setattr(termios, "tcgetattr", mock_tcgetattr) + mp.setattr(termios, "tcsetattr", mock_tcsetattr) + mp.setattr(tty, "setraw", mock_setraw) + mp.setattr(linux_read, "kbhit", mock_kbhit) yield mock diff --git a/tests/posix/test_readchar.py b/tests/posix/test_readchar.py index c88b116..4c35d49 100644 --- a/tests/posix/test_readchar.py +++ b/tests/posix/test_readchar.py @@ -3,21 +3,19 @@ from readchar import readchar, key -@pytest.mark.skip(reason="These work localy, but not on GitHub...") @pytest.mark.parametrize("c", printable) def test_printableCharacters(patched_stdin, c): patched_stdin.push(c) assert c == readchar(blocking=True) -@pytest.mark.skip(reason="I have no idea why these dont work...") @pytest.mark.parametrize( ["seq", "key"], [ ("\x0a", key.LF), ("\x0a", key.ENTER), ("\x0d", key.CR), - ("\x08", key.BACKSPACE), + ("\x7f", key.BACKSPACE), ("\x20", key.SPACE), ("\x1b", key.ESC), ("\x09", key.TAB), diff --git a/tests/posix/test_readkey.py b/tests/posix/test_readkey.py index 56ee0df..43c613e 100644 --- a/tests/posix/test_readkey.py +++ b/tests/posix/test_readkey.py @@ -2,7 +2,6 @@ from readchar import readkey, key -@pytest.mark.skip(reason="These work localy, but not on GitHub...") def test_KeyboardInterrupt(patched_stdin): patched_stdin.push("\x03") with pytest.raises(KeyboardInterrupt): @@ -14,7 +13,6 @@ def test_singleCharacter(patched_stdin): assert "a" == readkey() -@pytest.mark.skip(reason="I dont know enough about linux to make these work...") @pytest.mark.parametrize( ["seq", "key"], [ @@ -29,7 +27,6 @@ def test_arrowKeys(seq, key, patched_stdin): assert key == readkey() -@pytest.mark.skip(reason="I dont know enough about linux to make these work...") @pytest.mark.parametrize( ["seq", "key"], [ @@ -46,7 +43,6 @@ def test_specialKeys(seq, key, patched_stdin): assert key == readkey() -@pytest.mark.skip(reason="I dont know enough about linux to make these work...") @pytest.mark.parametrize( ["seq", "key"], [ From 419a6610d985bf642794c0ec7487a21f6dcded5f Mon Sep 17 00:00:00 2001 From: c0d3d3v Date: Wed, 4 May 2022 15:30:01 +0200 Subject: [PATCH 12/21] linux: fix function keys f5 to f12 --- readchar/posix_key.py | 16 ++++++++-------- readchar/posix_read.py | 2 +- tests/posix/test_readkey.py | 16 ++++++++-------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/readchar/posix_key.py b/readchar/posix_key.py index c21416f..a1effc2 100644 --- a/readchar/posix_key.py +++ b/readchar/posix_key.py @@ -55,14 +55,14 @@ F2 = "\x1b\x4f\x51" F3 = "\x1b\x4f\x52" F4 = "\x1b\x4f\x53" -F5 = "\x1b\x4f\x31\x35\x7e" -F6 = "\x1b\x4f\x31\x37\x7e" -F7 = "\x1b\x4f\x31\x38\x7e" -F8 = "\x1b\x4f\x31\x39\x7e" -F9 = "\x1b\x4f\x32\x30\x7e" -F10 = "\x1b\x4f\x32\x31\x7e" -F11 = "\x1b\x4f\x32\x33\x7e" -F12 = "\x1b\x4f\x32\x34\x7e" +F5 = "\x1b\x5b\x31\x35\x7e" +F6 = "\x1b\x5b\x31\x37\x7e" +F7 = "\x1b\x5b\x31\x38\x7e" +F8 = "\x1b\x5b\x31\x39\x7e" +F9 = "\x1b\x5b\x32\x30\x7e" +F10 = "\x1b\x5b\x32\x31\x7e" +F11 = "\x1b\x5b\x32\x33\x7e" +F12 = "\x1b\x5b\x32\x34\x7e" # ALT ALT_A = "\x1b\x61" diff --git a/readchar/posix_read.py b/readchar/posix_read.py index 783d95c..82315e6 100644 --- a/readchar/posix_read.py +++ b/readchar/posix_read.py @@ -52,7 +52,7 @@ def readkey(): return c1 + c2 + c3 c4 = readchar() - if c2 != "\x4F" or c4 not in "\x30\x31\x33\x34\x35\x37\x38\x39": + if c4 not in "\x30\x31\x33\x34\x35\x37\x38\x39": return c1 + c2 + c3 + c4 c5 = readchar() diff --git a/tests/posix/test_readkey.py b/tests/posix/test_readkey.py index 43c613e..d913bd3 100644 --- a/tests/posix/test_readkey.py +++ b/tests/posix/test_readkey.py @@ -50,14 +50,14 @@ def test_specialKeys(seq, key, patched_stdin): (key.F2, "\x1b\x4f\x51"), (key.F3, "\x1b\x4f\x52"), (key.F4, "\x1b\x4f\x53"), - (key.F5, "\x1b\x4f\x31\x35\x7e"), - (key.F6, "\x1b\x4f\x31\x37\x7e"), - (key.F7, "\x1b\x4f\x31\x38\x7e"), - (key.F8, "\x1b\x4f\x31\x39\x7e"), - (key.F9, "\x1b\x4f\x32\x30\x7e"), - (key.F10, "\x1b\x4f\x32\x31\x7e"), - (key.F11, "\x1b\x4f\x32\x33\x7e"), - (key.F12, "\x1b\x4f\x32\x34\x7e"), + (key.F5, "\x1b\x5b\x31\x35\x7e"), + (key.F6, "\x1b\x5b\x31\x37\x7e"), + (key.F7, "\x1b\x5b\x31\x38\x7e"), + (key.F8, "\x1b\x5b\x31\x39\x7e"), + (key.F9, "\x1b\x5b\x32\x30\x7e"), + (key.F10, "\x1b\x5b\x32\x31\x7e"), + (key.F11, "\x1b\x5b\x32\x33\x7e"), + (key.F12, "\x1b\x5b\x32\x34\x7e"), ], ) def test_functionKeys(seq, key, patched_stdin): From 2cb69f053006a3f4430c248003cc4a6c97e2ae49 Mon Sep 17 00:00:00 2001 From: Jan Wille Date: Sat, 2 Jul 2022 17:21:26 +0200 Subject: [PATCH 13/21] improved manual testscript The testscript now automatically knows of all special keys listed in the `key.py` submodule. It additionally outputs the hex representaion of all detected keys --- test.py | 39 --------------------------------------- tests/manual-test.py | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 39 deletions(-) delete mode 100644 test.py create mode 100644 tests/manual-test.py diff --git a/test.py b/test.py deleted file mode 100644 index d78b18f..0000000 --- a/test.py +++ /dev/null @@ -1,39 +0,0 @@ -import readchar.key -import readchar.readchar - -decode_dict = { - readchar.key.ESC: "ESC", - readchar.key.UP: "UP", - readchar.key.DOWN: "DOWN", - readchar.key.LEFT: "LEFT", - readchar.key.RIGHT: "RIGHT", - readchar.key.PAGE_UP: "PAGE_UP", - readchar.key.PAGE_DOWN: "PAGE_DOWN", - readchar.key.HOME: "HOME", - readchar.key.END: "END", - readchar.key.INSERT: "INSERT", - readchar.key.SUPR: "DELETE", - readchar.key.F1: "F1", - readchar.key.F2: "F2", - readchar.key.F3: "F3", - readchar.key.F4: "F4", - readchar.key.F5: "F5", - readchar.key.F6: "F6", - readchar.key.F7: "F7", - readchar.key.F8: "F8", - readchar.key.F9: "F9", - readchar.key.F10: "F10", - readchar.key.F12: "F12", - readchar.key.ALT_A: "ALT_A", -} - -while True: - c = readchar.readkey() - - if c in decode_dict: - print("got {}".format(decode_dict[c])) - else: - print(c) - - if c == "d": - break diff --git a/tests/manual-test.py b/tests/manual-test.py new file mode 100644 index 0000000..9c35649 --- /dev/null +++ b/tests/manual-test.py @@ -0,0 +1,17 @@ +from readchar import key, readkey + + +# construct an inverted code -> key-name mapping +# we need to revese the items so that aliases won't overrive the original name later on +known_keys = {v: k for k, v in reversed(vars(key).items()) if not k.startswith("__")} + + +while True: + data = readkey() + + if data in known_keys: + print(f"got {known_keys[data]}", end="") + else: + print(data, end="") + + print(" - " + "".join([f"\\x{ord(c):02x}" for c in data])) From e0aa5684051f028d6f56518920c889e16b3b7922 Mon Sep 17 00:00:00 2001 From: Jan Wille Date: Sun, 10 Jul 2022 19:04:09 +0200 Subject: [PATCH 14/21] workflows: automated testing for both platforms github will now run precommit and pytest on all pushes and PRs automatically. --- .github/workflows/pre-commit.yml | 18 --------- .github/workflows/run-tests.yml | 65 ++++++++++++++++++++++---------- 2 files changed, 46 insertions(+), 37 deletions(-) delete mode 100644 .github/workflows/pre-commit.yml diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml deleted file mode 100644 index 55e230b..0000000 --- a/.github/workflows/pre-commit.yml +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: pre-commit - -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - pre-commit: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - - uses: pre-commit/action@v2.0.3 diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 7a0502e..ea19c14 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -4,40 +4,67 @@ # For more information see: # https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions -name: Python package +name: Tests on: - push: - branches: - - master - pull_request: - branches: - - master + - push + - pull_request + jobs: - build: + pre-commit: runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + - name: Run pre-commit + uses: pre-commit/action@v2.0.3 + + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + cache: pip + - run: | + pip install setuptools wheel + - run: | + python setup.py sdist bdist_wheel + + pytest: + runs-on: ${{ matrix.os }} strategy: matrix: + os: + - ubuntu-latest + - windows-latest python-version: - - "3.5" - - "3.6" - - "3.7" - - "3.8" - - "3.9" - - "3.10" - + - '3.6' + - '3.7' + - '3.8' + - '3.9' + - '3.10' steps: - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + cache: pip - name: Install dependencies run: | - python -m pip install --upgrade pip - python -m pip install -r requirements.txt + pip install -r requirements.txt + pip install -e . - name: Test with pytest run: | pytest From 6fa796d2873bb4e6f86b3d58ef6c4ab2115383da Mon Sep 17 00:00:00 2001 From: Jan Wille Date: Sun, 10 Jul 2022 19:01:18 +0200 Subject: [PATCH 15/21] workflows: add coveralls.io upload --- .github/workflows/run-tests.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index ea19c14..0bec7fc 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -64,7 +64,32 @@ jobs: - name: Install dependencies run: | pip install -r requirements.txt + pip install coveralls pip install -e . - name: Test with pytest run: | pytest + - name: Coverage upload + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_PARALLEL: true + COVERALLS_FLAG_NAME: ${{ join(matrix.*, ',') }} + run: | + coveralls --service=github + + finish-coveralls: + needs: pytest + runs-on: ubuntu-latest + steps: + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + - name: Install dependencies + run: | + pip install coveralls + - name: Coverage finish + env: + GITHUB_TOKEN: ${{ secrets.github_token }} + run: | + coveralls --service=github --finish From ca23496c06b6d49265ac3ea18d32c3ec90e3ac0a Mon Sep 17 00:00:00 2001 From: Jan Wille Date: Sun, 10 Jul 2022 19:05:56 +0200 Subject: [PATCH 16/21] workflows: auto publish to pypi for every tag --- .github/workflows/publish.yml | 29 ++++++++++++++++++----------- .yamllint.yml | 2 ++ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d8e6a51..7bc558b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,34 +1,41 @@ --- -# This workflow will upload a Python Package using Twine when a release is -# created +# This workflow will upload a Python Package using Twine # For more information see: # https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries name: Upload Python Package on: - release: - types: [created] + push: + tags: + - 'v*.*.*' + jobs: - deploy: + deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: - python-version: 3.x + python-version: '3.x' + cache: pip - name: Install dependencies run: | - python -m pip install --upgrade pip pip install setuptools wheel twine - - name: Build and publish + - name: Write Version + run: | + sed -i "s/__version__ = .*/__version__ = '${GITHUB_REF#refs/*/v}'/" readchar/__init__.py + - name: Build sdist and bdist_wheel + run: | + python setup.py sdist bdist_wheel + - name: publish to PyPi env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} run: | - python setup.py sdist bdist_wheel twine upload dist/* diff --git a/.yamllint.yml b/.yamllint.yml index f8ff608..c2a3114 100644 --- a/.yamllint.yml +++ b/.yamllint.yml @@ -6,5 +6,7 @@ ignore: | rules: indentation: spaces: 2 + line-length: + max: 120 new-lines: type: platform From 27d585aec935b1b3d91d9008dc3f5d6f5cae3641 Mon Sep 17 00:00:00 2001 From: Jan Wille Date: Mon, 4 Jul 2022 15:41:43 +0200 Subject: [PATCH 17/21] os support: macOS --- readchar/__init__.py | 5 ++++- readchar/key.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/readchar/__init__.py b/readchar/__init__.py index 5b9e2e5..b389a1b 100644 --- a/readchar/__init__.py +++ b/readchar/__init__.py @@ -7,7 +7,10 @@ from sys import platform -if platform.startswith("linux"): +if ( + platform.startswith("linux") + or platform == "darwin" +): from .posix_read import readchar, readkey elif platform in ("win32", "cygwin"): from .win_read import readchar, readkey diff --git a/readchar/key.py b/readchar/key.py index 683da1d..74e7004 100644 --- a/readchar/key.py +++ b/readchar/key.py @@ -2,7 +2,10 @@ from . import platform -if platform.startswith("linux"): +if ( + platform.startswith("linux") + or platform == "darwin" +): from .posix_key import * elif platform in ("win32", "cygwin"): from .win_key import * From a757e868fac50ee9633f6c7bfd1553392b92f867 Mon Sep 17 00:00:00 2001 From: trombonehero Date: Mon, 4 Jul 2022 15:47:23 +0200 Subject: [PATCH 18/21] os support: FreeBSD closes #78, closes #46 --- readchar/__init__.py | 1 + readchar/key.py | 1 + 2 files changed, 2 insertions(+) diff --git a/readchar/__init__.py b/readchar/__init__.py index b389a1b..a713d05 100644 --- a/readchar/__init__.py +++ b/readchar/__init__.py @@ -10,6 +10,7 @@ if ( platform.startswith("linux") or platform == "darwin" + or platform.startswith("freebsd") ): from .posix_read import readchar, readkey elif platform in ("win32", "cygwin"): diff --git a/readchar/key.py b/readchar/key.py index 74e7004..a57b9b0 100644 --- a/readchar/key.py +++ b/readchar/key.py @@ -5,6 +5,7 @@ if ( platform.startswith("linux") or platform == "darwin" + or platform.startswith("freebsd") ): from .posix_key import * elif platform in ("win32", "cygwin"): From 7f97973f311696536941579f79c37dbbc4ba193e Mon Sep 17 00:00:00 2001 From: Jan Wille Date: Mon, 4 Jul 2022 16:58:21 +0200 Subject: [PATCH 19/21] README update --- README.rst | 162 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 99 insertions(+), 63 deletions(-) diff --git a/README.rst b/README.rst index c233d7e..c7a04a2 100644 --- a/README.rst +++ b/README.rst @@ -1,38 +1,28 @@ -See it at: +| |GitHub badge| |PyPi badge| |python versions badge| |licence badge| +| |test status| |coverage status| |pip downloads badge| -- `pypi`_ -- `GitHub`_ +python-readchar +*************** -============== =============== ========= ============ -VERSION DOWNLOADS TESTS COVERAGE -============== =============== ========= ============ -|pip version| |pip downloads| |travis| |coveralls| -============== =============== ========= ============ - -Library to easily read single chars and key strokes. - -Goal and Philosophy -=================== +Library to easily read single chars and keystrokes. Born as a `python-inquirer`_ requirement. -The idea is to have a portable way to read **single** characters and **key-strokes**. - - -Documentation -============= Installation ------------- +============ -:: +simply install it via :code:`pip`: + +.. code:: bash pip install readchar -The :code:`readchar` library works with python 3.6, 3.7, 3.8, 3.9 and 3.10. +or download the source code from PyPi_. + Usage ------ +===== Usage example: @@ -44,44 +34,87 @@ Usage example: key = readchar.readkey() API ----- +=== There are just two methods: -:code:`readchar()` -////////////////// +:code:`readchar(blocking=True) -> str | None` +--------------------------------------------- Reads the next char from :code:`stdin`, returning it as a string with length 1. +By default, it will block until a character is available, but if you pass :code:`blocking=False` the function will not block and return :code:`None` +if no character is available. + + +:code:`readkey() -> str` +------------------------ + +Reads the next keystroke from :code:`stdin`, returning it as a string. Waits until a keystroke is available. + +A keystroke can be: + +- single characters as returned by :code:`readchar()`. These include: + + - character for normal keys: 'a', 'Z', '9'... + - special characters like 'ENTER', 'BACKSPACE', 'TAB'... + - combinations with 'CTRL': 'CTRL'+'A',... + +- keys that are made up of multiple characters: -:code:`readkey()` -///////////////// + - characters for cursors/arrows: 🡩, 🡪, 🡫, 🡨 + - navigation keys: 'INSERT', 'HOME',... + - function keys: 'F1' to 'F12' + - combinations with 'ALT': 'ALT'+'A',... + - combinations with 'CTRL' and 'ALT': 'CTRL'+'ALT'+'SUPR',... -Reads the next key-stroke from :code:`stdin`, returning it as a string. +.. attention:: -A key-stroke can have: + 'CTRL'+'C' will not be returned by :code:`readkey()`, but instead raise a :code:`KeyboardInterupt`. If you what to handle it yourself, + use :code:`readchar()`. Also note that using the none-blocking version may result in unexpected behaviour for :code:`KeyboardInterupt`. -- 1 character for normal keys: 'a', 'z', '9'... -- 2 characters for combinations with ALT: ALT+A, ... -- 3 characters for cursors: ->, <-, ... -- 4 characters for combinations with CTRL and ALT: CTRL+ALT+SUPR, ... -There is a list of previously captured chars with their names in :code:`readchar.key`, in order to be used in comparisons and so on. This list is not enough tested and it can have mistakes, so use it carefully. Please, report them if found. +:code:`key.py` module +--------------------- + +This submodule contains a list of available keys to compare against. You can use it like this: + +.. code:: python + + from readchar import readkey, key + + while True: + k = readkey() + if k == key.UP: + # do stuff + if k == key.DOWN: + # do stuff + if k == key.ENTER: + # do stuff OS Support ----------- +========== + +This library support both Linux and Windows, but on Windows the :code:`key` submodule has fewer keys available. + +Currently unsupported, but enabled operating systems: -Sadly, this library has only being probed on GNU/Linux. Please, if you can try it in another OS and find a bug, put an issue or send the pull-request. +- macOS +- FreeBSD + +Theoretically every Unix based system should work, but they will not be actively tested. It is also required that somebody provides initial test +results before the OS is enabled and added to the list. Feel free to open a PR for that. Thank you! + How to contribute ================= -You can download the code, make some changes with their tests, and make a pull-request. +You can download the code, make some changes with their tests, and open a pull-request. -In order to develop or running the tests, you can do: +In order to develop and run the tests, follow these steps: 1. Clone the repository. @@ -95,7 +128,7 @@ In order to develop or running the tests, you can do: python -m venv .venv -3. Enter in the virtual environment +3. Enter the virtual environment .. code:: bash @@ -107,7 +140,7 @@ In order to develop or running the tests, you can do: pip install -r requirements.txt -5. Install the local version of readchar (in edit mode so it automatically reflects changes) +5. Install the local version of readchar (in edit mode, so it automatically reflects changes) .. code:: bash @@ -126,36 +159,39 @@ Please, **Execute the tests before any pull-request**. This will avoid invalid b Licence ======= -Copyright (c) 2014-2021 Miguel Angel Garcia (`@magmax_en`_). - -Based on previous work on gist `getch()-like unbuffered character reading from stdin on both Windows and Unix (Python recipe)`_, started by `Danny Yoo`_. +Copyright (c) 2014-2022 Miguel Angel Garcia (`@magmax_en`_). -Licensed under `the MIT licence`_. +Based on previous work on gist `getch()-like unbuffered character reading from stdin on both Windows and Unix (Python recipe) +`_, started by Danny Yoo as well as gist +`kbhit.py `_ by Michel Blancard. +Licensed under `the MIT licence `_. -.. |travis| image:: https://travis-ci.org/magmax/python-readchar.png - :target: `Travis`_ - :alt: Travis results -.. |coveralls| image:: https://coveralls.io/repos/magmax/python-readchar/badge.png - :target: `Coveralls`_ - :alt: Coveralls results_ - -.. |pip version| image:: https://img.shields.io/pypi/v/readchar.svg - :target: https://pypi.python.org/pypi/readchar +.. |GitHub badge| image:: https://badges.aleen42.com/src/github.svg + :target: GitHub_ + :alt: GitHub Repository +.. |PyPi badge| image:: https://img.shields.io/pypi/v/readchar.svg + :target: PyPi_ :alt: Latest PyPI version - -.. |pip downloads| image:: https://img.shields.io/pypi/dm/readchar.svg - :target: https://pypi.python.org/pypi/readchar +.. |Python versions badge| image:: https://img.shields.io/pypi/pyversions/readchar + :target: PyPi_ + :alt: supported Python versions +.. |licence badge| image:: https://img.shields.io/pypi/l/readchar?color=blue + :target: licence_ + :alt: Project licence +.. |test status| image:: https://github.com/magmax/python-readchar/actions/workflows/run-tests.yml/badge.svg + :target: github.com/magmax/python-readchar/actions/workflows/run-tests.yml?query=branch%3Amaster + :alt: Automated testing results +.. |coverage status| image:: https://coveralls.io/repos/github/magmax/python-readchar/badge.svg?branch=master + :target: https://coveralls.io/github/magmax/python-readchar?branch=master + :alt: Coveralls results +.. |pip downloads badge| image:: https://img.shields.io/pypi/dd/readchar.svg + :target: PyPi_ :alt: Number of PyPI downloads -.. _pypi: https://pypi.python.org/pypi/readchar .. _GitHub: https://github.com/magmax/python-readchar +.. _PyPi: https://pypi.python.org/pypi/readchar +.. _licence: LICENCE .. _python-inquirer: https://github.com/magmax/python-inquirer -.. _Travis: https://travis-ci.org/magmax/python-readchar -.. _Coveralls: https://coveralls.io/r/magmax/python-readchar .. _@magmax_en: https://twitter.com/magmax_en - -.. _the MIT licence: http://opensource.org/licenses/MIT -.. _getch()-like unbuffered character reading from stdin on both Windows and Unix (Python recipe): http://code.activestate.com/recipes/134892/ -.. _Danny Yoo: http://code.activestate.com/recipes/users/98032/ From be1af46c19c42af01a3113e2ae41aacdca844409 Mon Sep 17 00:00:00 2001 From: Jan Wille <62838959+Cube707@users.noreply.github.com> Date: Sat, 9 Jul 2022 20:34:11 +0200 Subject: [PATCH 20/21] Added issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 38 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 24 ++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..8a8274e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +## Describe the bug + +A clear and concise description of what the bug is. + +## To Reproduce + +Steps to reproduce the behaviour: + +1. Use this code '...' +2. Do the following '....' +3. See error + +## Expected behaviour + +A clear and concise description of what you expected to happen. + +## Screenshots + +If applicable, add screenshots to help explain your problem. + +## Environment (please complete the following information) + +- OS: [e.g. Linux / Windows / macOS / etc.] +- python version: [get by running: `python --version`] +- readchar version: [get by running: `pip show readchar`] + +## Additional context + +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..6f7e174 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,24 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +## Is your feature request related to a problem? Please describe. + +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +## Describe the solution you'd like + +A clear and concise description of what you want to happen. + +## Describe alternatives you've considered + +A clear and concise description of any alternative solutions or features you've considered. + +## Additional context + +Add any other context or screenshots about the feature request here. From be6fca78faa6714449d61e0a2b3316c86ef771eb Mon Sep 17 00:00:00 2001 From: fleytman Date: Fri, 15 Jul 2022 17:40:02 +0300 Subject: [PATCH 21/21] add key shift+tab --- readchar/posix_key.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/readchar/posix_key.py b/readchar/posix_key.py index a1effc2..cb18392 100644 --- a/readchar/posix_key.py +++ b/readchar/posix_key.py @@ -67,6 +67,9 @@ # ALT ALT_A = "\x1b\x61" +# SHIFT +SHIFT_TAB = "\x1b\x5b\x5a" + # CTRL + ALT CTRL_ALT_A = "\x1b\x01" CTRL_ALT_SUPR = "\x1b\x5b\x33\x5e"