diff --git a/.github/workflows/build_deploy.yml b/.github/workflows/build_deploy.yml
new file mode 100644
index 0000000..ce64bb8
--- /dev/null
+++ b/.github/workflows/build_deploy.yml
@@ -0,0 +1,122 @@
+---
+name: Build and upload
+
+on: # yamllint disable-line rule:truthy
+ push:
+ paths-ignore:
+ - 'README.md'
+ release:
+ types: [created]
+
+jobs:
+ build_wheels:
+ name: Build wheels on ${{ matrix.os }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+
+ if: github.event_name == 'release' && github.event.action == 'created'
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Build wheels
+ uses: pypa/cibuildwheel@v2.21.3
+ env:
+ CIBW_TEST_COMMAND_WINDOWS: >
+ cd /d {package}
+ && ( rmdir ..\uwcwidth_tmp /s /q 2>NUL || cd . )
+ && mkdir ..\uwcwidth_tmp
+ && cd ..\uwcwidth_tmp
+ && xcopy {package} /s
+ && rmdir uwcwidth /s /q
+ && pytest
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
+ path: ./wheelhouse/*.whl
+
+ build_sdist:
+ name: Build source distribution
+ runs-on: ubuntu-latest
+ if: github.event_name == 'release' && github.event.action == 'created'
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install dependencies
+ run: python3 -m pip install --upgrade build
+
+ - name: Build sdist
+ run: python3 -m build --sdist
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: cibw-sdist
+ path: dist/*.tar.gz
+
+ upload_pypi:
+ needs: [build_wheels, build_sdist]
+ runs-on: ubuntu-latest
+ environment: production
+ permissions:
+ id-token: write
+ if: |
+ github.event_name == 'release' && github.event.action == 'created'
+ && !endsWith(github.ref, '-test')
+ steps:
+ - uses: actions/download-artifact@v4
+ with:
+ # unpacks all CIBW artifacts into dist/
+ pattern: cibw-*
+ path: dist
+ merge-multiple: true
+
+ - uses: pypa/gh-action-pypi-publish@release/v1
+
+ upload_pypi_test:
+ needs: [build_wheels, build_sdist]
+ runs-on: ubuntu-latest
+ environment: production
+ permissions:
+ id-token: write
+ if: |
+ github.event_name == 'release' && github.event.action == 'created'
+ && endsWith(github.ref, '-test')
+ steps:
+ - uses: actions/download-artifact@v4
+ with:
+ # unpacks all CIBW artifacts into dist/
+ pattern: cibw-*
+ path: dist
+ merge-multiple: true
+
+ - uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ repository-url: https://test.pypi.org/legacy/
+
+ upload_gh_release:
+ needs: [build_wheels, build_sdist]
+ runs-on: ubuntu-latest
+ environment: production
+ permissions:
+ contents: write
+ id-token: write
+
+ steps:
+ - uses: actions/download-artifact@v4
+ with:
+ # unpacks all CIBW artifacts into dist/
+ pattern: cibw-*
+ path: dist
+ merge-multiple: true
+
+ - uses: sigstore/gh-action-sigstore-python@v2.1.1
+ with:
+ inputs: >-
+ ./dist/*.tar.gz
+ ./dist/*.whl
+
+ - uses: softprops/action-gh-release@v2
+ with:
+ files: dist/**
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..1387a2c
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,17 @@
+name: Test
+
+on:
+ push:
+ paths-ignore:
+ - 'README.md'
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
+ with:
+ python-version: '3.13'
+ - run: make test
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..d80ebc8
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,26 @@
+.PHONY: all build build-debug clean clean-build clean-venv test
+
+all: build venv
+ @:
+
+build: venv
+ DEBUG=$(DEBUG) venv/bin/python3 setup.py build_ext --inplace
+
+build-debug: override DEBUG=1
+build-debug: build ;
+
+clean: clean-venv clean-build
+ @:
+
+clean-venv:
+ rm -rf venv
+
+clean-build:
+ rm -rf build
+
+test: build
+ venv/bin/pytest
+
+venv:
+ python3 -mvenv venv
+ venv/bin/pip install setuptools Cython pytest
diff --git a/README.md b/README.md
index f5ff67a..8fbe96d 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,15 @@
-## Overview
+## uwcwidth
+*terminal width of Unicode 16.0+Emoji strings in nanoseconds*
+
+
+[](https://pypi.org/project/uwcwidth/)
+[](https://github.com/Z4JC/uwcwidth/blob/main/LICENSE)
+
+[](https://github.com/Z4JC/uwcwidth/actions/workflows/build_deploy.yml)
+[](https://github.com/Z4JC/uwcwidth/actions/workflows/test.yml)
+
+
+
Use `uwcwidth` when you want to very quickly find out how many characters a Unicode string takes up in your terminal.
For example, `uwcwidth.wcswidth('Hello🥹')` returns `7` because your terminal will use 5 places for "Hello" and then 2 places for the "🥹" emoji.
@@ -40,20 +51,20 @@ See the `tests` folder for more.
`uwcwidth` reserves around 4 KB of memory for its lookup tables. Parts of the storage scheme are derived from an older `wcwidth` implementation in [musl libc](https://musl.libc.org/). Generally sparse or dense bitmaps are used to look things up.
The `uwcwidth.pyx` file is under 100 lines of code, with comments and whitespace.
-## Performance: 30x faster than `wcwidth`
-`uwcwidth` is about 30 times faster than the popular, well-documented and highly tested [wcwidth](https://github.com/jquast/wcwidth) library, while maintaining similar accuracy. It's also 5 times faster than `cwcwidth`, which does not work on new Emojis and breaks on some other edge cases.
+## Performance: 40x faster than `wcwidth`
+`uwcwidth` is about 40 times faster than the popular, well-documented and highly tested [wcwidth](https://github.com/jquast/wcwidth) library, while maintaining similar accuracy. It's also 5 times faster than `cwcwidth`, which does not work on new Emojis and breaks on some other edge cases.
```python3
In [1]: import wcwidth, cwcwidth, uwcwidth
In [2]: %%timeit
...: wcwidth.wcswidth("コンニチハ, セカイ!")
-1.28 μs ± 6.22 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
+1.73 μs ± 7.93 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
In [3]: %%timeit
...: cwcwidth.wcswidth("コンニチハ, セカイ!")
-205 ns ± 0.408 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
+211 ns ± 3.63 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
In [4]: %%timeit
...: uwcwidth.wcswidth("コンニチハ, セカイ!")
-38.5 ns ± 0.29 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
-```
\ No newline at end of file
+41 ns ± 0.0363 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
+```
diff --git a/pyproject.toml b/pyproject.toml
index 66ab3ef..8ad213d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "uwcwidth"
-version = "0.9.2"
+version = "1.0.0"
authors = [{name = "!ZAJC!"}]
readme = "README.md"
description = "terminal width of Unicode 16.0+Emoji strings in nanoseconds"
@@ -33,5 +33,17 @@ exclude = []
namespaces = false
[tool.pytest.ini_options]
+pythonpath = ["."]
testpaths = ["tests"]
addopts = ["--import-mode=importlib"]
+
+[tool.cibuildwheel]
+build-frontend = "build"
+test-command = """
+cd $( mktemp -d ) \
+&& cp -pr {project}/* ./ \
+&& rm -rf uwcwidth \
+&& pytest
+"""
+test-requires = "pytest"
+skip = ["cp36-*", "cp37-*"]
diff --git a/setup.py b/setup.py
index 1ce64d1..60d24c7 100644
--- a/setup.py
+++ b/setup.py
@@ -1,9 +1,24 @@
# SPDX-License-Identifier: MIT
+import os
+import platform
+
from setuptools import setup, Extension
+
+DEBUG=(os.getenv('DEBUG') or '').strip().lower() in ['1', 'y', 'true']
+MSVC=(platform.platform().startswith('Windows') and
+ platform.python_compiler().startswith('MS'))
+COMPILE_ARGS=[] if MSVC else (["-g", "-O0", "-UNDEBUG"] if DEBUG else ["-O3"])
+
+
+def uwcwidth_ext(module, pyx_file):
+ return Extension(module,
+ sources=[pyx_file],
+ extra_compile_args=COMPILE_ARGS)
+
+
setup(
name='uwcwidth',
- ext_modules=[Extension("uwcwidth.uwcwidth",
- sources=["uwcwidth/uwcwidth.pyx"])],
+ ext_modules=[uwcwidth_ext("uwcwidth.uwcwidth", "uwcwidth/uwcwidth.pyx")],
package_data={'uwcwidth': ['__init__.pxd', 'uwcwidth.pxd', 'tables.pxd']}
)