wcwidth 0.9.0__tar.gz → 0.9.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. {wcwidth-0.9.0 → wcwidth-0.9.1}/.gitignore +4 -0
  2. {wcwidth-0.9.0 → wcwidth-0.9.1}/PKG-INFO +1 -1
  3. {wcwidth-0.9.0 → wcwidth-0.9.1}/bin/update-docs.py +38 -15
  4. {wcwidth-0.9.0 → wcwidth-0.9.1}/docs/developing.rst +6 -0
  5. {wcwidth-0.9.0 → wcwidth-0.9.1}/docs/history.rst +9 -1
  6. {wcwidth-0.9.0 → wcwidth-0.9.1}/docs/libwcwidth.rst +28 -8
  7. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/include/wcwidth/wcwidth_config.h +2 -2
  8. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/width.c +42 -16
  9. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/tests/test_align.c +29 -0
  10. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/tests/test_clip.c +38 -0
  11. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/tests/test_sgr.c +50 -0
  12. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/tests/test_text_sizing.c +13 -0
  13. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/tests/test_textwrap.c +50 -0
  14. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/tests/test_utf8.c +18 -0
  15. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/tests/test_wcswidth.c +7 -0
  16. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/tests/test_width.c +69 -1
  17. {wcwidth-0.9.0 → wcwidth-0.9.1}/pyproject.toml +1 -1
  18. {wcwidth-0.9.0 → wcwidth-0.9.1}/tests/test_benchmarks.py +10 -0
  19. wcwidth-0.9.1/tests/test_fastcall_args.py +118 -0
  20. {wcwidth-0.9.0 → wcwidth-0.9.1}/tests/test_term_overrides.py +6 -0
  21. {wcwidth-0.9.0 → wcwidth-0.9.1}/tests/test_width.py +5 -0
  22. {wcwidth-0.9.0 → wcwidth-0.9.1}/tox.ini +50 -2
  23. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/__init__.py +1 -1
  24. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/_wcwidth_c.c +724 -107
  25. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth.egg-info/PKG-INFO +1 -1
  26. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth.egg-info/SOURCES.txt +1 -1
  27. wcwidth-0.9.0/docs/api_c.rst +0 -1042
  28. {wcwidth-0.9.0 → wcwidth-0.9.1}/.pylintrc +0 -0
  29. {wcwidth-0.9.0 → wcwidth-0.9.1}/LICENSE +0 -0
  30. {wcwidth-0.9.0 → wcwidth-0.9.1}/MANIFEST.in +0 -0
  31. {wcwidth-0.9.0 → wcwidth-0.9.1}/README.rst +0 -0
  32. {wcwidth-0.9.0 → wcwidth-0.9.1}/bin/check-release.py +0 -0
  33. {wcwidth-0.9.0 → wcwidth-0.9.1}/bin/update-tables.py +0 -0
  34. {wcwidth-0.9.0 → wcwidth-0.9.1}/bin/verify-table-integrity.py +0 -0
  35. {wcwidth-0.9.0 → wcwidth-0.9.1}/bin/wcwidth-browser.py +0 -0
  36. {wcwidth-0.9.0 → wcwidth-0.9.1}/bin/wcwidth-libc-comparator.py +0 -0
  37. {wcwidth-0.9.0 → wcwidth-0.9.1}/code_templates/api_c.rst.j2 +0 -0
  38. {wcwidth-0.9.0 → wcwidth-0.9.1}/code_templates/c_gcb_class.c.j2 +0 -0
  39. {wcwidth-0.9.0 → wcwidth-0.9.1}/code_templates/c_table.c.j2 +0 -0
  40. {wcwidth-0.9.0 → wcwidth-0.9.1}/code_templates/c_term_programs.c.j2 +0 -0
  41. {wcwidth-0.9.0 → wcwidth-0.9.1}/code_templates/c_terminal_overrides.c.j2 +0 -0
  42. {wcwidth-0.9.0 → wcwidth-0.9.1}/code_templates/grapheme_override_per_terminal.py.j2 +0 -0
  43. {wcwidth-0.9.0 → wcwidth-0.9.1}/code_templates/grapheme_override_table.py.j2 +0 -0
  44. {wcwidth-0.9.0 → wcwidth-0.9.1}/code_templates/grapheme_registry.py.j2 +0 -0
  45. {wcwidth-0.9.0 → wcwidth-0.9.1}/code_templates/grapheme_table.c.j2 +0 -0
  46. {wcwidth-0.9.0 → wcwidth-0.9.1}/code_templates/grapheme_table.py.j2 +0 -0
  47. {wcwidth-0.9.0 → wcwidth-0.9.1}/code_templates/python_table.py.j2 +0 -0
  48. {wcwidth-0.9.0 → wcwidth-0.9.1}/code_templates/table_overrides.py.j2 +0 -0
  49. {wcwidth-0.9.0 → wcwidth-0.9.1}/code_templates/tables.h.j2 +0 -0
  50. {wcwidth-0.9.0 → wcwidth-0.9.1}/code_templates/term_programs.py.j2 +0 -0
  51. {wcwidth-0.9.0 → wcwidth-0.9.1}/code_templates/unicode_version.rst.j2 +0 -0
  52. {wcwidth-0.9.0 → wcwidth-0.9.1}/code_templates/unicode_versions.py.j2 +0 -0
  53. {wcwidth-0.9.0 → wcwidth-0.9.1}/code_templates/wcwidth_config.h.j2 +0 -0
  54. {wcwidth-0.9.0 → wcwidth-0.9.1}/docs/api.rst +0 -0
  55. {wcwidth-0.9.0 → wcwidth-0.9.1}/docs/conf.py +0 -0
  56. {wcwidth-0.9.0 → wcwidth-0.9.1}/docs/index.rst +0 -0
  57. {wcwidth-0.9.0 → wcwidth-0.9.1}/docs/intro.rst +0 -0
  58. {wcwidth-0.9.0 → wcwidth-0.9.1}/docs/related.rst +0 -0
  59. {wcwidth-0.9.0 → wcwidth-0.9.1}/docs/specs.rst +0 -0
  60. {wcwidth-0.9.0 → wcwidth-0.9.1}/docs/unicode_version.rst +0 -0
  61. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/.clang-format +0 -0
  62. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/CMakeLists.txt +0 -0
  63. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/LICENSE +0 -0
  64. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/Makefile +0 -0
  65. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/README.rst +0 -0
  66. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/cmake/libwcwidth-config.cmake.in +0 -0
  67. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/examples/align.c +0 -0
  68. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/examples/textwrap.c +0 -0
  69. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/examples/width.c +0 -0
  70. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/include/wcwidth/align.h +0 -0
  71. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/include/wcwidth/clip.h +0 -0
  72. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/include/wcwidth/escape.h +0 -0
  73. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/include/wcwidth/grapheme.h +0 -0
  74. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/include/wcwidth/sgr.h +0 -0
  75. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/include/wcwidth/table_types.h +0 -0
  76. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/include/wcwidth/tables.h +0 -0
  77. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/include/wcwidth/terminal_override.h +0 -0
  78. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/include/wcwidth/text_sizing.h +0 -0
  79. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/include/wcwidth/textwrap.h +0 -0
  80. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/include/wcwidth/unicode.h +0 -0
  81. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/include/wcwidth/utf8.h +0 -0
  82. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/include/wcwidth/wcwidth.h +0 -0
  83. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/include/wcwidth/width.h +0 -0
  84. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/align.c +0 -0
  85. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/bisearch.c +0 -0
  86. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/clip.c +0 -0
  87. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/escape.c +0 -0
  88. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/grapheme.c +0 -0
  89. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/sgr.c +0 -0
  90. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/tables/table_ambiguous.c +0 -0
  91. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/tables/table_gcb_class.c +0 -0
  92. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/tables/table_grapheme.c +0 -0
  93. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/tables/table_mc.c +0 -0
  94. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/tables/table_term_programs.c +0 -0
  95. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/tables/table_terminal_overrides.c +0 -0
  96. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/tables/table_vs15.c +0 -0
  97. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/tables/table_vs16.c +0 -0
  98. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/tables/table_wide.c +0 -0
  99. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/tables/table_zero.c +0 -0
  100. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/terminal_override.c +0 -0
  101. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/text_sizing.c +0 -0
  102. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/textwrap.c +0 -0
  103. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/utf8.c +0 -0
  104. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/wcstwidth.c +0 -0
  105. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/wcswidth.c +0 -0
  106. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/src/wcwidth.c +0 -0
  107. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/tests/test_common.h +0 -0
  108. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/tests/test_escape.c +0 -0
  109. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/tests/test_gcb_class.c +0 -0
  110. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/tests/test_grapheme.c +0 -0
  111. {wcwidth-0.9.0 → wcwidth-0.9.1}/libwcwidth/tests/test_wcwidth.c +0 -0
  112. {wcwidth-0.9.0 → wcwidth-0.9.1}/requirements-develop.txt +0 -0
  113. {wcwidth-0.9.0 → wcwidth-0.9.1}/requirements-docs.in +0 -0
  114. {wcwidth-0.9.0 → wcwidth-0.9.1}/requirements-tests39.in +0 -0
  115. {wcwidth-0.9.0 → wcwidth-0.9.1}/requirements-tests39.txt +0 -0
  116. {wcwidth-0.9.0 → wcwidth-0.9.1}/requirements-update.in +0 -0
  117. {wcwidth-0.9.0 → wcwidth-0.9.1}/requirements-update.txt +0 -0
  118. {wcwidth-0.9.0 → wcwidth-0.9.1}/requirements-wheels.in +0 -0
  119. {wcwidth-0.9.0 → wcwidth-0.9.1}/requirements-wheels.txt +0 -0
  120. {wcwidth-0.9.0 → wcwidth-0.9.1}/setup.cfg +0 -0
  121. {wcwidth-0.9.0 → wcwidth-0.9.1}/setup.py +0 -0
  122. {wcwidth-0.9.0 → wcwidth-0.9.1}/tests/__init__.py +0 -0
  123. {wcwidth-0.9.0 → wcwidth-0.9.1}/tests/conftest.py +0 -0
  124. {wcwidth-0.9.0 → wcwidth-0.9.1}/tests/test_ambiguous.py +0 -0
  125. {wcwidth-0.9.0 → wcwidth-0.9.1}/tests/test_check_release.py +0 -0
  126. {wcwidth-0.9.0 → wcwidth-0.9.1}/tests/test_clip.py +0 -0
  127. {wcwidth-0.9.0 → wcwidth-0.9.1}/tests/test_clip_cjk_emoji.py +0 -0
  128. {wcwidth-0.9.0 → wcwidth-0.9.1}/tests/test_clip_overtyping.py +0 -0
  129. {wcwidth-0.9.0 → wcwidth-0.9.1}/tests/test_core.py +0 -0
  130. {wcwidth-0.9.0 → wcwidth-0.9.1}/tests/test_emojis.py +0 -0
  131. {wcwidth-0.9.0 → wcwidth-0.9.1}/tests/test_grapheme.py +0 -0
  132. {wcwidth-0.9.0 → wcwidth-0.9.1}/tests/test_hyperlink.py +0 -0
  133. {wcwidth-0.9.0 → wcwidth-0.9.1}/tests/test_justify.py +0 -0
  134. {wcwidth-0.9.0 → wcwidth-0.9.1}/tests/test_setup.py +0 -0
  135. {wcwidth-0.9.0 → wcwidth-0.9.1}/tests/test_sgr_state.py +0 -0
  136. {wcwidth-0.9.0 → wcwidth-0.9.1}/tests/test_text_sizing.py +0 -0
  137. {wcwidth-0.9.0 → wcwidth-0.9.1}/tests/test_textwrap.py +0 -0
  138. {wcwidth-0.9.0 → wcwidth-0.9.1}/tests/test_ucslevel.py +0 -0
  139. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/_clip.py +0 -0
  140. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/_constants.py +0 -0
  141. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/_wcswidth.py +0 -0
  142. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/_wcwidth.py +0 -0
  143. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/_wcwidth_c.pyi +0 -0
  144. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/_width.py +0 -0
  145. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/align.py +0 -0
  146. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/bisearch.py +0 -0
  147. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/control_codes.py +0 -0
  148. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/escape_sequences.py +0 -0
  149. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/grapheme.py +0 -0
  150. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/hyperlink.py +0 -0
  151. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/py.typed +0 -0
  152. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/sgr_state.py +0 -0
  153. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_ambiguous.py +0 -0
  154. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme.py +0 -0
  155. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/__init__.py +0 -0
  156. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/_known_058c7585.py +0 -0
  157. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/_known_2148ff29.py +0 -0
  158. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/_known_244dc88b.py +0 -0
  159. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/_known_336a71a1.py +0 -0
  160. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/_known_33ac73f2.py +0 -0
  161. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/_known_3a1895d5.py +0 -0
  162. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/_known_59180a0d.py +0 -0
  163. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/_known_65c6beb5.py +0 -0
  164. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/_known_78f828e8.py +0 -0
  165. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/_known_8c2ae14d.py +0 -0
  166. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/_known_900356bf.py +0 -0
  167. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/_known_97904ece.py +0 -0
  168. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/_known_a5880eeb.py +0 -0
  169. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/_known_b6d33cf4.py +0 -0
  170. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/_known_c57c295b.py +0 -0
  171. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/_known_cf02297d.py +0 -0
  172. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/_known_e18ac6a7.py +0 -0
  173. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/_known_e2b75ee3.py +0 -0
  174. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/_known_fb5fd79b.py +0 -0
  175. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/_known_fdc99132.py +0 -0
  176. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_grapheme_overrides/_registry.py +0 -0
  177. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_mc.py +0 -0
  178. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_overrides.py +0 -0
  179. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_term_programs.py +0 -0
  180. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_vs15.py +0 -0
  181. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_vs16.py +0 -0
  182. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_wide.py +0 -0
  183. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/table_zero.py +0 -0
  184. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/text_sizing.py +0 -0
  185. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/textwrap.py +0 -0
  186. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/unicode_versions.py +0 -0
  187. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth/wcwidth.py +0 -0
  188. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth.egg-info/dependency_links.txt +0 -0
  189. {wcwidth-0.9.0 → wcwidth-0.9.1}/wcwidth.egg-info/top_level.txt +0 -0
@@ -14,7 +14,11 @@ wheelhouse
14
14
  *.so
15
15
  dist
16
16
  docs/_build
17
+ # generated from the C headers by bin/update-docs.py, not committed
18
+ docs/api_c.rst
17
19
  htmlcov
20
+ htmlcov-c
21
+ coverage-c.lcov
18
22
  .coveralls.yml
19
23
  data
20
24
  .DS_Store
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wcwidth
3
- Version: 0.9.0
3
+ Version: 0.9.1
4
4
  Summary: Measures the displayed width of unicode strings in a terminal
5
5
  Author-email: Jeff Quast <contact@jeffquast.com>
6
6
  Project-URL: Homepage, https://github.com/jquast/wcwidth
@@ -6,7 +6,9 @@ from __future__ import annotations
6
6
  # std imports
7
7
  import os
8
8
  import re
9
+ import sys
9
10
  import glob
11
+ import argparse
10
12
  import textwrap
11
13
  import importlib.util
12
14
 
@@ -21,7 +23,7 @@ import jinja2
21
23
  # docs/unicode_version.rst, docs/libwcwidth.rst, and the list_term_programs()
22
24
  # example in README.rst.
23
25
 
24
- PATH_UP = os.path.relpath(os.path.join(os.path.dirname(__file__), os.path.pardir))
26
+ PATH_UP = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
25
27
  JINJA_ENV = jinja2.Environment(
26
28
  loader=jinja2.FileSystemLoader(os.path.join(PATH_UP, 'code_templates')),
27
29
  keep_trailing_newline=True,
@@ -458,21 +460,29 @@ def report(label: str, changed: bool) -> None:
458
460
  print(f'{label}: {"updated" if changed else "up-to-date"}')
459
461
 
460
462
 
461
- def write_if_changed(path: str, content: str, label: str) -> None:
462
- """Write *content* to *path* only if it differs, reporting the result."""
463
+ def write_if_changed(path: str, content: str, label: str, check: bool = False) -> bool:
464
+ """
465
+ Write *content* to *path* only if it differs, reporting and returning whether it did.
466
+
467
+ With *check*, report the difference without writing, so a stale file fails the run.
468
+ """
463
469
  try:
464
470
  with open(path, encoding='utf-8') as fin:
465
471
  original = fin.read()
466
472
  except FileNotFoundError:
467
473
  original = ''
468
- if content != original:
469
- new_path = path + '.new'
470
- with open(new_path, 'w', encoding='utf-8', newline='\n') as fout:
471
- fout.write(content)
472
- os.replace(new_path, path)
473
- print(f'{label}: updated')
474
- else:
474
+ if content == original:
475
475
  print(f'{label}: up-to-date')
476
+ return False
477
+ if check:
478
+ print(f'{label}: out of date, run bin/update-docs.py')
479
+ return True
480
+ new_path = path + '.new'
481
+ with open(new_path, 'w', encoding='utf-8', newline='\n') as fout:
482
+ fout.write(content)
483
+ os.replace(new_path, path)
484
+ print(f'{label}: updated')
485
+ return True
476
486
 
477
487
 
478
488
  def update_libwcwidth_term_programs() -> bool:
@@ -545,18 +555,31 @@ def unicode_version_page() -> str:
545
555
  source_headers=unicode_source_headers())
546
556
 
547
557
 
548
- def main() -> None:
549
- """Regenerate all generated documentation files."""
550
- write_if_changed(API_C_OUTPUT, render_api_doc(), 'docs/api_c.rst')
558
+ def main(api_only: bool = False, check: bool = False) -> int:
559
+ """Regenerate all generated documentation files, or only docs/api_c.rst."""
560
+ stale = write_if_changed(API_C_OUTPUT, render_api_doc(), 'docs/api_c.rst', check)
561
+ if api_only:
562
+ # api_c.rst is parsed from the C headers, but everything below reads the
563
+ # Unicode sources under data/, which only `tox -e fetch` downloads.
564
+ return int(check and stale)
551
565
  write_if_changed(os.path.join(PATH_DOCS, 'unicode_version.rst'),
552
- unicode_version_page(), 'docs/unicode_version.rst')
566
+ unicode_version_page(), 'docs/unicode_version.rst', check)
553
567
  report('docs/libwcwidth.rst: canonical terminal names',
554
568
  update_libwcwidth_term_programs())
555
569
  report('docs/libwcwidth.rst: Unicode version',
556
570
  update_libwcwidth_unicode_version())
557
571
  report('README.rst: list_term_programs() example',
558
572
  update_readme_term_programs())
573
+ return int(check and stale)
559
574
 
560
575
 
561
576
  if __name__ == '__main__':
562
- main()
577
+ parser = argparse.ArgumentParser(description=__doc__)
578
+ parser.add_argument('--api-only', action='store_true',
579
+ help='regenerate docs/api_c.rst only, which needs no Unicode data')
580
+ parser.add_argument('--check', action='store_true',
581
+ help='report stale output instead of writing it, for CI')
582
+ args = parser.parse_args()
583
+ if args.check and not args.api_only:
584
+ parser.error('--check needs --api-only: the other outputs read the fetched Unicode data')
585
+ sys.exit(main(api_only=args.api_only, check=args.check))
@@ -18,6 +18,12 @@ To run tests with detailed coverage reporting showing missing lines::
18
18
 
19
19
  tox -epy314 -- --cov-report=term-missing
20
20
 
21
+ The C11 library's coverage is measured separately, since coverage.py cannot see compiled
22
+ code. This merges two instrumented runs, the C11 test suite and the Python suite driving
23
+ the C extension, and writes ``coverage-c.lcov`` and ``htmlcov-c/index.html``::
24
+
25
+ tox -e c_coverage
26
+
21
27
  Updating Unicode Version
22
28
  ------------------------
23
29
 
@@ -2,7 +2,13 @@
2
2
  History
3
3
  =======
4
4
 
5
- 0.9.0 *unreleased* ('jq/libwcwidth-shim-wheels' branch)
5
+ 0.9.1 *2026-09-23*
6
+ * **Bugfix** a regression in 0.9.0 Python and libwcwidth_ `width()`_ mis-measured CSI sequences
7
+ over 64 bytes, `PR #271`_.
8
+ * **Performance** improvement of ~15% for 11 many Python API functions when using
9
+ default ``ambiguous_width`` and ``term_program`` arguments. `PR #273`_
10
+
11
+ 0.9.0 *2026-09-23*
6
12
  * **New** Optional CPython extension using libwcwidth_, improving performance 10-58x `PR #248`_.
7
13
  * **Drop Support** for Python 3.8, `PR #248`_.
8
14
 
@@ -299,6 +305,8 @@ https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c::
299
305
  .. _`PR #253`: https://github.com/jquast/wcwidth/pull/253
300
306
  .. _`PR #259`: https://github.com/jquast/wcwidth/pull/259
301
307
  .. _`PR #262`: https://github.com/jquast/wcwidth/pull/262
308
+ .. _`PR #271`: https://github.com/jquast/wcwidth/pull/271
309
+ .. _`PR #273`: https://github.com/jquast/wcwidth/pull/273
302
310
  .. _`Issue #101`: https://github.com/jquast/wcwidth/issues/101
303
311
  .. _`Issue #155`: https://github.com/jquast/wcwidth/issues/155
304
312
  .. _`Issue #211`: https://github.com/jquast/wcwidth/issues/211
@@ -10,16 +10,20 @@ The Python documentation_ closely matches this C library. The C API adds UTF-8
10
10
  array interfaces.
11
11
 
12
12
  The lowest-level functions are derived from POSIX.1-2001 and POSIX.1-2008 `wcwidth(3)`_ and
13
- `wcswidth(3)`_, which this library implements as `wcwidth_u32()`_ and `wcswidth_u32()`_. These
14
- functions return -1 when C0 and C1 control codes other than NUL are present; NUL measures as
15
- zero-width. They do not parse terminal escape sequences: any escape sequence contains control
16
- codes, so these functions return -1 for it.
13
+ `wcswidth(3)`_, which this library implements as :ref:`wcwidth_u32() <libwcwidth-wcwidth>` and
14
+ :ref:`wcswidth_u32() <libwcwidth-wcswidth>`. These functions return -1 when C0 and C1 control
15
+ codes other than NUL are present; NUL measures as zero-width.
16
+ :ref:`wcstwidth_u8() <libwcwidth-wcstwidth>` applies corrections for a specific terminal program,
17
+ as described in the Python Corrections_ documentation.
17
18
 
18
- `wcwidth_width_u8()`_ is a higher-level wrapper of `wcswidth_u8()`_ that also measures terminal
19
- control sequences, like colors, bold, tabstops, and horizontal cursor movement.
19
+ :ref:`wcwidth_width_u8() <libwcwidth-width>` is a higher-level wrapper of
20
+ :ref:`wcswidth_u8() <libwcwidth-wcswidth>` that also measures terminal control sequences, like
21
+ colors, bold, tabstops, and horizontal cursor movement.
20
22
 
21
- `wcstwidth_u8()`_ applies corrections for a specific terminal program and version, as described
22
- in the Python Corrections_ documentation.
23
+ The text transforms have their own sections:
24
+ :ref:`wcwidth_ljust_u8() <libwcwidth-align>` with ``rjust`` and ``center``,
25
+ :ref:`wcwidth_clip_u8() <libwcwidth-clip>`, :ref:`wcwidth_wrap_u8() <libwcwidth-wrap>`, and
26
+ :ref:`wcwidth_escape_strip() <libwcwidth-escape-strip>`.
23
27
 
24
28
  Quick Start
25
29
  -----------
@@ -187,6 +191,8 @@ throughout, or use the ``_u32`` forms and re-encode the result. `wcwidth_encode
187
191
  Re-encoding to a legacy charset is the caller's iconv(3) or ICU (``ucnv_*``) call; a byte cast
188
192
  works when every codepoint fits the target, and iconv reports ``EILSEQ`` when one does not.
189
193
 
194
+ .. _libwcwidth-wcwidth:
195
+
190
196
  wcwidth_u32()
191
197
  ~~~~~~~~~~~~~
192
198
 
@@ -206,6 +212,8 @@ U+2640 above is Ambiguous, so it answers to the second argument, while U+2630 is
206
212
  2 under either setting. A single codepoint needs no ``_u8`` variant; use `wcswidth_u8()`_ to
207
213
  measure text.
208
214
 
215
+ .. _libwcwidth-wcswidth:
216
+
209
217
  wcswidth_u32() and wcswidth_u8()
210
218
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
211
219
 
@@ -219,6 +227,8 @@ when any control code other than NUL is present:
219
227
  wcswidth_u32(family, 5, 1) /* man ZWJ woman ZWJ girl */ 2
220
228
  wcswidth_u8("café", 5, 1) 4
221
229
 
230
+ .. _libwcwidth-wcstwidth:
231
+
222
232
  wcstwidth_u32() and wcstwidth_u8()
223
233
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
224
234
 
@@ -230,6 +240,8 @@ argument applies terminal-specific corrections:
230
240
  wcswidth_u8("☰", 3, 1) /* U+2630, wide */ 2
231
241
  wcstwidth_u8("☰", 3, 1, "vte") 1
232
242
 
243
+ .. _libwcwidth-width:
244
+
233
245
  wcwidth_width_u32() and wcwidth_width_u8()
234
246
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
235
247
 
@@ -261,6 +273,8 @@ codes:
261
273
  /* err is WCWIDTH_ERROR_VERTICAL_CTRL */
262
274
  }
263
275
 
276
+ .. _libwcwidth-align:
277
+
264
278
  wcwidth_ljust_u8(), wcwidth_rjust_u8(), and wcwidth_center_u8()
265
279
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
266
280
 
@@ -288,6 +302,8 @@ returns a ``malloc``\ 'd NUL-terminated string the caller must ``free``:
288
302
  /* "*café*" */
289
303
  free(out);
290
304
 
305
+ .. _libwcwidth-clip:
306
+
291
307
  wcwidth_clip_u8()
292
308
  ~~~~~~~~~~~~~~~~~
293
309
 
@@ -344,6 +360,8 @@ Movement is reported wherever it appears; the OSC sequences only where the clip
344
360
  `wcwidth_clip_u32()`_ is the codepoint-array form, returning a ``malloc``\ 'd array of ``*out_len``
345
361
  codepoints.
346
362
 
363
+ .. _libwcwidth-wrap:
364
+
347
365
  wcwidth_wrap_u8() and wcwidth_wrap_u8_text()
348
366
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
349
367
 
@@ -383,6 +401,8 @@ unbreakable unit, so a line is never broken inside one, even at a space or hyphe
383
401
  text. Python's `wrap()`_ behaves the same way. `wcwidth_wrap_u32()`_ and `wcwidth_wrap_u32_text()`_ are the
384
402
  codepoint-array forms.
385
403
 
404
+ .. _libwcwidth-escape-strip:
405
+
386
406
  wcwidth_escape_strip()
387
407
  ~~~~~~~~~~~~~~~~~~~~~~
388
408
 
@@ -18,8 +18,8 @@ extern "C" {
18
18
 
19
19
  #define WCWIDTH_VERSION_MAJOR 0
20
20
  #define WCWIDTH_VERSION_MINOR 9
21
- #define WCWIDTH_VERSION_PATCH 0
22
- #define WCWIDTH_VERSION "0.9.0"
21
+ #define WCWIDTH_VERSION_PATCH 1
22
+ #define WCWIDTH_VERSION "0.9.1"
23
23
 
24
24
  /* Unicode version the library tables were generated from. */
25
25
  #define WCWIDTH_UNICODE_VERSION "18.0.0"
@@ -90,12 +90,6 @@ is_illegal_ctrl(uint32_t ucs)
90
90
  if (ucs >= 28 && ucs <= 31) {
91
91
  return true; /* FS, GS, RS, US */
92
92
  }
93
- if (ucs == 0x7F) {
94
- return true; /* DEL */
95
- }
96
- if (ucs >= 0x80 && ucs <= 0x9F) {
97
- return true; /* C1 control characters */
98
- }
99
93
  return false;
100
94
  }
101
95
 
@@ -417,7 +411,9 @@ static size_t
417
411
  escape_span_u32(const uint32_t *cp, size_t n, size_t idx)
418
412
  {
419
413
  char buf[64];
414
+ char *heap = NULL;
420
415
  size_t cap = 16;
416
+ size_t span = 1;
421
417
  wcwidth_esc_result_t result;
422
418
 
423
419
  if (idx + 1 < n && (cp[idx + 1] == '(' || cp[idx + 1] == ')')) {
@@ -428,26 +424,56 @@ escape_span_u32(const uint32_t *cp, size_t n, size_t idx)
428
424
  }
429
425
 
430
426
  /*
431
- * Copy 16 first: nearly every sequence is shorter, and copying 64 each
432
- * time dominates on SGR-dense text. The classifier misreads a truncated
433
- * run as an unterminated sequence, so widen and retry before classifying.
427
+ * Widen before classifying: the classifier reads a truncated run as
428
+ * unterminated. Stop once a byte has ended the sequence, or the copy would
429
+ * run on into the text that follows, and only a CSI with no final byte
430
+ * takes a heap copy.
434
431
  */
435
432
  for (;;) {
433
+ char *out = (heap != NULL) ? heap : buf;
434
+ bool open = true; /* the sequence has no final byte yet */
436
435
  size_t j = 0;
437
436
 
438
437
  while (j < cap && idx + j < n && cp[idx + j] < 0x80) {
439
- buf[j] = (char) cp[idx + j];
438
+ uint32_t b = cp[idx + j];
439
+
440
+ if (j > 1 && (b < 0x20 || b > 0x3F)) {
441
+ open = false;
442
+ }
443
+ out[j] = (char) b;
440
444
  j++;
441
445
  }
442
- if (j == cap && idx + j < n && cp[idx + j] < 0x80 && cap < sizeof(buf)) {
443
- cap = sizeof(buf);
444
- continue;
446
+ if (open && j == cap && idx + j < n && cp[idx + j] < 0x80) {
447
+ if (cap < sizeof(buf)) {
448
+ cap = sizeof(buf);
449
+ continue;
450
+ }
451
+ if (heap == NULL && cp[idx + 1] == '[') {
452
+ size_t run = cap;
453
+
454
+ while (idx + run < n && cp[idx + run] < 0x80) {
455
+ run++;
456
+ }
457
+ heap = (char *) malloc(run);
458
+ if (heap == NULL) {
459
+ return 1;
460
+ }
461
+ cap = run;
462
+ continue;
463
+ }
445
464
  }
446
- if (j == 0 || !wcwidth_escape_classify(buf, j, 0, &result) || result.length == 0) {
447
- return 1;
465
+ if (j == 0 || !wcwidth_escape_classify(out, j, 0, &result) || result.length == 0) {
466
+ span = 1;
448
467
  }
449
- return result.length;
468
+ else {
469
+ span = result.length;
470
+ }
471
+ break;
472
+ }
473
+ if (heap != NULL) {
474
+ free(heap);
450
475
  }
476
+ return span;
451
477
  }
452
478
 
453
479
  /*
@@ -234,6 +234,34 @@ TEST(align_control_codes_strict)
234
234
  ASSERT_EQ(WCWIDTH_ERROR_ILLEGAL_CTRL, error);
235
235
  }
236
236
 
237
+ TEST(null_opts_defaults)
238
+ {
239
+ uint32_t cps[] = {'h', 'i'};
240
+ size_t len = 0;
241
+ int error = WCWIDTH_ERROR_NONE;
242
+ char *s;
243
+ uint32_t *u;
244
+
245
+ s = wcwidth_ljust_u8("hi", 2, WCWIDTH_PARSE, NULL, &len, &error);
246
+ ASSERT_EQ(2, len);
247
+ free(s);
248
+ s = wcwidth_rjust_u8("hi", 2, WCWIDTH_PARSE, NULL, &len, &error);
249
+ ASSERT_EQ(2, len);
250
+ free(s);
251
+ s = wcwidth_center_u8("hi", 2, WCWIDTH_PARSE, NULL, &len, &error);
252
+ ASSERT_EQ(2, len);
253
+ free(s);
254
+ u = wcwidth_ljust_u32(cps, 2, WCWIDTH_PARSE, NULL, &len, &error);
255
+ ASSERT_EQ(2, len);
256
+ free(u);
257
+ u = wcwidth_rjust_u32(cps, 2, WCWIDTH_PARSE, NULL, &len, &error);
258
+ ASSERT_EQ(2, len);
259
+ free(u);
260
+ u = wcwidth_center_u32(cps, 2, WCWIDTH_PARSE, NULL, &len, &error);
261
+ ASSERT_EQ(2, len);
262
+ free(u);
263
+ }
264
+
237
265
  int
238
266
  main(void)
239
267
  {
@@ -247,5 +275,6 @@ main(void)
247
275
  RUN_TEST(rjust_huge_dest_width_multibyte_fill);
248
276
  RUN_TEST(center_huge_dest_width);
249
277
  RUN_TEST(align_control_codes_strict);
278
+ RUN_TEST(null_opts_defaults);
250
279
  return test_summary();
251
280
  }
@@ -143,6 +143,42 @@ TEST(unsupported_osc)
143
143
  cs_assert("ab\x1b]0;t\x07xy", 0, 3, "ab\x1b]0;t\x07x");
144
144
  }
145
145
 
146
+ TEST(null_opts_and_tabsize_zero)
147
+ {
148
+ size_t len = 0;
149
+ int error = WCWIDTH_ERROR_NONE;
150
+ wcwidth_clip_opts_t opts = WCWIDTH_CLIP_OPTS_DEFAULT;
151
+ char *s;
152
+
153
+ s = wcwidth_clip_u8("a\tb", 3, WCWIDTH_PARSE, NULL, &len, &error);
154
+ ASSERT_NOT_NULL(s);
155
+ free(s);
156
+
157
+ opts.tabsize = 0;
158
+ s = wcwidth_clip_u8("a\tb", 3, WCWIDTH_PARSE, &opts, &len, &error);
159
+ ASSERT_STREQ("a\tb", s);
160
+ free(s);
161
+ }
162
+
163
+ TEST(u32_null_opts_and_past_end)
164
+ {
165
+ uint32_t cps[] = {'h', 'i'};
166
+ size_t len = 0;
167
+ int error = WCWIDTH_ERROR_NONE;
168
+ wcwidth_clip_opts_t opts = WCWIDTH_CLIP_OPTS_DEFAULT;
169
+ uint32_t *u;
170
+
171
+ u = wcwidth_clip_u32(cps, 2, WCWIDTH_PARSE, NULL, &len, &error);
172
+ ASSERT_NOT_NULL(u);
173
+ free(u);
174
+
175
+ opts.v_start = 5;
176
+ opts.v_end = 9;
177
+ u = wcwidth_clip_u32(cps, 2, WCWIDTH_PARSE, &opts, &len, &error);
178
+ ASSERT_NOT_NULL(u);
179
+ free(u);
180
+ }
181
+
146
182
  int
147
183
  main(void)
148
184
  {
@@ -154,5 +190,7 @@ main(void)
154
190
  RUN_TEST(tab_captures_style);
155
191
  RUN_TEST(unsupported_cursor_movement);
156
192
  RUN_TEST(unsupported_osc);
193
+ RUN_TEST(null_opts_and_tabsize_zero);
194
+ RUN_TEST(u32_null_opts_and_past_end);
157
195
  return test_summary();
158
196
  }
@@ -162,6 +162,55 @@ TEST(to_escape_colon_form)
162
162
  ASSERT_STREQ("\x1b[38;2;10;20;30m", buf);
163
163
  }
164
164
 
165
+ TEST(attribute_and_color_codes)
166
+ {
167
+ static const char *const active[] = {"2", "3", "5", "6", "7", "8", "9",
168
+ "21", "30", "37", "40", "47", "48;5;9", "48;2;1;2;3"};
169
+ size_t i;
170
+ wcwidth_sgr_state_t s;
171
+
172
+ for (i = 0; i < sizeof(active) / sizeof(active[0]); i++) {
173
+ s = WCWIDTH_SGR_STATE_DEFAULT;
174
+ wcwidth_sgr_update(&s, active[i], strlen(active[i]));
175
+ ASSERT_TRUE(wcwidth_sgr_is_active(&s));
176
+ }
177
+
178
+ s = WCWIDTH_SGR_STATE_DEFAULT;
179
+ wcwidth_sgr_update(&s, "31;41", 5);
180
+ ASSERT_TRUE(wcwidth_sgr_is_active(&s));
181
+ wcwidth_sgr_update(&s, "39", 2);
182
+ ASSERT_TRUE(wcwidth_sgr_is_active(&s));
183
+ wcwidth_sgr_update(&s, "49", 2);
184
+ ASSERT_FALSE(wcwidth_sgr_is_active(&s));
185
+
186
+ s = WCWIDTH_SGR_STATE_DEFAULT;
187
+ wcwidth_sgr_update(&s, "1;2;3;4;5;6;7;8;9;21", 20);
188
+ ASSERT_TRUE(wcwidth_sgr_is_active(&s));
189
+ wcwidth_sgr_update(&s, "22;23;24;25;27;28;29", 20);
190
+ ASSERT_FALSE(wcwidth_sgr_is_active(&s));
191
+
192
+ s = WCWIDTH_SGR_STATE_DEFAULT;
193
+ wcwidth_sgr_update(&s, "38;5;99999999999999", 19);
194
+ ASSERT_TRUE(wcwidth_sgr_is_active(&s));
195
+
196
+ {
197
+ char many[512];
198
+ size_t p;
199
+
200
+ for (p = 0; p < sizeof(many) - 1; p++) {
201
+ many[p] = (p % 2 == 0) ? '1' : ';';
202
+ }
203
+ many[p] = '\0';
204
+ s = WCWIDTH_SGR_STATE_DEFAULT;
205
+ wcwidth_sgr_update(&s, many, p);
206
+ ASSERT_TRUE(s.bold);
207
+
208
+ s = WCWIDTH_SGR_STATE_DEFAULT;
209
+ wcwidth_sgr_update(&s, ";;1", 3);
210
+ ASSERT_TRUE(s.bold);
211
+ }
212
+ }
213
+
165
214
  int
166
215
  main(void)
167
216
  {
@@ -171,5 +220,6 @@ main(void)
171
220
  RUN_TEST(to_escape_bounded);
172
221
  RUN_TEST(to_escape_colon_form);
173
222
  RUN_TEST(propagate_basic);
223
+ RUN_TEST(attribute_and_color_codes);
174
224
  return test_summary();
175
225
  }
@@ -40,10 +40,23 @@ TEST(display_width_basic)
40
40
  ASSERT_EQ(3, wcwidth_ts_display_width(&ts, 1));
41
41
  }
42
42
 
43
+ TEST(parse_invalid_and_clamped)
44
+ {
45
+ wcwidth_ts_params_t params;
46
+
47
+ ASSERT_TRUE(wcwidth_ts_parse_params("z=1", 3, &params)); /* unknown key */
48
+ ASSERT_TRUE(wcwidth_ts_parse_params("s=-5", 4, &params)); /* clamped low */
49
+ ASSERT_TRUE(wcwidth_ts_parse_params("s=", 2, &params)); /* no digits */
50
+ ASSERT_TRUE(wcwidth_ts_parse_params("s=2x", 4, &params)); /* trailing junk */
51
+ ASSERT_TRUE(wcwidth_ts_parse_params("s=99999999999999999999", 22, &params));
52
+ ASSERT_TRUE(params.scale >= 1); /* saturated, then clamped high */
53
+ }
54
+
43
55
  int
44
56
  main(void)
45
57
  {
46
58
  RUN_TEST(parse_basic);
47
59
  RUN_TEST(display_width_basic);
60
+ RUN_TEST(parse_invalid_and_clamped);
48
61
  return test_summary();
49
62
  }
@@ -240,6 +240,55 @@ TEST(wrap_lone_continuation_bytes)
240
240
  }
241
241
  }
242
242
 
243
+ TEST(wrap_max_lines_placeholder)
244
+ {
245
+ char *out;
246
+ size_t len;
247
+ wcwidth_wrap_opts_t o = WCWIDTH_WRAP_OPTS_DEFAULT;
248
+ const char *exp[] = {"aa bb [...]"};
249
+
250
+ o.width = 11;
251
+ o.max_lines = 1;
252
+ ASSERT_EQ(0, wcwidth_wrap_u8("aa bb cccccccc", 14, &o, &out, &len));
253
+ check_lines(out, len, exp, 1);
254
+ free(out);
255
+
256
+ o = WCWIDTH_WRAP_OPTS_DEFAULT;
257
+ o.width = 4;
258
+ o.max_lines = 1;
259
+ ASSERT_EQ(-2, wcwidth_wrap_u8("aaaa bbbb", 9, &o, &out, &len));
260
+
261
+ o = WCWIDTH_WRAP_OPTS_DEFAULT;
262
+ o.width = 6;
263
+ o.max_lines = 1;
264
+ ASSERT_EQ(0, wcwidth_wrap_u8("aa bbbb", 7, &o, &out, &len));
265
+ free(out);
266
+
267
+ o = WCWIDTH_WRAP_OPTS_DEFAULT;
268
+ o.width = 6;
269
+ o.max_lines = 2;
270
+ ASSERT_EQ(0, wcwidth_wrap_u8("aa bb cc dd ee", 14, &o, &out, &len));
271
+ free(out);
272
+
273
+ o = WCWIDTH_WRAP_OPTS_DEFAULT;
274
+ o.width = 11;
275
+ o.max_lines = 2;
276
+ ASSERT_EQ(0, wcwidth_wrap_u8("aa bb ccccccccc dd", 18, &o, &out, &len));
277
+ free(out);
278
+
279
+ o = WCWIDTH_WRAP_OPTS_DEFAULT;
280
+ o.width = 3;
281
+ o.expand_tabs = false;
282
+ ASSERT_EQ(0, wcwidth_wrap_u8("a\tb", 3, &o, &out, &len));
283
+ free(out);
284
+
285
+ o = WCWIDTH_WRAP_OPTS_DEFAULT;
286
+ o.width = 2;
287
+ o.break_long_words = false;
288
+ ASSERT_EQ(0, wcwidth_wrap_u8("abcdef", 6, &o, &out, &len));
289
+ free(out);
290
+ }
291
+
243
292
  int
244
293
  main(void)
245
294
  {
@@ -251,5 +300,6 @@ main(void)
251
300
  RUN_TEST(wrap_lines_u8);
252
301
  RUN_TEST(wrap_tabsize_zero);
253
302
  RUN_TEST(wrap_lone_continuation_bytes);
303
+ RUN_TEST(wrap_max_lines_placeholder);
254
304
  return test_summary();
255
305
  }
@@ -50,9 +50,27 @@ TEST(encode_roundtrip)
50
50
  }
51
51
  }
52
52
 
53
+ TEST(decode_malformed)
54
+ {
55
+ static const char *const bad[] = {
56
+ "\xf8", "\xff", "\xe2\x82", "\xe2\x28\xa1",
57
+ "\xc0\xaf", "\xe0\x80\xaf", "\xed\xa0\x80", "\xf4\x90\x80\x80"};
58
+ size_t i;
59
+ uint32_t cp = 0;
60
+
61
+ ASSERT_EQ(0, wcwidth_utf8_decode_single("", 0, &cp));
62
+ for (i = 0; i < sizeof(bad) / sizeof(bad[0]); i++) {
63
+ size_t used = wcwidth_utf8_decode_single(bad[i], strlen(bad[i]), &cp);
64
+
65
+ ASSERT_EQ(0xFFFD, cp);
66
+ ASSERT_TRUE(used >= 1 && used <= strlen(bad[i]));
67
+ }
68
+ }
69
+
53
70
  int
54
71
  main(void)
55
72
  {
56
73
  RUN_TEST(encode_roundtrip);
74
+ RUN_TEST(decode_malformed);
57
75
  return test_summary();
58
76
  }
@@ -88,6 +88,12 @@ TEST(wcstwidth_terminal_overrides)
88
88
  ASSERT_EQ(2, wcstwidth_u32(family, 5, 1, "kitty"));
89
89
  }
90
90
 
91
+ TEST(null_input)
92
+ {
93
+ ASSERT_EQ(0, wcswidth_u8(NULL, 0, 1));
94
+ ASSERT_EQ(0, wcstwidth_u8(NULL, 0, 1, "kitty"));
95
+ }
96
+
91
97
  int
92
98
  main(void)
93
99
  {
@@ -97,5 +103,6 @@ main(void)
97
103
  RUN_TEST(wcstwidth_u8_basic);
98
104
  RUN_TEST(wcstwidth_u8_long);
99
105
  RUN_TEST(wcstwidth_terminal_overrides);
106
+ RUN_TEST(null_input);
100
107
  return test_summary();
101
108
  }