backrefs 5.8__tar.gz → 6.0__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 (70) hide show
  1. {backrefs-5.8 → backrefs-6.0}/LICENSE.md +1 -1
  2. {backrefs-5.8 → backrefs-6.0}/PKG-INFO +6 -7
  3. {backrefs-5.8 → backrefs-6.0}/README.md +4 -6
  4. {backrefs-5.8 → backrefs-6.0}/backrefs/__meta__.py +1 -1
  5. {backrefs-5.8 → backrefs-6.0}/backrefs/_bre_parse.py +54 -27
  6. {backrefs-5.8 → backrefs-6.0}/backrefs/_bregex_parse.py +39 -24
  7. {backrefs-5.8 → backrefs-6.0}/docs/src/markdown/about/changelog.md +18 -2
  8. {backrefs-5.8 → backrefs-6.0}/docs/src/markdown/refs.md +83 -57
  9. {backrefs-5.8 → backrefs-6.0}/hatch_build.py +1 -0
  10. {backrefs-5.8 → backrefs-6.0}/mkdocs.yml +1 -3
  11. {backrefs-5.8 → backrefs-6.0}/pyproject.toml +2 -2
  12. {backrefs-5.8 → backrefs-6.0}/tests/test_bre.py +25 -0
  13. {backrefs-5.8 → backrefs-6.0}/tests/test_bregex.py +6 -0
  14. {backrefs-5.8 → backrefs-6.0}/tools/unipropgen.py +41 -45
  15. backrefs-5.8/tools/unicodedata/11.0.0.zip +0 -0
  16. {backrefs-5.8 → backrefs-6.0}/.coveragerc +0 -0
  17. {backrefs-5.8 → backrefs-6.0}/.gitignore +0 -0
  18. {backrefs-5.8 → backrefs-6.0}/.pyspelling.yml +0 -0
  19. {backrefs-5.8 → backrefs-6.0}/backrefs/__init__.py +0 -0
  20. {backrefs-5.8 → backrefs-6.0}/backrefs/_bregex_typing.py +0 -0
  21. {backrefs-5.8 → backrefs-6.0}/backrefs/bre.py +0 -0
  22. {backrefs-5.8 → backrefs-6.0}/backrefs/bregex.py +0 -0
  23. {backrefs-5.8 → backrefs-6.0}/backrefs/py.typed +0 -0
  24. {backrefs-5.8 → backrefs-6.0}/backrefs/uniprops/__init__.py +0 -0
  25. {backrefs-5.8 → backrefs-6.0}/backrefs/util.py +0 -0
  26. {backrefs-5.8 → backrefs-6.0}/docs/src/markdown/.snippets/links.md +0 -0
  27. {backrefs-5.8 → backrefs-6.0}/docs/src/markdown/about/contributing.md +0 -0
  28. {backrefs-5.8 → backrefs-6.0}/docs/src/markdown/about/license.md +0 -0
  29. {backrefs-5.8 → backrefs-6.0}/docs/src/markdown/about/security.md +0 -0
  30. {backrefs-5.8 → backrefs-6.0}/docs/src/markdown/index.md +0 -0
  31. {backrefs-5.8 → backrefs-6.0}/docs/src/markdown/usage.md +0 -0
  32. {backrefs-5.8 → backrefs-6.0}/docs/theme/announce.html +0 -0
  33. {backrefs-5.8 → backrefs-6.0}/requirements/docs.txt +0 -0
  34. {backrefs-5.8 → backrefs-6.0}/requirements/lint.txt +0 -0
  35. {backrefs-5.8 → backrefs-6.0}/requirements/test.txt +0 -0
  36. {backrefs-5.8 → backrefs-6.0}/tests/__init__.py +0 -0
  37. {backrefs-5.8 → backrefs-6.0}/tests/test_age.py +0 -0
  38. {backrefs-5.8 → backrefs-6.0}/tests/test_bidiclass.py +0 -0
  39. {backrefs-5.8 → backrefs-6.0}/tests/test_bidipairedbrackettype.py +0 -0
  40. {backrefs-5.8 → backrefs-6.0}/tests/test_binary.py +0 -0
  41. {backrefs-5.8 → backrefs-6.0}/tests/test_block.py +0 -0
  42. {backrefs-5.8 → backrefs-6.0}/tests/test_canonicalcombiningclass.py +0 -0
  43. {backrefs-5.8 → backrefs-6.0}/tests/test_decompositiontype.py +0 -0
  44. {backrefs-5.8 → backrefs-6.0}/tests/test_eastasianwidth.py +0 -0
  45. {backrefs-5.8 → backrefs-6.0}/tests/test_generalcategory.py +0 -0
  46. {backrefs-5.8 → backrefs-6.0}/tests/test_graphemeclusterbreak.py +0 -0
  47. {backrefs-5.8 → backrefs-6.0}/tests/test_hangulsyllabletype.py +0 -0
  48. {backrefs-5.8 → backrefs-6.0}/tests/test_indicpositionalcategory.py +0 -0
  49. {backrefs-5.8 → backrefs-6.0}/tests/test_indicsylabiccategory.py +0 -0
  50. {backrefs-5.8 → backrefs-6.0}/tests/test_joininggroup.py +0 -0
  51. {backrefs-5.8 → backrefs-6.0}/tests/test_joiningtype.py +0 -0
  52. {backrefs-5.8 → backrefs-6.0}/tests/test_linebreak.py +0 -0
  53. {backrefs-5.8 → backrefs-6.0}/tests/test_numerictype.py +0 -0
  54. {backrefs-5.8 → backrefs-6.0}/tests/test_numericvalue.py +0 -0
  55. {backrefs-5.8 → backrefs-6.0}/tests/test_quickcheck.py +0 -0
  56. {backrefs-5.8 → backrefs-6.0}/tests/test_script.py +0 -0
  57. {backrefs-5.8 → backrefs-6.0}/tests/test_scriptextensions.py +0 -0
  58. {backrefs-5.8 → backrefs-6.0}/tests/test_sentencebreak.py +0 -0
  59. {backrefs-5.8 → backrefs-6.0}/tests/test_uniprops.py +0 -0
  60. {backrefs-5.8 → backrefs-6.0}/tests/test_versions.py +0 -0
  61. {backrefs-5.8 → backrefs-6.0}/tests/test_verticalorientation.py +0 -0
  62. {backrefs-5.8 → backrefs-6.0}/tests/test_wordbreak.py +0 -0
  63. {backrefs-5.8 → backrefs-6.0}/tools/__init__.py +0 -0
  64. {backrefs-5.8 → backrefs-6.0}/tools/unicodedata/13.0.0.zip +0 -0
  65. {backrefs-5.8 → backrefs-6.0}/tools/unicodedata/14.0.0.zip +0 -0
  66. {backrefs-5.8 → backrefs-6.0}/tools/unicodedata/15.0.0.zip +0 -0
  67. {backrefs-5.8 → backrefs-6.0}/tools/unicodedata/15.1.0.zip +0 -0
  68. {backrefs-5.8 → backrefs-6.0}/tools/unicodedata/16.0.0.zip +0 -0
  69. {backrefs-5.8 → backrefs-6.0}/tools/unicodedata/LICENSE +0 -0
  70. {backrefs-5.8 → backrefs-6.0}/tools/unidatadownload.py +0 -0
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015 - 2023 Isaac Muse
3
+ Copyright (c) 2015 - 2025 Isaac Muse
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: backrefs
3
- Version: 5.8
3
+ Version: 6.0
4
4
  Summary: A wrapper around re and regex that adds additional back references.
5
5
  Project-URL: Homepage, https://github.com/facelessuser/backrefs
6
6
  Author-email: Isaac Muse <Isaac.Muse@gmail.com>
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
21
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
23
  Classifier: Typing :: Typed
23
24
  Requires-Python: >=3.9
@@ -26,13 +27,12 @@ Requires-Dist: regex; extra == 'extras'
26
27
  Description-Content-Type: text/markdown
27
28
 
28
29
  [![Donate via PayPal][donate-image]][donate-link]
29
- [![Discord][discord-image]][discord-link]
30
30
  [![Build][github-ci-image]][github-ci-link]
31
31
  [![Coverage Status][codecov-image]][codecov-link]
32
32
  [![PyPI Version][pypi-image]][pypi-link]
33
33
  [![PyPI Downloads][pypi-down]][pypi-link]
34
34
  [![PyPI - Python Version][python-image]][pypi-link]
35
- ![License][license-image-mit]
35
+ [![License][license-image-mit]][license-link]
36
36
 
37
37
  # Backrefs
38
38
 
@@ -42,7 +42,7 @@ Python's Re and/or Regex. The supported back references actually vary depending
42
42
  used as the engine may already have support for some.
43
43
 
44
44
  ```python
45
- from backrefs import bre
45
+ >>> from backrefs import bre
46
46
  >>> pattern = bre.compile(r'(\p{Letter}+)')
47
47
  >>> pattern.sub(r'\C\1\E', 'sometext')
48
48
  'SOMETEXT'
@@ -56,10 +56,8 @@ https://facelessuser.github.io/backrefs/
56
56
 
57
57
  MIT
58
58
 
59
- [github-ci-image]: https://github.com/facelessuser/backrefs/workflows/build/badge.svg?branch=master&event=push
59
+ [github-ci-image]: https://github.com/facelessuser/backrefs/workflows/build/badge.svg
60
60
  [github-ci-link]: https://github.com/facelessuser/backrefs/actions?query=workflow%3Abuild+branch%3Amaster
61
- [discord-image]: https://img.shields.io/discord/678289859768745989?logo=discord&logoColor=aaaaaa&color=mediumpurple&labelColor=333333
62
- [discord-link]:https://discord.gg/TWs8Tgr
63
61
  [codecov-image]: https://img.shields.io/codecov/c/github/facelessuser/backrefs/master.svg?logo=codecov&logoColor=aaaaaa&labelColor=333333
64
62
  [codecov-link]: https://codecov.io/github/facelessuser/backrefs
65
63
  [pypi-image]: https://img.shields.io/pypi/v/backrefs.svg?logo=pypi&logoColor=aaaaaa&labelColor=333333
@@ -67,6 +65,7 @@ MIT
67
65
  [pypi-link]: https://pypi.python.org/pypi/backrefs
68
66
  [python-image]: https://img.shields.io/pypi/pyversions/backrefs?logo=python&logoColor=aaaaaa&labelColor=333333
69
67
  [license-image-mit]: https://img.shields.io/badge/license-MIT-blue.svg?labelColor=333333
68
+ [license-link]: https://github.com/facelessuser/backrefs/blob/main/LICENSE.md
70
69
  [donate-image]: https://img.shields.io/badge/Donate-PayPal-3fabd1?logo=paypal
71
70
  [donate-link]: https://www.paypal.me/facelessuser
72
71
 
@@ -1,11 +1,10 @@
1
1
  [![Donate via PayPal][donate-image]][donate-link]
2
- [![Discord][discord-image]][discord-link]
3
2
  [![Build][github-ci-image]][github-ci-link]
4
3
  [![Coverage Status][codecov-image]][codecov-link]
5
4
  [![PyPI Version][pypi-image]][pypi-link]
6
5
  [![PyPI Downloads][pypi-down]][pypi-link]
7
6
  [![PyPI - Python Version][python-image]][pypi-link]
8
- ![License][license-image-mit]
7
+ [![License][license-image-mit]][license-link]
9
8
 
10
9
  # Backrefs
11
10
 
@@ -15,7 +14,7 @@ Python's Re and/or Regex. The supported back references actually vary depending
15
14
  used as the engine may already have support for some.
16
15
 
17
16
  ```python
18
- from backrefs import bre
17
+ >>> from backrefs import bre
19
18
  >>> pattern = bre.compile(r'(\p{Letter}+)')
20
19
  >>> pattern.sub(r'\C\1\E', 'sometext')
21
20
  'SOMETEXT'
@@ -29,10 +28,8 @@ https://facelessuser.github.io/backrefs/
29
28
 
30
29
  MIT
31
30
 
32
- [github-ci-image]: https://github.com/facelessuser/backrefs/workflows/build/badge.svg?branch=master&event=push
31
+ [github-ci-image]: https://github.com/facelessuser/backrefs/workflows/build/badge.svg
33
32
  [github-ci-link]: https://github.com/facelessuser/backrefs/actions?query=workflow%3Abuild+branch%3Amaster
34
- [discord-image]: https://img.shields.io/discord/678289859768745989?logo=discord&logoColor=aaaaaa&color=mediumpurple&labelColor=333333
35
- [discord-link]:https://discord.gg/TWs8Tgr
36
33
  [codecov-image]: https://img.shields.io/codecov/c/github/facelessuser/backrefs/master.svg?logo=codecov&logoColor=aaaaaa&labelColor=333333
37
34
  [codecov-link]: https://codecov.io/github/facelessuser/backrefs
38
35
  [pypi-image]: https://img.shields.io/pypi/v/backrefs.svg?logo=pypi&logoColor=aaaaaa&labelColor=333333
@@ -40,6 +37,7 @@ MIT
40
37
  [pypi-link]: https://pypi.python.org/pypi/backrefs
41
38
  [python-image]: https://img.shields.io/pypi/pyversions/backrefs?logo=python&logoColor=aaaaaa&labelColor=333333
42
39
  [license-image-mit]: https://img.shields.io/badge/license-MIT-blue.svg?labelColor=333333
40
+ [license-link]: https://github.com/facelessuser/backrefs/blob/main/LICENSE.md
43
41
  [donate-image]: https://img.shields.io/badge/Donate-PayPal-3fabd1?logo=paypal
44
42
  [donate-link]: https://www.paypal.me/facelessuser
45
43
 
@@ -193,5 +193,5 @@ def parse_version(ver: str) -> Version:
193
193
  return Version(major, minor, micro, release, pre, post, dev)
194
194
 
195
195
 
196
- __version_info__ = Version(5, 8, 0, "final")
196
+ __version_info__ = Version(6, 0, 0, "final")
197
197
  __version__ = __version_info__._get_canonical()
@@ -37,11 +37,19 @@ _STANDARD_ESCAPES = frozenset(('a', 'b', 'f', 'n', 'r', 't', 'v'))
37
37
  _CURLY_BRACKETS = frozenset(('{', '}'))
38
38
  _PROPERTY_STRIP = frozenset((' ', '-', '_'))
39
39
  _PROPERTY = _WORD | _DIGIT | _PROPERTY_STRIP
40
- _GLOBAL_FLAGS = frozenset(('a', 'u', 'L'))
41
- _SCOPED_FLAGS = frozenset(('i', 'm', 's', 'u', 'x'))
40
+ _GLOBAL_FLAGS = frozenset(('a', 'i', 'L', 'm', 's', 'u', 'x'))
41
+ _SCOPED_FLAGS = frozenset(('i', 'm', 's', 'x'))
42
+ _SCOPED_END = frozenset((':', ')'))
42
43
 
43
44
  _CURLY_BRACKETS_ORD = frozenset((0x7b, 0x7d))
44
45
 
46
+ _COMPATIBILITY_PROPERTIES = frozenset(
47
+ (
48
+ 'alpha', 'lower', 'upper', 'punct', 'digit', 'xdigit', 'alnum',
49
+ 'space', 'blank', 'cntrl', 'graph', 'print', 'word'
50
+ )
51
+ )
52
+
45
53
  # Case upper or lower
46
54
  _UPPER = 1
47
55
  _LOWER = 2
@@ -173,20 +181,24 @@ class _SearchParser(Generic[AnyStr]):
173
181
  def flags(self, text: str, scoped: bool = False) -> None:
174
182
  """Analyze flags."""
175
183
 
184
+ flags = text.split('-')
185
+ enable = flags[0]
186
+ disable = flags[1] if len(flags) > 1 else ''
187
+
176
188
  global_retry = False
177
- if ('a' in text or 'L' in text) and self.unicode:
189
+ if ('a' in enable or 'L' in enable) and self.unicode:
178
190
  self.unicode = False
179
191
  if not scoped:
180
192
  self.temp_global_flag_swap["unicode"] = True
181
193
  global_retry = True
182
- elif 'u' in text and not self.unicode and not self.is_bytes:
194
+ elif 'u' in enable and not self.unicode and not self.is_bytes:
183
195
  self.unicode = True
184
196
  if not scoped:
185
197
  self.temp_global_flag_swap["unicode"] = True
186
198
  global_retry = True
187
- if '-x' in text and self.verbose:
199
+ if 'x' in disable and self.verbose:
188
200
  self.verbose = False
189
- elif 'x' in text and not self.verbose:
201
+ elif 'x' in enable and not self.verbose:
190
202
  self.verbose = True
191
203
  if not scoped:
192
204
  self.temp_global_flag_swap["verbose"] = True
@@ -253,7 +265,15 @@ class _SearchParser(Generic[AnyStr]):
253
265
  else:
254
266
  raise SyntaxError(f"Missing or unmatched '{{' at {index}!") from e
255
267
 
256
- return ''.join(prop).lower(), ''.join(value).lower()
268
+ p = ''.join(prop).lower()
269
+ v = ''.join(value).lower()
270
+
271
+ # Ensure when using POSIX form, that any property considered a compatibility property uses the POSIX form.
272
+ # POSIX form is not guaranteed to be different from standard form and sometimes is just an alias for standard.
273
+ if brackets and p in _COMPATIBILITY_PROPERTIES:
274
+ p = 'posix' + p
275
+
276
+ return p, v
257
277
 
258
278
  def get_named_unicode(self, i: _util.StringIter) -> str:
259
279
  """Get Unicode name."""
@@ -345,51 +365,56 @@ class _SearchParser(Generic[AnyStr]):
345
365
 
346
366
  return ''.join(value)
347
367
 
348
- def get_flags(self, i: _util.StringIter, scoped: bool = False) -> str | None:
349
- """Get flags."""
368
+ def get_flags(self, i: _util.StringIter) -> tuple[str | None, bool]:
369
+ """
370
+ Get flags.
371
+
372
+ In Re, flags are quite predictable when global or scoped.
373
+ Global can never be disabled with minus, and never have a `:` after them.
374
+ The global flag set is also very specific, but can be used as enablers in scoped.
375
+
376
+ The returned scoped status will indicate whether flags are generally considered
377
+ scoped flags or global flags.
378
+ """
350
379
 
351
380
  index = i.index
352
381
  value = ['(']
353
382
  toggle = False
354
- end = ':' if scoped else ')'
383
+ smells_scoped = False
355
384
  try:
356
385
  c = next(i)
357
386
  if c != '?':
358
387
  i.rewind(1)
359
- return None
388
+ return None, False
360
389
  value.append(c)
361
390
  c = next(i)
362
- while c != end:
391
+ while c not in _SCOPED_END:
363
392
  if toggle:
364
393
  if c not in _SCOPED_FLAGS:
365
394
  raise ValueError('Bad scope')
366
- toggle = False
367
- elif scoped and c == '-':
395
+ elif c == '-':
396
+ smells_scoped = True
368
397
  toggle = True
369
- elif scoped and c in _GLOBAL_FLAGS:
370
- raise ValueError("Bad flag")
371
- elif c not in _GLOBAL_FLAGS and c not in _SCOPED_FLAGS:
398
+ elif c not in _GLOBAL_FLAGS:
372
399
  raise ValueError("Bad flag")
373
400
  value.append(c)
374
401
  c = next(i)
402
+ if smells_scoped and c != ':':
403
+ raise ValueError("Bad flag")
404
+ elif c == ':':
405
+ smells_scoped = True
375
406
  value.append(c)
376
407
  except Exception:
377
408
  i.rewind(i.index - index)
378
409
  value = []
379
410
 
380
- return ''.join(value) if value else None
411
+ return ''.join(value) if value else None, smells_scoped
381
412
 
382
413
  def subgroup(self, t: str, i: _util.StringIter) -> list[str]:
383
414
  """Handle parenthesis."""
384
415
 
385
416
  current = [] # type: list[str]
386
417
 
387
- # (?flags)
388
- flags = self.get_flags(i)
389
- if flags:
390
- self.flags(flags[2:-1])
391
- return [flags]
392
-
393
418
  # (?#comment)
394
419
  comments = self.get_comments(i)
395
420
  if comments:
@@ -398,11 +423,13 @@ class _SearchParser(Generic[AnyStr]):
398
423
  verbose = self.verbose
399
424
  unicode_flag = self.unicode
400
425
 
401
- # (?flags:pattern)
402
- flags = self.get_flags(i, True)
426
+ # (?flags:pattern) or (?flags)
427
+ flags, scoped = self.get_flags(i)
403
428
  if flags: # pragma: no cover
404
429
  t = flags
405
- self.flags(flags[2:-1], scoped=True)
430
+ self.flags(flags[2:-1], scoped=scoped)
431
+ if not scoped:
432
+ return [flags]
406
433
 
407
434
  current = []
408
435
  try:
@@ -30,9 +30,10 @@ _STANDARD_ESCAPES = frozenset(('a', 'b', 'f', 'n', 'r', 't', 'v'))
30
30
  _CURLY_BRACKETS = frozenset(('{', '}'))
31
31
  _PROPERTY_STRIP = frozenset((' ', '-', '_'))
32
32
  _PROPERTY = _WORD | _DIGIT | _PROPERTY_STRIP
33
- _GLOBAL_FLAGS = frozenset(('L', 'a', 'b', 'e', 'r', 'u', 'p'))
34
- _SCOPED_FLAGS = frozenset(('i', 'm', 's', 'f', 'w', 'x'))
33
+ _GLOBAL_FLAGS = frozenset(('b', 'e', 'p', 'r', 'u'))
34
+ _SCOPED_FLAGS = frozenset(('a', 'f', 'i', 'L', 'm', 's', 'u', 'w', 'x'))
35
35
  _VERSIONS = frozenset(('0', '1'))
36
+ _SCOPED_END = frozenset((':', ')'))
36
37
 
37
38
  _CURLY_BRACKETS_ORD = frozenset((0x7b, 0x7d))
38
39
 
@@ -163,21 +164,25 @@ class _SearchParser(Generic[AnyStr]):
163
164
  def flags(self, text: str, scoped: bool = False) -> None:
164
165
  """Analyze flags."""
165
166
 
167
+ flags = text.split('-')
168
+ enable = flags[0]
169
+ disable = flags[1] if len(flags) > 1 else ''
170
+
166
171
  global_retry = False
167
- if (self.version == _regex.V1 or scoped) and '-x' in text and self.verbose:
172
+ if (self.version == _regex.V1 or scoped) and 'x' in disable and self.verbose:
168
173
  self.verbose = False
169
- elif 'x' in text and not self.verbose:
174
+ elif 'x' in enable and not self.verbose:
170
175
  self.verbose = True
171
176
  if not scoped and self.version == _regex.V0:
172
177
  self.temp_global_flag_swap['verbose'] = True
173
178
  global_retry = True
174
- if 'V0' in text and self.version == _regex.V1: # pragma: no cover
179
+ if 'V0' in enable and self.version == _regex.V1: # pragma: no cover
175
180
  # Default is V0 if none is selected,
176
181
  # so it is unlikely that this will be selected.
177
182
  self.temp_global_flag_swap['version'] = True
178
183
  self.version = _regex.V0
179
184
  global_retry = True
180
- elif "V1" in text and self.version == _regex.V0:
185
+ elif "V1" in enable and self.version == _regex.V0:
181
186
  self.temp_global_flag_swap['version'] = True
182
187
  self.version = _regex.V1
183
188
  global_retry = True
@@ -260,54 +265,61 @@ class _SearchParser(Generic[AnyStr]):
260
265
 
261
266
  return ''.join(value) if value else None
262
267
 
263
- def get_flags(self, i: _util.StringIter, version0: bool, scoped: bool = False) -> str | None:
264
- """Get flags."""
268
+ def get_flags(self, i: _util.StringIter) -> tuple[str | None, bool]:
269
+ """
270
+ Get flags.
271
+
272
+ Regex is more difficult to determine when flags are used in a global and scoped context.
273
+ There is a specific list of global flags, but they can be used in scoped notation and will still
274
+ be considered global, but that does not mean other flags are global. Additionally, flags that can be
275
+ scoped can also used be used in global syntax, but can be disabled with a minus unlike in RE.
276
+
277
+ Bregex only cares about capturing the "verbose" flag and the version flags. Version flags are always
278
+ global and verbose flags will be scoped based on whether they are in a scoped group. The returned
279
+ "scoped" parameter only refers to "verbose".
280
+ """
265
281
 
266
282
  index = i.index
267
283
  value = ['(']
268
284
  version = False
269
285
  toggle = False
270
- end = ':' if scoped else ')'
286
+ smells_scoped = False
271
287
  try:
272
288
  c = next(i)
273
289
  if c != '?':
274
290
  i.rewind(1)
275
- return None
291
+ return None, False
276
292
  value.append(c)
277
293
  c = next(i)
278
- while c != end:
294
+ while c not in _SCOPED_END:
279
295
  if toggle:
280
296
  if c not in _SCOPED_FLAGS:
281
297
  raise ValueError('Bad scope')
282
- toggle = False
283
- elif (not version0 or scoped) and c == '-':
284
- toggle = True
285
298
  elif version:
286
299
  if c not in _VERSIONS:
287
300
  raise ValueError('Bad version')
288
301
  version = False
302
+ elif c == '-':
303
+ toggle = True
289
304
  elif c == 'V':
290
305
  version = True
291
306
  elif c not in _GLOBAL_FLAGS and c not in _SCOPED_FLAGS:
292
307
  raise ValueError("Bad flag")
293
308
  value.append(c)
294
309
  c = next(i)
310
+ if c == ':':
311
+ smells_scoped = True
312
+
295
313
  value.append(c)
296
314
  except Exception:
297
315
  i.rewind(i.index - index)
298
316
  value = []
299
317
 
300
- return ''.join(value) if value else None
318
+ return ''.join(value) if value else None, smells_scoped
301
319
 
302
320
  def subgroup(self, t: str, i: _util.StringIter) -> list[str]:
303
321
  """Handle parenthesis."""
304
322
 
305
- # (?flags)
306
- flags = self.get_flags(i, self.version == _regex.V0)
307
- if flags:
308
- self.flags(flags[2:-1])
309
- return [flags]
310
-
311
323
  # (?#comment)
312
324
  comments = self.get_comments(i)
313
325
  if comments:
@@ -315,11 +327,14 @@ class _SearchParser(Generic[AnyStr]):
315
327
 
316
328
  verbose = self.verbose
317
329
 
318
- # (?flags:pattern)
319
- flags = self.get_flags(i, (self.version == _regex.V0), True)
330
+ # (?flags:pattern) or (?flags)
331
+ # "scoped" only refers to verbose
332
+ flags, scoped = self.get_flags(i)
320
333
  if flags:
321
334
  t = flags
322
- self.flags(flags[2:-1], scoped=True)
335
+ self.flags(flags[2:-1], scoped=scoped)
336
+ if not scoped:
337
+ return [flags]
323
338
 
324
339
  current = [] # type: list[str]
325
340
  try:
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 6.0
4
+
5
+ - **NEW**: POSIX character classes will now always use POSIX compatibility rules instead of Unicode standard rules,
6
+ if any are specified in the Unicode specification. The affected character classes are: `[[:alnum:]]`, `[[:digit:]]`,
7
+ `[[:xdigit:]]`, and `[[:punct:]]`. To explicitly use standard Unicode rules for these compatibility properties, use
8
+ the Unicode property form instead: `[\p{Alnum}]`, `[\p{Digit}]`, `[\p{Punct}]`, or `[\p{XDigit}]`. This has changed
9
+ to ensure no confusion for users expecting compatible POSIX style character class properties.
10
+ - **FIX**: Scoped ASCII/Unicode flags (`(?a:pattern)`/`(?u:pattern)`) should be respected for Unicode properties in
11
+ `bre` and will ensure ASCII or Unicode range if used.
12
+ - **FIX**: Fix issues related to detecting disabled scoped flags.
13
+
14
+ ## 5.9
15
+
16
+ - **NEW**: Add support for Python 3.14.
17
+ - **ENHANCE**: Switch to deploying with PyPI's "Trusted Publisher".
18
+
3
19
  ## 5.8
4
20
 
5
21
  - **NEW**: Drop Python 3.8.
@@ -23,8 +39,8 @@
23
39
 
24
40
  ## 5.5
25
41
 
26
- - **NEW**: `\e` and `\h` have both been deprecated in 6.0. Please migrate to using `\x1b` and `\p{Horiz_Space}` in
27
- their respective place.
42
+ - **NEW**: `\e` and `\h` have both been deprecated. Please migrate to using `\x1b` and `\p{Horiz_Space}` in their
43
+ respective place.
28
44
  - **FIX**: Fix flag issue with `sub` functions.
29
45
 
30
46
  ## 5.4
@@ -199,12 +199,12 @@ Block properties have a similar short form as Script and Binary properties. For
199
199
  a block. If we wanted to match characters in the `Basic_Latin` block, we could use the syntax `\p{InBasic_Latin}`. This
200
200
  would be the same as `\p{Block: Basic_Latin}` or `\p{Basic_Latin}`.
201
201
 
202
- /// warn | Short Name Conflicts
202
+ /// warning | Short Name Conflicts
203
203
  When it comes to short names, each new Unicode version, there is a risk that new properties could cause conflicts
204
204
  with existing names and/or aliases. Currently, most of the conflicts involve the Block properties. To reduce
205
205
  friction, they are evaluated last.
206
206
 
207
- Generally, it is discouraged to use short names for Block properties. But the option is still supported, but Block
207
+ Generally, it is discouraged to use short names for Block properties. But the option is still supported, but Block
208
208
  properties will be evaluated last. There are currently no known conflicts with `In*` properties, but in future
209
209
  Unicode versions there could.
210
210
 
@@ -259,67 +259,18 @@ Verbose\ Property\ Form | Terse\ Property\ Form
259
259
  `Line_Separator` | `Zl`
260
260
  `Paragraph_Separator` | `Z`
261
261
 
262
- ### POSIX Style Properties
263
-
264
- /// new | New in 5.0
265
- 5.0 brings significant improvements and bug fixes to Unicode property handling. Properties are sensitive to the
266
- `ASCII` flag along with more extensive testing and bug fixes. Additionally, POSIX style properties are now just
267
- an extension of normal Unicode properties. All the POSIX names are available and now conform to
268
- the [Unicode specification for POSIX compatibility](https://unicode.org/reports/tr18/#Compatibility_Properties).
269
- Read on to learn more.
270
- ///
271
-
272
- Backrefs allows for POSIX style properties in the form `[:name:]`. These properties can only be used inside character
273
- classes and are another form for expressing Unicode properties. Any Unicode property that can be expressed via the
274
- `\p{name}` form can also be expressed in the `[:name:]` form. To illustrate, the following are all the same:
275
-
276
- - `[[:upper:]]` == `[\p{upper}]`
277
- - `[[:^upper:]]` == `[\p{^upper}]`
278
- - `[[:alpha=yes:]]` == `[\p{alpha=yes}]`
279
-
280
- A number of POSIX property names are available via compatibility properties as outlined in the
281
- [Unicode specification for POSIX compatibility](https://unicode.org/reports/tr18/#Compatibility_Properties). These
282
- properties will operate in the Unicode range and the ASCII range depending on the regular expression mode. These
283
- patterns, like all Unicode properties, are sensitive to the `ASCII` flag (or `LOCALE` which will treat them as `ASCII`).
284
-
285
- It is important to note that whether used in the form `[[:name:]]` or `\p{name}`, each POSIX name is available both with
286
- and without the `posix` prefix, but it is recommended to use the `posix` prefix to get the POSIX definition of the
287
- pattern as number of patterns have both a POSIX and Unicode definition that differ. The
288
- [Unicode specification for POSIX compatibility](https://unicode.org/reports/tr18/#Compatibility_Properties) outlines all
289
- the POSIX compatible properties and the ones which have dual definitions: `punct`, `alnum`, `digit`, and `xdigit` all
290
- have a Unicode standard and a POSIX compatibility variant and must be accessed with the `posix` prefix to get the POSIX
291
- form.
292
-
293
- In the table below, patterns with `--` mean `[[in this] -- [but not this]]`.
294
-
295
- \[:posix:] | \\p\{Posix} | ASCII | Unicode
296
- ---------- | ------------- | ------------------------------------------------- | -------
297
- `alpha` | `Alpha` | `[a-zA-Z]` | `\p{Alphabetic}`
298
- `alnum` | `PosixAlnum` | `[[:alpha:][:digit:]]` | `[[:alpha:][:digit:]]`
299
- `blank` | `Blank` | `[ \t]` | `[\p{Zs}\t]`
300
- `cntrl` | `Cntrl` | `[\x00-\x1F\x7F]` | `\p{Cc}`
301
- `digit` | `PosixDigit` | `[0-9]` | `[0-9]`
302
- `graph` | `Graph` | `[^ [:cntrl:]]` | `[^[:space:][:cntrl:]\p{Cn}\p{Cs}]`
303
- `lower` | `Lower` | `[a-z]` | `[\p{Lowercase}]`
304
- `print` | `Print` | `[[:graph:] ]` | `[[\p{P}\p{S}]--[\p{alpha}]]`
305
- `punct` | `PosixPunct` | ``[!\"\#$%&'()*+,\-./:;&lt;=&gt;?@\[\\\]^_`{}~]`` | `[[[:graph:][:blank:]]--[[:cntrl:]]]`
306
- `space` | `Space` | `[ \t\r\n\v\f]` | `[\p{Whitespace}]`
307
- `upper` | `Upper` | `[A-Z]` | `[\p{Uppercase}]`
308
- `xdigit` | `PosixXDigit` | `[A-Fa-f0-9]` | `[A-Fa-f0-9]`
309
-
310
262
  ### Compatibility Properties
311
263
 
312
264
  /// new | New in 5.0
313
265
  While many of the properties were available before 5.0, `word` is newly available. And all the properties now
314
- conform to the [Unicode specification for POSIX compatibility](https://unicode.org/reports/tr18/#Compatibility_Properties).
266
+ conform to the [Unicode specification for compatibility properties](https://unicode.org/reports/tr18/#Compatibility_Properties).
315
267
  ///
316
268
 
317
- [Unicode specification for POSIX compatibility][unicode-posix] defines a number of properties, many of which double as
318
- [Posix properties](#posix-style-properties). These properties can be accessed via `\p{name}` or `[[:name:]]`.
319
-
320
- In the table below, patterns with `--` mean `[[in this] -- [but not this]]`.
269
+ [Unicode specification for compatibility properties][unicode-posix] defines a number of properties for use in regular
270
+ expressions. The table below shows the names of these properties along with their standard rules as implemented in
271
+ Backrefs. Patterns with `--` mean `[[in this] -- [but not this]]`.
321
272
 
322
- \\p\{Posix} | Unicode
273
+ \\p\{name} | Unicode
323
274
  ------------- | -------
324
275
  `Alpha` | `\p{Alphabetic}`
325
276
  `Alnum` | `[\p{Alpha}\p{Digit}]`
@@ -328,9 +279,84 @@ In the table below, patterns with `--` mean `[[in this] -- [but not this]]`.
328
279
  `Digit` | `\p{Nd}`
329
280
  `Graph` | `[^\p{Space}\p{Cntrl}\p{Cn}\p{Cs}]`
330
281
  `Lower` | `\p{Lowercase}`
331
- `Print` | `[[\p{P}\p{S}]--[\p{Alpha}]]`
282
+ `Print` | `[\p{graph}\p{blank}--\p{cntrl}]`
332
283
  `Punct` | `\p{P}`
333
284
  `Space` | `\p{Whitespace}`
334
285
  `Upper` | `\p{Uppercase}`
335
286
  `Word` | `[\p{Alnum}\p{M}\p{Pc}\p{JoinControl}]`
336
287
  `XDigit` | `[\p{Nd}\p{HexDigit}]`
288
+
289
+ The [Unicode specification for compatibility properties](https://unicode.org/reports/tr18/#Compatibility_Properties)
290
+ also defines rules for POSIX compatibility which provides modifications to the standard recommendation required to meet
291
+ the formal requirements of POSIX, and also to maintain (as much as possible) compatibility with the POSIX usage in
292
+ practice.
293
+
294
+ To accommodate these POSIX compatible rules, Backrefs defines POSIX property names for all the compatibility properties.
295
+ For any that do not have special accommodations, the POSIX property name will act as an alias for the standard Unicode
296
+ name. In all, four properties are specifically affected with POSIX specific accommodations: `Alnum`, `Digit`, `Punct`,
297
+ and `XDigit`.
298
+
299
+ \\p\{name} | Unicode
300
+ ------------- | -------
301
+ `PosixAlpha` | `\p{Alpha}`
302
+ `PosixAlnum` | `[\p{Alpha}\p{PosixDigit}]`
303
+ `PosixBlank` | `\p{Blank}`
304
+ `PosixCntrl` | `\p{Cntrl}`
305
+ `PosixDigit` | `[0-9]`
306
+ `PosixGraph` | `\p{Graph}`
307
+ `PosixLower` | `\p{Lower}`
308
+ `PosixPrint` | `\p{Print}`
309
+ `PosixPunct` | `[[\p{P}\p{S}]--[\p{alpha}]]`
310
+ `PosixSpace` | `\p{Space}`
311
+ `PosixUpper` | `\p{Uppper}`
312
+ `PosixWord` | `\p{Word}`
313
+ `PosixXDigit` | `[A-Fa-f0-9]`
314
+
315
+ ### POSIX Character Classes
316
+
317
+ /// new | New in 6.0
318
+ POSIX character classes in the form of `[:name:]` are now forced to always use POSIX compatible rules if available.
319
+ These rules are defined in the [Unicode specification for compatibility properties]
320
+ (https://unicode.org/reports/tr18/#Compatibility_Properties).
321
+ ///
322
+
323
+ Backrefs allows for POSIX style character classes in the form of `[:name:]`. These properties can only be used inside
324
+ character classes, e.g. `[[:name:]]`.
325
+
326
+ When using POSIX character classes, all character classes will use the matching rules as stated in the
327
+ [Unicode specification for compatibility properties](https://unicode.org/reports/tr18/#Compatibility_Properties). If
328
+ there are specific rules for POSIX compatibility, those will be used in the bracket expression instead of the Unicode
329
+ standard form. For all others, the Unicode standard form will be used.
330
+
331
+ The table below outlines the supported POSIX names that can be used in the form `[:name:]` and details the differences
332
+ in rules if using ASCII mode or Unicode mode.
333
+
334
+ \[:posix:] | ASCII | Unicode
335
+ ---------- | ------------------------------------------------------------- | -------
336
+ `alpha` | `[a-zA-Z]` | `\p{Alphabetic}`
337
+ `alnum` | `[[:alpha:][:digit:]]` | `[[:alpha:][:digit:]]`
338
+ `blank` | `[ \t]` | `[\p{Zs}\t]`
339
+ `cntrl` | `[\x00-\x1F\x7F]` | `\p{Cc}`
340
+ `digit` | `[0-9]` | `[0-9]`
341
+ `graph` | `[^ [:cntrl:]]` | `[^[:space:][:cntrl:]\p{Cn}\p{Cs}]`
342
+ `lower` | `[a-z]` | `[\p{Lowercase}]`
343
+ `print` | `[[:graph:] ]` | `[[[:graph:][:blank:]]--[[:cntrl:]]]`
344
+ `punct` | ``[!"#$%&'()*+,\-./:;<=>?@[\]^_`{|}~]`` | `[[\p{P}\p{S}]--[\p{alpha}]]`
345
+ `space` | `[ \t\r\n\v\f]` | `[\p{Whitespace}]`
346
+ `upper` | `[A-Z]` | `[\p{Uppercase}]`
347
+ `word` | `[A-Za-z0-9_]` | `[\p{Alnum}\p{M}\p{Pc}\p{JoinControl}]`
348
+ `xdigit` | `[A-Fa-f0-9]` | `[A-Fa-f0-9]`
349
+
350
+ /// Tip
351
+ Using POSIX character classes is equivalent to using Unicode compatibility properties for POSIX, e.g. `[[:punct:]]`
352
+ == `\p{Posix Punct}`.
353
+ ///
354
+
355
+ POSIX style character classes have also been extended to allow for specifying normal Unicode properties as well. Any
356
+ Unicode property, outside the compatibility properties, will be accepted in the bracket extension form and behave just
357
+ like normal Unicode properties. To illustrate, the following are equivalent:
358
+
359
+ - `[[:math:]]` == `[\p{math}]`
360
+ - `[[:scx=latin:]]` == `[\p{scx: latin}]`
361
+ - `[[:^scx=latin:]]` == `[\P{scx: latin}]`
362
+
@@ -79,6 +79,7 @@ class CustomMetadataHook(MetadataHookInterface):
79
79
  'Programming Language :: Python :: 3.11',
80
80
  'Programming Language :: Python :: 3.12',
81
81
  'Programming Language :: Python :: 3.13',
82
+ 'Programming Language :: Python :: 3.14',
82
83
  'Topic :: Software Development :: Libraries :: Python Modules',
83
84
  'Typing :: Typed'
84
85
  ]
@@ -4,7 +4,7 @@ repo_url: https://github.com/facelessuser/backrefs
4
4
  edit_uri: tree/master/docs/src/markdown
5
5
  site_description: A library to add additional back references to regular expressions.
6
6
  copyright: |
7
- Copyright &copy; 2015 - 2023 <a href="https://github.com/facelessuser" target="_blank" rel="noopener">Isaac Muse</a>
7
+ Copyright &copy; 2015 - 2025 <a href="https://github.com/facelessuser" target="_blank" rel="noopener">Isaac Muse</a>
8
8
 
9
9
  docs_dir: docs/src/markdown
10
10
  theme:
@@ -152,8 +152,6 @@ extra:
152
152
  social:
153
153
  - icon: fontawesome/brands/github
154
154
  link: https://github.com/facelessuser
155
- - icon: fontawesome/brands/discord
156
- link: https://discord.gg/TWs8Tgr
157
155
 
158
156
  plugins:
159
157
  - search
@@ -135,7 +135,7 @@ legacy_tox_ini = """
135
135
  [tox]
136
136
  isolated_build = true
137
137
  envlist =
138
- py39,py310,py311,py312,py313,
138
+ py39,py310,py311,py312,py313,py314,
139
139
  lint
140
140
 
141
141
  [testenv]
@@ -157,7 +157,7 @@ passenv = *
157
157
  deps=
158
158
  -r requirements/docs.txt
159
159
  commands=
160
- {envpython} -m mkdocs build --clean --verbose --strict
160
+ {envbindir}/mkdocs build --clean --verbose --strict
161
161
  {envbindir}/pyspelling
162
162
 
163
163
  [testenv:lint]
@@ -11,6 +11,7 @@ import copy
11
11
 
12
12
  PY39_PLUS = (3, 9) <= sys.version_info
13
13
  PY311_PLUS = (3, 11) <= sys.version_info
14
+ PY313_PLUS = (3, 13) <= sys.version_info
14
15
 
15
16
  if PY311_PLUS:
16
17
  import re._constants as _constants
@@ -21,6 +22,20 @@ else:
21
22
  class TestSearchTemplate(unittest.TestCase):
22
23
  """Search template tests."""
23
24
 
25
+ def test_multi_disable_flag(self):
26
+ """Test when multiple flags are disabled."""
27
+
28
+ self.assertTrue(bre.match(r'(?-ix:Test Test)', 'Test Test', flags=bre.IGNORECASE | bre.VERBOSE))
29
+ self.assertTrue(bre.match(r'(?s-ix:Test Test)', 'Test Test', flags=bre.IGNORECASE | bre.VERBOSE))
30
+
31
+ def test_inline_unicode(self):
32
+ """Test inline Unicode/ASCII cases."""
33
+
34
+ self.assertTrue(bre.match(r'\p{N}', '\uff19', flags=bre.ASCII) is None)
35
+ self.assertTrue(bre.match(r'(?u:\p{N})', '\uff19', flags=bre.ASCII) is not None)
36
+ self.assertTrue(bre.match(r'\p{N}', '\uff19', flags=bre.UNICODE) is not None)
37
+ self.assertTrue(bre.match(r'(?a:\p{N})', '\uff19', flags=bre.UNICODE) is None)
38
+
24
39
  def test_custom_binary_properties(self):
25
40
  """Test new custom binary properties."""
26
41
 
@@ -1986,6 +2001,16 @@ class TestReplaceTemplate(unittest.TestCase):
1986
2001
  class TestExceptions(unittest.TestCase):
1987
2002
  """Test Exceptions."""
1988
2003
 
2004
+ def test_bad_flag(self):
2005
+ """Test bad flag."""
2006
+
2007
+ if PY313_PLUS:
2008
+ with self.assertRaises(re.PatternError):
2009
+ bre.compile(r'(?-i)')
2010
+ else:
2011
+ with self.assertRaises(re.error):
2012
+ bre.compile(r'(?-i)')
2013
+
1989
2014
  def test_format_existing_group_no_match_with_index(self):
1990
2015
  """Test format group with no match and attempt at indexing."""
1991
2016
 
@@ -20,6 +20,12 @@ PY39_PLUS = (3, 9) <= sys.version_info
20
20
  class TestSearchTemplate(unittest.TestCase):
21
21
  """Search template tests."""
22
22
 
23
+ def test_multi_disable_flag(self):
24
+ """Test when multiple flags are disabled."""
25
+
26
+ self.assertTrue(bregex.match(r'(?-ix:Test Test)', 'Test Test', flags=bregex.IGNORECASE | bregex.VERBOSE))
27
+ self.assertTrue(bregex.match(r'(?s-ix:Test Test)', 'Test Test', flags=bregex.IGNORECASE | bregex.VERBOSE))
28
+
23
29
  def test_named_lists_attribute(self):
24
30
  """Test named list attribute."""
25
31
 
@@ -1,7 +1,6 @@
1
1
  """Generate a Unicode prop table for Python builds."""
2
2
  import sys
3
3
  import unicodedata
4
- import codecs
5
4
  import os
6
5
  import re
7
6
 
@@ -201,7 +200,7 @@ def discover_categories():
201
200
 
202
201
  # NF Quick Check categories
203
202
  file_name = os.path.join(HOME, 'unicodedata', UNIVERSION, 'DerivedNormalizationProps.txt')
204
- with codecs.open(file_name, 'r', 'utf-8') as uf:
203
+ with open(file_name, 'r', encoding='utf-8') as uf:
205
204
  for line in uf:
206
205
  if not line.startswith('#'):
207
206
  data = line.split('#')[0].split(';')
@@ -227,7 +226,7 @@ def discover_categories():
227
226
  binary_props.append(('emoji-data.txt', None))
228
227
 
229
228
  for filename, include in binary_props:
230
- with codecs.open(os.path.join(HOME, 'unicodedata', UNIVERSION, filename), 'r', 'utf-8') as uf:
229
+ with open(os.path.join(HOME, 'unicodedata', UNIVERSION, filename), 'r', encoding='utf-8') as uf:
231
230
  for line in uf:
232
231
  if not line.startswith('#'):
233
232
  data = line.split('#')[0].split(';')
@@ -245,7 +244,7 @@ def discover_categories():
245
244
  def gen_blocks(output, ascii_props=False, append=False, prefix="", aliases=None):
246
245
  """Generate Unicode blocks."""
247
246
 
248
- with codecs.open(output, 'a' if append else 'w', 'utf-8') as f:
247
+ with open(output, 'a' if append else 'w', encoding='utf-8') as f:
249
248
  if not append:
250
249
  f.write(HEADER.format(UNIVERSION, TYPING))
251
250
  f.write('%s_blocks: dict[str, str] = {' % prefix)
@@ -257,7 +256,7 @@ def gen_blocks(output, ascii_props=False, append=False, prefix="", aliases=None)
257
256
  max_range = MAXUNICODE
258
257
  formatter = uniformat
259
258
 
260
- with codecs.open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'Blocks.txt'), 'r', 'utf-8') as uf:
259
+ with open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'Blocks.txt'), 'r', encoding='utf-8') as uf:
261
260
  for line in uf:
262
261
  if not line.startswith('#'):
263
262
  data = line.split(';')
@@ -321,7 +320,7 @@ def gen_ccc(output, ascii_props=False, append=False, prefix="", aliases=None):
321
320
  for v in aliases.get('canonicalcombiningclass', {}).values():
322
321
  obj[v] = []
323
322
 
324
- with codecs.open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'DerivedCombiningClass.txt'), 'r', 'utf-8') as uf:
323
+ with open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'DerivedCombiningClass.txt'), 'r', encoding='utf-8') as uf:
325
324
  for line in uf:
326
325
  if not line.startswith('#'):
327
326
  data = line.split('#')[0].split(';')
@@ -350,7 +349,7 @@ def gen_ccc(output, ascii_props=False, append=False, prefix="", aliases=None):
350
349
  # Convert characters values to ranges
351
350
  char2range(obj, is_bytes=ascii_props)
352
351
 
353
- with codecs.open(output, 'a' if append else 'w', 'utf-8') as f:
352
+ with open(output, 'a' if append else 'w', encoding='utf-8') as f:
354
353
  if not append:
355
354
  f.write(HEADER.format(UNIVERSION, TYPING))
356
355
  # Write out the Unicode properties
@@ -382,13 +381,13 @@ def gen_scripts(
382
381
  obj2[v] = []
383
382
 
384
383
  alias = {}
385
- with codecs.open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'PropertyValueAliases.txt'), 'r', 'utf-8') as uf:
384
+ with open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'PropertyValueAliases.txt'), 'r', encoding='utf-8') as uf:
386
385
  for line in uf:
387
386
  if line.startswith('sc ;'):
388
387
  values = line.split(';')
389
388
  alias[format_name(values[1].strip())] = format_name(values[2].strip())
390
389
 
391
- with codecs.open(os.path.join(HOME, 'unicodedata', UNIVERSION, file_name_ext), 'r', 'utf-8') as uf:
390
+ with open(os.path.join(HOME, 'unicodedata', UNIVERSION, file_name_ext), 'r', encoding='utf-8') as uf:
392
391
  for line in uf:
393
392
  if not line.startswith('#'):
394
393
  data = line.split('#')[0].split(';')
@@ -404,7 +403,7 @@ def gen_scripts(
404
403
 
405
404
  obj2[ext].extend(span)
406
405
 
407
- with codecs.open(os.path.join(HOME, 'unicodedata', UNIVERSION, file_name), 'r', 'utf-8') as uf:
406
+ with open(os.path.join(HOME, 'unicodedata', UNIVERSION, file_name), 'r', encoding='utf-8') as uf:
408
407
  for line in uf:
409
408
  if not line.startswith('#'):
410
409
  data = line.split('#')[0].split(';')
@@ -439,7 +438,7 @@ def gen_scripts(
439
438
  char2range(obj, is_bytes=ascii_props)
440
439
  char2range(obj2, is_bytes=ascii_props)
441
440
 
442
- with codecs.open(output, 'a' if append else 'w', 'utf-8') as f:
441
+ with open(output, 'a' if append else 'w', encoding='utf-8') as f:
443
442
  if not append:
444
443
  f.write(HEADER.format(UNIVERSION, TYPING))
445
444
  # Write out the Unicode properties
@@ -454,7 +453,7 @@ def gen_scripts(
454
453
  f.write(',\n')
455
454
  i += 1
456
455
 
457
- with codecs.open(output_ext, 'a' if append else 'w', 'utf-8') as f:
456
+ with open(output_ext, 'a' if append else 'w', encoding='utf-8') as f:
458
457
  if not append:
459
458
  f.write(HEADER.format(UNIVERSION, TYPING))
460
459
  # Write out the Unicode properties
@@ -482,7 +481,7 @@ def gen_enum(
482
481
  for v in aliases.get(format_name(obj_name), {}).values():
483
482
  obj[v] = []
484
483
 
485
- with codecs.open(os.path.join(HOME, 'unicodedata', UNIVERSION, file_name), 'r', 'utf-8') as uf:
484
+ with open(os.path.join(HOME, 'unicodedata', UNIVERSION, file_name), 'r', encoding='utf-8') as uf:
486
485
  for line in uf:
487
486
  if not line.startswith('#'):
488
487
  data = line.split('#')[0].split(';')
@@ -508,7 +507,7 @@ def gen_enum(
508
507
  # Convert characters values to ranges
509
508
  char2range(obj, is_bytes=ascii_props)
510
509
 
511
- with codecs.open(output, 'a' if append else 'w', 'utf-8') as f:
510
+ with open(output, 'a' if append else 'w', encoding='utf-8') as f:
512
511
  if not append:
513
512
  f.write(HEADER.format(UNIVERSION, TYPING))
514
513
  # Write out the Unicode properties
@@ -535,7 +534,7 @@ def gen_age(output, ascii_props=False, append=False, prefix="", aliases=None):
535
534
  obj[v] = []
536
535
 
537
536
  all_chars = (ALL_CHARS - ASCII_UNUSED) if ascii_props else ALL_CHARS
538
- with codecs.open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'DerivedAge.txt'), 'r', 'utf-8') as uf:
537
+ with open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'DerivedAge.txt'), 'r', encoding='utf-8') as uf:
539
538
  for line in uf:
540
539
  if not line.startswith('#'):
541
540
  data = line.split('#')[0].split(';')
@@ -564,7 +563,7 @@ def gen_age(output, ascii_props=False, append=False, prefix="", aliases=None):
564
563
  # Convert characters values to ranges
565
564
  char2range(obj, is_bytes=ascii_props)
566
565
 
567
- with codecs.open(output, 'a' if append else 'w', 'utf-8') as f:
566
+ with open(output, 'a' if append else 'w', encoding='utf-8') as f:
568
567
  if not append:
569
568
  f.write(HEADER.format(UNIVERSION, TYPING))
570
569
  # Write out the Unicode properties
@@ -586,7 +585,7 @@ def gen_nf_quick_check(output, ascii_props=False, append=False, prefix="", alias
586
585
  nf = {}
587
586
  all_chars = (ALL_CHARS - ASCII_UNUSED) if ascii_props else ALL_CHARS
588
587
  file_name = os.path.join(HOME, 'unicodedata', UNIVERSION, 'DerivedNormalizationProps.txt')
589
- with codecs.open(file_name, 'r', 'utf-8') as uf:
588
+ with open(file_name, 'r', encoding='utf-8') as uf:
590
589
  for line in uf:
591
590
  if not line.startswith('#'):
592
591
  data = line.split('#')[0].split(';')
@@ -626,7 +625,7 @@ def gen_nf_quick_check(output, ascii_props=False, append=False, prefix="", alias
626
625
  # Convert characters values to ranges
627
626
  char2range(nf, is_bytes=ascii_props)
628
627
 
629
- with codecs.open(output, 'a' if append else 'w', 'utf-8') as f:
628
+ with open(output, 'a' if append else 'w', encoding='utf-8') as f:
630
629
  if not append:
631
630
  f.write(HEADER.format(UNIVERSION, TYPING))
632
631
  for key, value in sorted(nf.items()):
@@ -670,7 +669,7 @@ def gen_binary(table, output, ascii_props=False, append=False, prefix="", aliase
670
669
  binary['vertspace'] = [x for x in binary['vertspace'] if x <= max_range]
671
670
 
672
671
  for filename, include in binary_props:
673
- with codecs.open(os.path.join(HOME, 'unicodedata', UNIVERSION, filename), 'r', 'utf-8') as uf:
672
+ with open(os.path.join(HOME, 'unicodedata', UNIVERSION, filename), 'r', encoding='utf-8') as uf:
674
673
  for line in uf:
675
674
  if not line.startswith('#'):
676
675
  data = line.split('#')[0].split(';')
@@ -687,7 +686,7 @@ def gen_binary(table, output, ascii_props=False, append=False, prefix="", aliase
687
686
  continue
688
687
  binary[name].extend(span)
689
688
 
690
- with codecs.open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'CompositionExclusions.txt'), 'r', 'utf-8') as uf:
689
+ with open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'CompositionExclusions.txt'), 'r', encoding='utf-8') as uf:
691
690
  name = 'compositionexclusion'
692
691
  for line in uf:
693
692
  if not line.startswith('#'):
@@ -703,7 +702,7 @@ def gen_binary(table, output, ascii_props=False, append=False, prefix="", aliase
703
702
  binary[name].extend(span)
704
703
 
705
704
  file_name = os.path.join(HOME, 'unicodedata', UNIVERSION, 'DerivedNormalizationProps.txt')
706
- with codecs.open(file_name, 'r', 'utf-8') as uf:
705
+ with open(file_name, 'r', encoding='utf-8') as uf:
707
706
  name = "fullcompositionexclusion"
708
707
  for line in uf:
709
708
  if not line.startswith('#'):
@@ -720,7 +719,7 @@ def gen_binary(table, output, ascii_props=False, append=False, prefix="", aliase
720
719
  binary[name] = []
721
720
  binary[name].extend(span)
722
721
 
723
- with codecs.open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'UnicodeData.txt'), 'r', 'utf-8') as uf:
722
+ with open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'UnicodeData.txt'), 'r', encoding='utf-8') as uf:
724
723
  name = 'bidimirrored'
725
724
  for line in uf:
726
725
  data = line.strip().split(';')
@@ -741,16 +740,6 @@ def gen_binary(table, output, ascii_props=False, append=False, prefix="", aliase
741
740
 
742
741
  gen_uposix(table, binary, ascii_props)
743
742
 
744
- # One off Unicode property found under https://unicode.org/reports/tr18/#Compatibility_Properties
745
- # `Word: [\p{alnum}\p{M}\p{Pc}\p{JoinControl}]`
746
- s = set(binary["alnum"])
747
- for k, v in table['m'].items():
748
- if not k.startswith('^'):
749
- s |= set(v)
750
- s |= set(table["p"]["c"])
751
- s |= set(binary["joincontrol"])
752
- binary["word"] = list(s)
753
-
754
743
  if aliases:
755
744
  for v in aliases.get('binary', {}).values():
756
745
  if v not in binary and '^' + v not in binary:
@@ -759,7 +748,7 @@ def gen_binary(table, output, ascii_props=False, append=False, prefix="", aliase
759
748
  # Convert characters values to ranges
760
749
  char2range(binary, is_bytes=ascii_props)
761
750
 
762
- with codecs.open(output, 'a' if append else 'w', 'utf-8') as f:
751
+ with open(output, 'a' if append else 'w', encoding='utf-8') as f:
763
752
  if not append:
764
753
  f.write(HEADER.format(UNIVERSION, TYPING))
765
754
  # Write out the Unicode properties
@@ -786,7 +775,7 @@ def gen_bidi(output, ascii_props=False, append=False, prefix="", aliases=None):
786
775
  bidi_class[v] = []
787
776
 
788
777
  max_range = MAXVALIDASCII if ascii_props else MAXUNICODE
789
- with codecs.open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'UnicodeData.txt'), 'r', 'utf-8') as uf:
778
+ with open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'UnicodeData.txt'), 'r', encoding='utf-8') as uf:
790
779
  for line in uf:
791
780
  data = line.strip().split(';')
792
781
  if data:
@@ -810,7 +799,7 @@ def gen_bidi(output, ascii_props=False, append=False, prefix="", aliases=None):
810
799
  # Convert characters values to ranges
811
800
  char2range(bidi_class, is_bytes=ascii_props)
812
801
 
813
- with codecs.open(output, 'a' if append else 'w', 'utf-8') as f:
802
+ with open(output, 'a' if append else 'w', encoding='utf-8') as f:
814
803
  if not append:
815
804
  f.write(HEADER.format(UNIVERSION, TYPING))
816
805
  f.write('%s_bidi_classes: dict[str, str] = {\n' % prefix)
@@ -882,9 +871,14 @@ def gen_uposix(table, posix_table, ascii_props):
882
871
  s -= set(posix_table["posixcntrl"])
883
872
  posix_table["posixprint"] = list(s)
884
873
 
885
- # `ASCII: [\x00-\x7F]`
886
- s = set(range(0, 0x7F + 1))
887
- posix_table["posixascii"] = list(s)
874
+ # `Word: [\p{alnum}\p{M}\p{Pc}\p{JoinControl}]`
875
+ s = set(posix_table["alnum"])
876
+ for k, v in table['m'].items():
877
+ if not k.startswith('^'):
878
+ s |= set(v)
879
+ s |= set(table["p"]["c"])
880
+ s |= set(posix_table["joincontrol"])
881
+ posix_table['posixword'] = list(s)
888
882
 
889
883
 
890
884
  def gen_alias(nonbinary, binary, output):
@@ -905,15 +899,17 @@ def gen_alias(nonbinary, binary, output):
905
899
  'posixlower': 'lowercase',
906
900
  'posixupper': 'uppercase',
907
901
  'posixspace': 'whitespace',
902
+ 'posixascii': 'basiclatin',
908
903
  'blank': 'posixblank',
909
904
  'graph': 'posixgraph',
910
- 'print': 'posixprint'
905
+ 'print': 'posixprint',
906
+ 'word': 'posixword'
911
907
  }
912
908
  toplevel = (
913
909
  'catalog', 'enumerated', 'numeric', 'miscellaneous'
914
910
  )
915
911
 
916
- with codecs.open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'PropertyAliases.txt'), 'r', 'utf-8') as uf:
912
+ with open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'PropertyAliases.txt'), 'r', encoding='utf-8') as uf:
917
913
  div = False
918
914
  capture = False
919
915
  name = None
@@ -952,7 +948,7 @@ def gen_alias(nonbinary, binary, output):
952
948
  for d in data[1:]:
953
949
  alias[name][d] = data[0]
954
950
 
955
- with codecs.open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'PropertyValueAliases.txt'), 'r', 'utf-8') as uf:
951
+ with open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'PropertyValueAliases.txt'), 'r', encoding='utf-8') as uf:
956
952
  for line in uf:
957
953
  m = alias_re.match(line)
958
954
  if m:
@@ -992,7 +988,7 @@ def gen_alias(nonbinary, binary, output):
992
988
  alias['binary']['h'] = 'horizspace'
993
989
  alias['binary']['v'] = 'vertspace'
994
990
 
995
- with codecs.open(output, 'w', 'utf-8') as f:
991
+ with open(output, 'w', encoding='utf-8') as f:
996
992
  f.write(HEADER.format(UNIVERSION, TYPING))
997
993
  f.write('%s_alias: dict[str, dict[str, str]] = {\n' % prefix)
998
994
  count = len(alias) - 1
@@ -1037,7 +1033,7 @@ def gen_properties(output, files, aliases, ascii_props=False, append=False):
1037
1033
  # before sending it through.
1038
1034
  table = {'l': {'c': []}}
1039
1035
  itable = {'l': {}}
1040
- with codecs.open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'UnicodeData.txt'), 'r', 'utf-8') as uf:
1036
+ with open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'UnicodeData.txt'), 'r', encoding='utf-8') as uf:
1041
1037
  for line in uf:
1042
1038
  data = line.strip().split(';')
1043
1039
  if data:
@@ -1194,7 +1190,7 @@ def gen_properties(output, files, aliases, ascii_props=False, append=False):
1194
1190
  for k1, v1 in itable.items():
1195
1191
  table[k1]['^'] = v1['^']
1196
1192
 
1197
- with codecs.open(files['gc'], 'a' if append else 'w', 'utf-8') as f:
1193
+ with open(files['gc'], 'a' if append else 'w', encoding='utf-8') as f:
1198
1194
  if not append:
1199
1195
  f.write(HEADER.format(UNIVERSION, TYPING))
1200
1196
  # Write out the Unicode properties
@@ -1219,7 +1215,7 @@ def gen_properties(output, files, aliases, ascii_props=False, append=False):
1219
1215
  i += 1
1220
1216
 
1221
1217
  if not append:
1222
- with codecs.open(os.path.join(output, '__init__.py'), 'w') as f:
1218
+ with open(os.path.join(output, '__init__.py'), 'w') as f:
1223
1219
  f.write(HEADER.format(UNIVERSION, ''))
1224
1220
  for x in sorted(files):
1225
1221
  f.write('from .%s import * # noqa\n' % os.path.basename(files[x])[:-3])
Binary file
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes