diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b1a6ebf1..0588d6dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,32 +34,18 @@ jobs: - "3.12" - "3.13" - "3.14" - - "pypy-3.7" - "pypy-3.8" - "pypy-3.9" - "pypy-3.10" - "pypy-3.11" - include: - - os: windows-latest - python-version: "3.7" - - os: ubuntu-22.04 - python-version: "3.7" - - os: ubuntu-22.04 - python-version: "pypy-3.6" - - os: ubuntu-latest - container: python:3.6 - python-version: "3.6" - exclude: - - os: macos-latest - python-version: "pypy-3.7" runs-on: ${{ matrix.os }} container: ${{ matrix.container }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} allow-prereleases: true @@ -109,7 +95,7 @@ jobs: fi - name: Upload coverage data - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: coverage-data.${{ matrix.os }}-${{ matrix.python-version }} path: .coverage.* @@ -122,8 +108,8 @@ jobs: needs: tests steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: python-version-file: .python-version-default cache: pip @@ -137,7 +123,7 @@ jobs: delete-merged: true - name: Download coverage data - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v6 with: name: coverage-data @@ -153,7 +139,7 @@ jobs: python -Im coverage report --format=markdown >> $GITHUB_STEP_SUMMARY - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 env: CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}} @@ -163,7 +149,7 @@ jobs: python -Im coverage report --fail-under=100 - name: Upload HTML report if check failed. - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: html-report path: htmlcov diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 19e407a8..84339a2f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v6 - name: Initialize CodeQL uses: github/codeql-action/init@v2 diff --git a/bin/update-tables.py b/bin/update-tables.py index fc85b7ce..c3975df2 100644 --- a/bin/update-tables.py +++ b/bin/update-tables.py @@ -675,8 +675,8 @@ def replace_if_modified(new_filename: str, original_filename: str) -> None: If there are other changes or the original doesn't exist, replace it. """ if os.path.exists(original_filename): - with open(original_filename, 'r', encoding='utf-8') as f1, \ - open(new_filename, 'r', encoding='utf-8') as f2: + with open(original_filename, encoding='utf-8') as f1, \ + open(new_filename, encoding='utf-8') as f2: old_lines = f1.readlines() new_lines = f2.readlines() diff --git a/bin/wcwidth-browser.py b/bin/wcwidth-browser.py index 545f9113..18513101 100755 --- a/bin/wcwidth-browser.py +++ b/bin/wcwidth-browser.py @@ -290,8 +290,7 @@ def on_resize(self, *args): # pylint: disable=W0613 # Unused argument 'args' assert self.term.width >= self.screen.hint_width, ( - 'Screen to small {}, must be at least {}'.format( - self.term.width, self.screen.hint_width)) + f'Screen to small {self.term.width}, must be at least {self.screen.hint_width}') self._set_lastpage() self.dirty = self.STATE_REFRESH diff --git a/bin/wcwidth-libc-comparator.py b/bin/wcwidth-libc-comparator.py index f07a4fe3..14f201f4 100755 --- a/bin/wcwidth-libc-comparator.py +++ b/bin/wcwidth-libc-comparator.py @@ -64,8 +64,10 @@ def report_ucs_msg(ucs, wcwidth_libc, wcwidth_local): .lstrip('0')) url = f"http://codepoints.net/U+{ucp}" name = unicodedata.name(ucs) - return ("libc,ours={},{} [--o{}o--] name={} val={} {}" - " ".format(wcwidth_libc, wcwidth_local, ucs, name, ord(ucs), url)) + return ( + f"libc,ours={wcwidth_libc},{wcwidth_local} " + f"[--o{ucs}o--] name={name} val={ord(ucs)} {url} ") + if sys.maxunicode < 1114111: diff --git a/docs/intro.rst b/docs/intro.rst index 8d25b4fc..b8464a34 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -107,7 +107,7 @@ Install wcwidth in editable mode:: Execute unit tests using tox_ for all supported Python versions:: - tox -e py36,py37,py38,py39,py310,py311,py312,py313,py314 + tox -e py38,py39,py310,py311,py312,py313,py314 Updating Unicode Version ------------------------ @@ -141,7 +141,7 @@ To upgrade requirements for updating unicode version, run:: To upgrade requirements for testing, run:: - tox -e update_requirements37,update_requirements39 + tox -e update_requirements38,update_requirements39 To upgrade requirements for building documentation, run:: diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..d5fc8767 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,50 @@ +[build-system] +build-backend = "hatchling.build" +requires = [ "hatchling" ] + +[project] +name = "wcwidth" +version = "0.2.14" +description = "Measures the displayed width of unicode strings in a terminal" +readme = "README.rst" +keywords = [ + "cjk", + "combining", + "console", + "eastasian", + "emoji", + "emulator", + "terminal", + "unicode", + "wcswidth", + "wcwidth", + "xterm", +] +license = "MIT" +license-files = [ "LICENSE" ] +authors = [ + { name = "Jeff Quast", email = "contact@jeffquast.com" }, +] +requires-python = ">=3.8" +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Developers", + "Natural Language :: English", + "Operating System :: POSIX", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Topic :: Software Development :: Internationalization", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Localization", + "Topic :: Terminals", +] + +[project.urls] +Homepage = "https://github.com/jquast/wcwidth" diff --git a/requirements-tests36.txt b/requirements-tests36.txt deleted file mode 100644 index 304eb080..00000000 --- a/requirements-tests36.txt +++ /dev/null @@ -1,41 +0,0 @@ -# -# This file is autogenerated by pip-compile with python 3.6 -# To update, run: -# -# pip-compile --no-emit-index-url --output-file=requirements-tests36.txt requirements-tests37.in -# -attrs==22.2.0 - # via pytest -coverage[toml]==5.5 - # via - # -r requirements-tests37.in - # pytest-cov -importlib-metadata==4.8.3 ; python_version < "3.8" - # via - # -r requirements-tests37.in - # pluggy - # pytest -iniconfig==1.1.1 - # via pytest -packaging==21.3 - # via pytest -pluggy==1.0.0 - # via pytest -py==1.11.0 - # via pytest -pyparsing==3.0.9 - # via packaging -pytest==6.2.5 - # via - # -r requirements-tests37.in - # pytest-cov -pytest-cov==4.0.0 - # via -r requirements-tests37.in -toml==0.10.2 - # via - # coverage - # pytest -typing-extensions==4.1.1 - # via importlib-metadata -zipp==3.6.0 - # via importlib-metadata diff --git a/requirements-tests37.in b/requirements-tests37.in deleted file mode 100644 index d1b25eff..00000000 --- a/requirements-tests37.in +++ /dev/null @@ -1,6 +0,0 @@ -# for versions of python3.7 *and earlier* -pytest<7 -pytest-cov -coverage[toml]<6 -importlib_metadata; python_version < '3.8' -pytest-benchmark diff --git a/requirements-tests37.txt b/requirements-tests37.txt deleted file mode 100644 index e3dc45b5..00000000 --- a/requirements-tests37.txt +++ /dev/null @@ -1,45 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.7 -# by the following command: -# -# pip-compile --allow-unsafe --no-emit-index-url --output-file=requirements-tests37.txt --resolver=backtracking --strip-extras requirements-tests37.in -# -attrs==24.2.0 - # via pytest -coverage==5.5 - # via - # -r requirements-tests37.in - # pytest-cov -importlib-metadata==6.7.0 ; python_version < "3.8" - # via - # -r requirements-tests37.in - # attrs - # pluggy - # pytest -iniconfig==2.0.0 - # via pytest -packaging==24.0 - # via pytest -pluggy==1.2.0 - # via pytest -py==1.11.0 - # via pytest -py-cpuinfo==9.0.0 - # via pytest-benchmark -pytest==6.2.5 - # via - # -r requirements-tests37.in - # pytest-benchmark - # pytest-cov -pytest-benchmark==4.0.0 - # via -r requirements-tests37.in -pytest-cov==4.1.0 - # via -r requirements-tests37.in -toml==0.10.2 - # via - # coverage - # pytest -typing-extensions==4.7.1 - # via importlib-metadata -zipp==3.15.0 - # via importlib-metadata diff --git a/requirements-tests38.in b/requirements-tests38.in new file mode 100644 index 00000000..d849b582 --- /dev/null +++ b/requirements-tests38.in @@ -0,0 +1,5 @@ +# for python3.8 +pytest<7 +pytest-cov +coverage[toml]<6 +pytest-benchmark diff --git a/requirements-tests38.txt b/requirements-tests38.txt new file mode 100644 index 00000000..f11f44dc --- /dev/null +++ b/requirements-tests38.txt @@ -0,0 +1,35 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# pip-compile --allow-unsafe --no-emit-index-url --output-file=requirements-tests38.txt --strip-extras requirements-tests38.in +# +attrs==25.3.0 + # via pytest +coverage==5.5 + # via + # -r requirements-tests38.in + # pytest-cov +iniconfig==2.1.0 + # via pytest +packaging==25.0 + # via pytest +pluggy==1.5.0 + # via pytest +py==1.11.0 + # via pytest +py-cpuinfo==9.0.0 + # via pytest-benchmark +pytest==6.2.5 + # via + # -r requirements-tests38.in + # pytest-benchmark + # pytest-cov +pytest-benchmark==4.0.0 + # via -r requirements-tests38.in +pytest-cov==5.0.0 + # via -r requirements-tests38.in +toml==0.10.2 + # via + # coverage + # pytest diff --git a/setup.py b/setup.py deleted file mode 100755 index c796519e..00000000 --- a/setup.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env python -""" -Setup.py distribution file for wcwidth. - -https://github.com/jquast/wcwidth -""" -# std imports -import os -import codecs - -# 3rd party -import setuptools - - -def _get_here(fname): - return os.path.join(os.path.dirname(__file__), fname) - - -class _SetupUpdate(setuptools.Command): - # This is a compatibility, some downstream distributions might - # still call "setup.py update". - # - # New entry point is tox, 'tox -eupdate'. - description = "Fetch and update unicode code tables" - user_options = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - import sys - import subprocess - retcode = subprocess.Popen([ - sys.executable, - _get_here(os.path.join('bin', 'update-tables.py'))]).wait() - assert retcode == 0, ('non-zero exit code', retcode) - - -def main(): - """Setup.py entry point.""" - setuptools.setup( - name='wcwidth', - # NOTE: manually manage __version__ in wcwidth/__init__.py ! - version='0.2.14', - description=( - "Measures the displayed width of unicode strings in a terminal"), - long_description=codecs.open( - _get_here('README.rst'), 'rb', 'utf8').read(), - author='Jeff Quast', - author_email='contact@jeffquast.com', - license='MIT', - packages=['wcwidth'], - url='https://github.com/jquast/wcwidth', - package_data={ - '': ['LICENSE', '*.rst'], - }, - zip_safe=True, - python_requires='>=3.6', - classifiers=[ - 'Intended Audience :: Developers', - 'Natural Language :: English', - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'License :: OSI Approved :: MIT License', - 'Operating System :: POSIX', - '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 :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Programming Language :: Python :: 3.13', - 'Programming Language :: Python :: 3.14', - 'Topic :: Software Development :: Libraries', - 'Topic :: Software Development :: Localization', - 'Topic :: Software Development :: Internationalization', - 'Topic :: Terminals' - ], - keywords=[ - 'cjk', - 'combining', - 'console', - 'eastasian', - 'emoji', - 'emulator', - 'terminal', - 'unicode', - 'wcswidth', - 'wcwidth', - 'xterm', - ], - cmdclass={'update': _SetupUpdate}, - ) - - -if __name__ == '__main__': - main() diff --git a/tests/test_core.py b/tests/test_core.py index 34cee384..75c65a83 100755 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,10 +1,5 @@ """Core tests for wcwidth module. isort:skip_file""" -try: - # std import - import importlib.metadata as importmeta -except ImportError: - # 3rd party for python3.7 and earlier - import importlib_metadata as importmeta +import importlib.metadata # local import wcwidth @@ -13,7 +8,7 @@ def test_package_version(): """wcwidth.__version__ is expected value.""" # given, - expected = importmeta.version('wcwidth') + expected = importlib.metadata.version('wcwidth') # exercise, result = wcwidth.__version__ diff --git a/tests/test_emojis.py b/tests/test_emojis.py index 310d0c3d..9df6b34d 100644 --- a/tests/test_emojis.py +++ b/tests/test_emojis.py @@ -138,7 +138,7 @@ def test_longer_emoji_zwj_sequence(): def read_sequences_from_file(filename): - fp = open(os.path.join(os.path.dirname(__file__), filename), 'r', encoding='utf-8') + fp = open(os.path.join(os.path.dirname(__file__), filename), encoding='utf-8') lines = [line.strip() for line in fp.readlines() if not line.startswith('#') and line.strip()] diff --git a/tox.ini b/tox.ini index 9d66fbf8..ba2e927f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,6 @@ [tox] -envlist = update, compile, autopep8, docformatter, isort, pylint, flake8, pydocstyle, docs, verify_tables, py{36, 37, 38, 39, 310, 311, 312, 313, 314}, pypy{36, 37, 38, 39, 310} +envlist = update, compile, autopep8, docformatter, isort, pylint, flake8, pydocstyle, docs, verify_tables, py{38, 39, 310, 311, 312, 313, 314}, pypy{38, 39, 310, 311} skip_missing_interpreters = true -# needed for Python 3.6 -# https://tox.wiki/en/4.11.3/faq.html#testing-end-of-life-python-versions -requires = virtualenv<20.22.0 [base] pip_compile_command = pip-compile --resolver=backtracking --strip-extras --no-emit-index-url --allow-unsafe --upgrade @@ -39,7 +36,7 @@ atomic = true [pytest] norecursedirs = .git .tox build addopts = --disable-pytest-warnings - --cov-append --cov-report=html --color=yes --ignore=setup.py --ignore=.tox + --cov-append --cov-report=html --color=yes --ignore=.tox --cov=wcwidth filterwarnings = error @@ -88,28 +85,16 @@ basepython = python3.9 deps = pip-tools commands = {[base]pip_compile_command} requirements-tests39.in -o requirements-tests39.txt -[testenv:update_requirements37] -basepython = python3.7 +[testenv:update_requirements38] +basepython = python3.8 deps = pip-tools -commands = {[base]pip_compile_command} requirements-tests37.in -o requirements-tests37.txt +commands = {[base]pip_compile_command} requirements-tests38.in -o requirements-tests38.txt [testenv:py38] -deps = -r requirements-tests37.txt +deps = -r requirements-tests38.txt [testenv:pypy38] -deps = -r requirements-tests37.txt - -[testenv:py37] -deps = -r requirements-tests37.txt - -[testenv:pypy37] -deps = -r requirements-tests37.txt - -[testenv:py36] -deps = -r requirements-tests36.txt - -[testenv:pypy36] -deps = -r requirements-tests36.txt +deps = -r requirements-tests38.txt [testenv:update] basepython = python3.13 @@ -126,7 +111,7 @@ commands = --recursive \ --aggressive \ --aggressive \ - wcwidth/ bin/ tests/ setup.py + wcwidth/ bin/ tests/ [testenv:isort] deps = isort @@ -137,13 +122,13 @@ commands = {envbindir}/isort --quiet --apply --recursive wcwidth tests bin basepython = python3.13 deps = pylint commands = {envbindir}/pylint --rcfile={toxinidir}/.pylintrc \ - --ignore=tests,docs,setup.py,conf.py,build,distutils,.pyenv,.git,.tox \ + --ignore=tests,docs,conf.py,build,distutils,.pyenv,.git,.tox \ {posargs:{toxinidir}}/wcwidth [testenv:flake8] basepython = python3.13 deps = flake8 -commands = {envbindir}/flake8 --exclude=tests setup.py docs/ wcwidth/ bin/ tests/ +commands = {envbindir}/flake8 --exclude=tests docs/ wcwidth/ bin/ tests/ [testenv:docs] # matches .readthedocs.yaml and environment diff --git a/wcwidth/wcwidth.py b/wcwidth/wcwidth.py index 92ca14af..46dee418 100644 --- a/wcwidth/wcwidth.py +++ b/wcwidth/wcwidth.py @@ -274,11 +274,10 @@ def _wcmatch_version(given_version): except ValueError: # submitted value raises ValueError in int(), warn and use latest. - warnings.warn("UNICODE_VERSION value, {given_version!r}, is invalid. " + warnings.warn(f"UNICODE_VERSION value, {given_version!r}, is invalid. " "Value should be in form of `integer[.]+', the latest " - "supported unicode version {latest_version!r} has been " - "inferred.".format(given_version=given_version, - latest_version=latest_version)) + f"supported unicode version {latest_version!r} has been " + "inferred.") return latest_version # given version is less than any available version, return earliest @@ -290,11 +289,9 @@ def _wcmatch_version(given_version): # this probably isn't what you wanted, the oldest wcwidth.c you will # find in the wild is likely version 5 or 6, which we both support, # but it's better than not saying anything at all. - warnings.warn("UNICODE_VERSION value, {given_version!r}, is lower " + warnings.warn(f"UNICODE_VERSION value, {given_version!r}, is lower " "than any available unicode version. Returning lowest " - "version level, {earliest_version!r}".format( - given_version=given_version, - earliest_version=earliest_version)) + f"version level, {earliest_version!r}") return earliest_version # create list of versions which are less than our equal to given version,