backrefs 8.0__tar.gz → 8.2__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.
- {backrefs-8.0 → backrefs-8.2}/.pyspelling.yml +2 -3
- {backrefs-8.0 → backrefs-8.2}/PKG-INFO +4 -4
- {backrefs-8.0 → backrefs-8.2}/backrefs/__meta__.py +1 -1
- {backrefs-8.0 → backrefs-8.2}/backrefs/_bre_parse.py +40 -37
- {backrefs-8.0 → backrefs-8.2}/backrefs/_bregex_parse.py +37 -34
- {backrefs-8.0 → backrefs-8.2}/backrefs/util.py +10 -0
- backrefs-8.2/docs/src/dictionary/en-custom.txt +67 -0
- backrefs-8.2/docs/src/dictionary/en-python.txt +15 -0
- {backrefs-8.0 → backrefs-8.2}/docs/src/markdown/about/changelog.md +22 -1
- {backrefs-8.0 → backrefs-8.2}/docs/src/markdown/about/contributing.md +42 -15
- {backrefs-8.0 → backrefs-8.2}/docs/src/markdown/refs.md +2 -2
- {backrefs-8.0 → backrefs-8.2}/docs/src/markdown/usage.md +55 -78
- {backrefs-8.0 → backrefs-8.2}/hatch_build.py +1 -1
- {backrefs-8.0 → backrefs-8.2}/pyproject.toml +69 -41
- {backrefs-8.0 → backrefs-8.2}/tests/test_bidiclass.py +7 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_bre.py +103 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_bregex.py +61 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_generalcategory.py +8 -0
- backrefs-8.2/tools/pycon.py +283 -0
- {backrefs-8.0 → backrefs-8.2}/tools/unipropgen.py +18 -2
- backrefs-8.2/zensical.yml +178 -0
- backrefs-8.0/docs/theme/assets/pymdownx-extras/extra-95634471d6.css +0 -2
- backrefs-8.0/docs/theme/assets/pymdownx-extras/extra-loader-Ccztcqfq.js +0 -4
- backrefs-8.0/docs/theme/main.html +0 -17
- backrefs-8.0/requirements/docs.txt +0 -4
- backrefs-8.0/requirements/lint.txt +0 -1
- backrefs-8.0/requirements/test.txt +0 -4
- {backrefs-8.0 → backrefs-8.2}/.coveragerc +0 -0
- {backrefs-8.0 → backrefs-8.2}/.gitignore +0 -0
- {backrefs-8.0 → backrefs-8.2}/LICENSE.md +0 -0
- {backrefs-8.0 → backrefs-8.2}/README.md +0 -0
- {backrefs-8.0 → backrefs-8.2}/backrefs/__init__.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/backrefs/_bregex_typing.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/backrefs/bre.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/backrefs/bregex.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/backrefs/py.typed +0 -0
- {backrefs-8.0 → backrefs-8.2}/backrefs/uniprops/__init__.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/docs/src/markdown/.snippets/links.md +0 -0
- {backrefs-8.0 → backrefs-8.2}/docs/src/markdown/about/license.md +0 -0
- {backrefs-8.0 → backrefs-8.2}/docs/src/markdown/about/security.md +0 -0
- {backrefs-8.0 → backrefs-8.2}/docs/src/markdown/index.md +0 -0
- {backrefs-8.0 → backrefs-8.2}/docs/theme/announce.html +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/__init__.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_age.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_bidipairedbrackettype.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_binary.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_block.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_canonicalcombiningclass.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_decompositiontype.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_eastasianwidth.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_graphemeclusterbreak.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_hangulsyllabletype.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_indicpositionalcategory.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_indicsylabiccategory.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_joininggroup.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_joiningtype.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_linebreak.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_numerictype.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_numericvalue.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_quickcheck.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_script.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_scriptextensions.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_sentencebreak.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_uniprops.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_versions.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_verticalorientation.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tests/test_wordbreak.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tools/__init__.py +0 -0
- {backrefs-8.0 → backrefs-8.2}/tools/unicodedata/13.0.0.zip +0 -0
- {backrefs-8.0 → backrefs-8.2}/tools/unicodedata/14.0.0.zip +0 -0
- {backrefs-8.0 → backrefs-8.2}/tools/unicodedata/15.0.0.zip +0 -0
- {backrefs-8.0 → backrefs-8.2}/tools/unicodedata/15.1.0.zip +0 -0
- {backrefs-8.0 → backrefs-8.2}/tools/unicodedata/16.0.0.zip +0 -0
- {backrefs-8.0 → backrefs-8.2}/tools/unicodedata/17.0.0.zip +0 -0
- {backrefs-8.0 → backrefs-8.2}/tools/unicodedata/LICENSE +0 -0
- {backrefs-8.0 → backrefs-8.2}/tools/unidatadownload.py +0 -0
|
@@ -18,11 +18,10 @@ matrix:
|
|
|
18
18
|
- alt
|
|
19
19
|
ignores:
|
|
20
20
|
- 'code, pre'
|
|
21
|
-
- 'a:is(.magiclink-compare, .magiclink-commit, .magiclink-repository)'
|
|
22
21
|
- 'span.keys'
|
|
23
22
|
- '.MathJax_Preview, .md-nav__link, .md-footer-custom-text, .md-source__repository, .headerlink, .md-icon'
|
|
24
|
-
- '.md-social__link, .magiclink
|
|
25
|
-
|
|
23
|
+
- '.md-social__link, .magiclink'
|
|
24
|
+
|
|
26
25
|
- pyspelling.filters.url:
|
|
27
26
|
|
|
28
27
|
- name: markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: backrefs
|
|
3
|
-
Version: 8.
|
|
3
|
+
Version: 8.2
|
|
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>
|
|
@@ -13,14 +13,14 @@ Classifier: Intended Audience :: Developers
|
|
|
13
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
14
14
|
Classifier: Operating System :: OS Independent
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
21
21
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
22
|
Classifier: Typing :: Typed
|
|
23
|
-
Requires-Python: >=3.
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
24
|
Provides-Extra: extras
|
|
25
25
|
Requires-Dist: regex; extra == 'extras'
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
@@ -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(8,
|
|
196
|
+
__version_info__ = Version(8, 2, 0, "final")
|
|
197
197
|
__version__ = __version_info__._get_canonical()
|
|
@@ -10,7 +10,6 @@ import sys
|
|
|
10
10
|
import copyreg as _copyreg
|
|
11
11
|
from . import util as _util
|
|
12
12
|
from .util import PatternError
|
|
13
|
-
import unicodedata as _unicodedata
|
|
14
13
|
from . import uniprops as _uniprops
|
|
15
14
|
from typing import Generic, AnyStr, Match, Any, Pattern, cast
|
|
16
15
|
|
|
@@ -37,7 +36,7 @@ _WORD = _LETTERS_UNDERSCORE | _DIGIT
|
|
|
37
36
|
_STANDARD_ESCAPES = frozenset(('a', 'b', 'f', 'n', 'r', 't', 'v'))
|
|
38
37
|
_CURLY_BRACKETS = frozenset(('{', '}'))
|
|
39
38
|
_PROPERTY_STRIP = frozenset((' ', '-', '_'))
|
|
40
|
-
_PROPERTY = _WORD | _DIGIT | _PROPERTY_STRIP
|
|
39
|
+
_PROPERTY = _WORD | _DIGIT | _PROPERTY_STRIP | frozenset(('.',))
|
|
41
40
|
_SCOPED_FLAGS = frozenset(('a', 'i', 'L', 'm', 'u', 's', 'x'))
|
|
42
41
|
_SCOPED_INV_FLAGS = frozenset(('i', 'm', 's', 'x'))
|
|
43
42
|
_SCOPED_END = frozenset((':', ')'))
|
|
@@ -176,7 +175,7 @@ class _SearchParser(Generic[AnyStr]):
|
|
|
176
175
|
value = []
|
|
177
176
|
try:
|
|
178
177
|
c = next(i)
|
|
179
|
-
if c.upper() in _ASCII_LETTERS:
|
|
178
|
+
if not brackets and c.upper() in _ASCII_LETTERS:
|
|
180
179
|
prop.append(c)
|
|
181
180
|
elif (not brackets and c != '{') or (brackets and c != ':'):
|
|
182
181
|
raise PatternError(f"Unicode property missing '{{' at {i.index - 1}!")
|
|
@@ -533,7 +532,7 @@ class _SearchParser(Generic[AnyStr]):
|
|
|
533
532
|
def unicode_name(self, name: str, in_group: bool = False) -> list[str]:
|
|
534
533
|
"""Insert Unicode value by its name."""
|
|
535
534
|
|
|
536
|
-
value = ord(
|
|
535
|
+
value = ord(_util.unicode_char(name))
|
|
537
536
|
if self.is_bytes and value > 0xFF:
|
|
538
537
|
if not in_group:
|
|
539
538
|
return ['(?!)']
|
|
@@ -755,7 +754,7 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
755
754
|
else:
|
|
756
755
|
width.append(fill)
|
|
757
756
|
fill = None
|
|
758
|
-
|
|
757
|
+
elif c != '}':
|
|
759
758
|
fill = c
|
|
760
759
|
c = self.format_next(i)
|
|
761
760
|
if fill == 's' and c == '}':
|
|
@@ -852,6 +851,18 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
852
851
|
|
|
853
852
|
return ''.join(value) if value else None
|
|
854
853
|
|
|
854
|
+
def append_chars(self, text: str, i: _util.StringIter) -> None:
|
|
855
|
+
"""Append each character of an escape value as a template escape."""
|
|
856
|
+
|
|
857
|
+
for c in text:
|
|
858
|
+
value = ord(c)
|
|
859
|
+
if self.use_format and value in _CURLY_BRACKETS_ORD:
|
|
860
|
+
self.handle_format(c, i)
|
|
861
|
+
elif value <= 0xFF:
|
|
862
|
+
self.result.append(f'\\{value:03o}')
|
|
863
|
+
else:
|
|
864
|
+
self.result.append(c)
|
|
865
|
+
|
|
855
866
|
def parse_octal(self, text: str, i: _util.StringIter) -> None:
|
|
856
867
|
"""Parse octal value."""
|
|
857
868
|
|
|
@@ -863,15 +874,12 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
863
874
|
single = self.get_single_stack()
|
|
864
875
|
if self.span_stack:
|
|
865
876
|
text = self.convert_case(chr(value), self.span_stack[-1])
|
|
866
|
-
|
|
877
|
+
text = self.convert_case(text, single) if single is not None else text
|
|
867
878
|
elif single:
|
|
868
|
-
|
|
869
|
-
if self.use_format and value in _CURLY_BRACKETS_ORD:
|
|
870
|
-
self.handle_format(chr(value), i)
|
|
871
|
-
elif value <= 0xFF:
|
|
872
|
-
self.result.append(f'\\{value:03o}')
|
|
879
|
+
text = self.convert_case(chr(value), single)
|
|
873
880
|
else:
|
|
874
|
-
|
|
881
|
+
text = chr(value)
|
|
882
|
+
self.append_chars(text, i)
|
|
875
883
|
|
|
876
884
|
def get_named_unicode(self, i: _util.StringIter) -> str:
|
|
877
885
|
"""Get named Unicode."""
|
|
@@ -893,19 +901,16 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
893
901
|
def parse_named_unicode(self, i: _util.StringIter) -> None:
|
|
894
902
|
"""Parse named Unicode."""
|
|
895
903
|
|
|
896
|
-
value = ord(
|
|
904
|
+
value = ord(_util.unicode_char(self.get_named_unicode(i)))
|
|
897
905
|
single = self.get_single_stack()
|
|
898
906
|
if self.span_stack:
|
|
899
907
|
text = self.convert_case(chr(value), self.span_stack[-1])
|
|
900
|
-
|
|
908
|
+
text = self.convert_case(text, single) if single is not None else text
|
|
901
909
|
elif single:
|
|
902
|
-
|
|
903
|
-
if self.use_format and value in _CURLY_BRACKETS_ORD:
|
|
904
|
-
self.handle_format(chr(value), i)
|
|
905
|
-
elif value <= 0xFF:
|
|
906
|
-
self.result.append(f'\\{value:03o}')
|
|
910
|
+
text = self.convert_case(chr(value), single)
|
|
907
911
|
else:
|
|
908
|
-
|
|
912
|
+
text = chr(value)
|
|
913
|
+
self.append_chars(text, i)
|
|
909
914
|
|
|
910
915
|
def get_wide_unicode(self, i: _util.StringIter) -> str:
|
|
911
916
|
"""Get narrow Unicode."""
|
|
@@ -952,15 +957,12 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
952
957
|
single = self.get_single_stack()
|
|
953
958
|
if self.span_stack:
|
|
954
959
|
text = self.convert_case(chr(value), self.span_stack[-1])
|
|
955
|
-
|
|
960
|
+
text = self.convert_case(text, single) if single is not None else text
|
|
956
961
|
elif single:
|
|
957
|
-
|
|
958
|
-
if self.use_format and value in _CURLY_BRACKETS_ORD:
|
|
959
|
-
self.handle_format(chr(value), i)
|
|
960
|
-
elif value <= 0xFF:
|
|
961
|
-
self.result.append(f'\\{value:03o}')
|
|
962
|
+
text = self.convert_case(chr(value), single)
|
|
962
963
|
else:
|
|
963
|
-
|
|
964
|
+
text = chr(value)
|
|
965
|
+
self.append_chars(text, i)
|
|
964
966
|
|
|
965
967
|
def get_byte(self, i: _util.StringIter) -> str:
|
|
966
968
|
"""Get byte."""
|
|
@@ -981,13 +983,12 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
981
983
|
single = self.get_single_stack()
|
|
982
984
|
if self.span_stack:
|
|
983
985
|
text = self.convert_case(chr(value), self.span_stack[-1])
|
|
984
|
-
|
|
986
|
+
text = self.convert_case(text, single) if single is not None else text
|
|
985
987
|
elif single:
|
|
986
|
-
|
|
987
|
-
if self.use_format and value in _CURLY_BRACKETS_ORD:
|
|
988
|
-
self.handle_format(chr(value), i)
|
|
988
|
+
text = self.convert_case(chr(value), single)
|
|
989
989
|
else:
|
|
990
|
-
|
|
990
|
+
text = chr(value)
|
|
991
|
+
self.append_chars(text, i)
|
|
991
992
|
|
|
992
993
|
def get_named_group(self, t: str, i: _util.StringIter) -> str:
|
|
993
994
|
"""Get group number."""
|
|
@@ -1004,16 +1005,18 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
1004
1005
|
value.append(c)
|
|
1005
1006
|
c = next(i)
|
|
1006
1007
|
while c != '>':
|
|
1007
|
-
if c in _DIGIT:
|
|
1008
|
-
|
|
1008
|
+
if c not in _DIGIT:
|
|
1009
|
+
raise PatternError(f"Bad character in group name at {i.index - 1}!")
|
|
1010
|
+
value.append(c)
|
|
1009
1011
|
c = next(i)
|
|
1010
1012
|
value.append(c)
|
|
1011
1013
|
elif c in _LETTERS_UNDERSCORE:
|
|
1012
1014
|
value.append(c)
|
|
1013
1015
|
c = next(i)
|
|
1014
1016
|
while c != '>':
|
|
1015
|
-
if c in _WORD:
|
|
1016
|
-
|
|
1017
|
+
if c not in _WORD:
|
|
1018
|
+
raise PatternError(f"Bad character in group name at {i.index - 1}!")
|
|
1019
|
+
value.append(c)
|
|
1017
1020
|
c = next(i)
|
|
1018
1021
|
value.append(c)
|
|
1019
1022
|
else:
|
|
@@ -1062,7 +1065,7 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
1062
1065
|
elif not self.is_bytes and t == "u":
|
|
1063
1066
|
value = chr(int(self.get_narrow_unicode(i), 16))
|
|
1064
1067
|
elif not self.is_bytes and t == "N":
|
|
1065
|
-
value =
|
|
1068
|
+
value = _util.unicode_char(self.get_named_unicode(i))
|
|
1066
1069
|
elif t == "x":
|
|
1067
1070
|
value = chr(int(self.get_byte(i), 16))
|
|
1068
1071
|
else:
|
|
@@ -5,7 +5,6 @@ Licensed under MIT
|
|
|
5
5
|
Copyright (c) 2011 - 2020 Isaac Muse <isaacmuse@gmail.com>
|
|
6
6
|
"""
|
|
7
7
|
from __future__ import annotations
|
|
8
|
-
import unicodedata as _unicodedata
|
|
9
8
|
import copyreg as _copyreg
|
|
10
9
|
from . import util as _util
|
|
11
10
|
from .util import PatternError
|
|
@@ -599,7 +598,7 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
599
598
|
else:
|
|
600
599
|
width.append(fill)
|
|
601
600
|
fill = None
|
|
602
|
-
|
|
601
|
+
elif c != '}':
|
|
603
602
|
fill = c
|
|
604
603
|
c = self.format_next(i)
|
|
605
604
|
if fill == 's' and c == '}':
|
|
@@ -696,6 +695,18 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
696
695
|
|
|
697
696
|
return ''.join(value) if value else None
|
|
698
697
|
|
|
698
|
+
def append_chars(self, text: str, i: _util.StringIter) -> None:
|
|
699
|
+
"""Append each character of an escape value as a template escape."""
|
|
700
|
+
|
|
701
|
+
for c in text:
|
|
702
|
+
value = ord(c)
|
|
703
|
+
if self.use_format and value in _CURLY_BRACKETS_ORD:
|
|
704
|
+
self.handle_format(c, i)
|
|
705
|
+
elif value <= 0xFF:
|
|
706
|
+
self.result.append(f'\\{value:03o}')
|
|
707
|
+
else:
|
|
708
|
+
self.result.append(c)
|
|
709
|
+
|
|
699
710
|
def parse_octal(self, text: str, i: _util.StringIter) -> None:
|
|
700
711
|
"""Parse octal value."""
|
|
701
712
|
|
|
@@ -707,15 +718,12 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
707
718
|
single = self.get_single_stack()
|
|
708
719
|
if self.span_stack:
|
|
709
720
|
text = self.convert_case(chr(value), self.span_stack[-1])
|
|
710
|
-
|
|
721
|
+
text = self.convert_case(text, single) if single is not None else text
|
|
711
722
|
elif single:
|
|
712
|
-
|
|
713
|
-
if self.use_format and value in _CURLY_BRACKETS_ORD:
|
|
714
|
-
self.handle_format(chr(value), i)
|
|
715
|
-
elif value <= 0xFF:
|
|
716
|
-
self.result.append(f'\\{value:03o}')
|
|
723
|
+
text = self.convert_case(chr(value), single)
|
|
717
724
|
else:
|
|
718
|
-
|
|
725
|
+
text = chr(value)
|
|
726
|
+
self.append_chars(text, i)
|
|
719
727
|
|
|
720
728
|
def get_named_unicode(self, i: _util.StringIter) -> str:
|
|
721
729
|
"""Get named Unicode."""
|
|
@@ -737,19 +745,16 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
737
745
|
def parse_named_unicode(self, i: _util.StringIter) -> None:
|
|
738
746
|
"""Parse named Unicode."""
|
|
739
747
|
|
|
740
|
-
value = ord(
|
|
748
|
+
value = ord(_util.unicode_char(self.get_named_unicode(i)))
|
|
741
749
|
single = self.get_single_stack()
|
|
742
750
|
if self.span_stack:
|
|
743
751
|
text = self.convert_case(chr(value), self.span_stack[-1])
|
|
744
|
-
|
|
752
|
+
text = self.convert_case(text, single) if single is not None else text
|
|
745
753
|
elif single:
|
|
746
|
-
|
|
747
|
-
if self.use_format and value in _CURLY_BRACKETS_ORD:
|
|
748
|
-
self.handle_format(chr(value), i)
|
|
749
|
-
elif value <= 0xFF:
|
|
750
|
-
self.result.append(f'\\{value:03o}')
|
|
754
|
+
text = self.convert_case(chr(value), single)
|
|
751
755
|
else:
|
|
752
|
-
|
|
756
|
+
text = chr(value)
|
|
757
|
+
self.append_chars(text, i)
|
|
753
758
|
|
|
754
759
|
def get_wide_unicode(self, i: _util.StringIter) -> str:
|
|
755
760
|
"""Get narrow Unicode."""
|
|
@@ -796,15 +801,12 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
796
801
|
single = self.get_single_stack()
|
|
797
802
|
if self.span_stack:
|
|
798
803
|
text = self.convert_case(chr(value), self.span_stack[-1])
|
|
799
|
-
|
|
804
|
+
text = self.convert_case(text, single) if single is not None else text
|
|
800
805
|
elif single:
|
|
801
|
-
|
|
802
|
-
if self.use_format and value in _CURLY_BRACKETS_ORD:
|
|
803
|
-
self.handle_format(chr(value), i)
|
|
804
|
-
elif value <= 0xFF:
|
|
805
|
-
self.result.append(f'\\{value:03o}')
|
|
806
|
+
text = self.convert_case(chr(value), single)
|
|
806
807
|
else:
|
|
807
|
-
|
|
808
|
+
text = chr(value)
|
|
809
|
+
self.append_chars(text, i)
|
|
808
810
|
|
|
809
811
|
def get_byte(self, i: _util.StringIter) -> str:
|
|
810
812
|
"""Get byte."""
|
|
@@ -825,13 +827,12 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
825
827
|
single = self.get_single_stack()
|
|
826
828
|
if self.span_stack:
|
|
827
829
|
text = self.convert_case(chr(value), self.span_stack[-1])
|
|
828
|
-
|
|
830
|
+
text = self.convert_case(text, single) if single is not None else text
|
|
829
831
|
elif single:
|
|
830
|
-
|
|
831
|
-
if self.use_format and value in _CURLY_BRACKETS_ORD:
|
|
832
|
-
self.handle_format(chr(value), i)
|
|
832
|
+
text = self.convert_case(chr(value), single)
|
|
833
833
|
else:
|
|
834
|
-
|
|
834
|
+
text = chr(value)
|
|
835
|
+
self.append_chars(text, i)
|
|
835
836
|
|
|
836
837
|
def get_named_group(self, t: str, i: _util.StringIter) -> str:
|
|
837
838
|
"""Get group number."""
|
|
@@ -848,16 +849,18 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
848
849
|
value.append(c)
|
|
849
850
|
c = next(i)
|
|
850
851
|
while c != '>':
|
|
851
|
-
if c in _DIGIT:
|
|
852
|
-
|
|
852
|
+
if c not in _DIGIT:
|
|
853
|
+
raise PatternError(f"Bad character in group name at {i.index - 1}!")
|
|
854
|
+
value.append(c)
|
|
853
855
|
c = next(i)
|
|
854
856
|
value.append(c)
|
|
855
857
|
elif c in _LETTERS_UNDERSCORE:
|
|
856
858
|
value.append(c)
|
|
857
859
|
c = next(i)
|
|
858
860
|
while c != '>':
|
|
859
|
-
if c in _WORD:
|
|
860
|
-
|
|
861
|
+
if c not in _WORD:
|
|
862
|
+
raise PatternError(f"Bad character in group name at {i.index - 1}!")
|
|
863
|
+
value.append(c)
|
|
861
864
|
c = next(i)
|
|
862
865
|
value.append(c)
|
|
863
866
|
else:
|
|
@@ -906,7 +909,7 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
906
909
|
elif not self.is_bytes and t == "u":
|
|
907
910
|
value = chr(int(self.get_narrow_unicode(i), 16))
|
|
908
911
|
elif not self.is_bytes and t == "N":
|
|
909
|
-
value =
|
|
912
|
+
value = _util.unicode_char(self.get_named_unicode(i))
|
|
910
913
|
elif t == "x":
|
|
911
914
|
value = chr(int(self.get_byte(i), 16))
|
|
912
915
|
else:
|
|
@@ -7,6 +7,7 @@ Copyright (c) 2015 - 2020 Isaac Muse <isaacmuse@gmail.com>
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
import warnings
|
|
9
9
|
import sys
|
|
10
|
+
import unicodedata as _unicodedata
|
|
10
11
|
from typing import Any, Callable, AnyStr
|
|
11
12
|
|
|
12
13
|
PY311 = (3, 11) <= sys.version_info
|
|
@@ -23,6 +24,15 @@ class PatternError(Exception):
|
|
|
23
24
|
"""Pattern error."""
|
|
24
25
|
|
|
25
26
|
|
|
27
|
+
def unicode_char(name: str) -> str:
|
|
28
|
+
"""Unicode character by its official name, raising our pattern error when no character has it."""
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
return _unicodedata.lookup(name)
|
|
32
|
+
except KeyError as e:
|
|
33
|
+
raise PatternError(f"Undefined Unicode character name {name!r}!") from e
|
|
34
|
+
|
|
35
|
+
|
|
26
36
|
class StringIter:
|
|
27
37
|
"""Preprocess replace tokens."""
|
|
28
38
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
API
|
|
2
|
+
Accessors
|
|
3
|
+
Backport
|
|
4
|
+
Backrefs
|
|
5
|
+
Bre
|
|
6
|
+
Bregex
|
|
7
|
+
CVE
|
|
8
|
+
Changelog
|
|
9
|
+
EmojiOne
|
|
10
|
+
Feb
|
|
11
|
+
Formatter
|
|
12
|
+
GitHub
|
|
13
|
+
Gitter
|
|
14
|
+
Jan
|
|
15
|
+
Jun
|
|
16
|
+
MERCHANTABILITY
|
|
17
|
+
Mar
|
|
18
|
+
MkDocs
|
|
19
|
+
NONINFRINGEMENT
|
|
20
|
+
Posix
|
|
21
|
+
PyMdown
|
|
22
|
+
PyPI
|
|
23
|
+
PyPI's
|
|
24
|
+
Regex
|
|
25
|
+
Regex's
|
|
26
|
+
Sep
|
|
27
|
+
Tidelift
|
|
28
|
+
Tox
|
|
29
|
+
Twemoji
|
|
30
|
+
Zensical
|
|
31
|
+
accessor
|
|
32
|
+
backend
|
|
33
|
+
boolean
|
|
34
|
+
decrement
|
|
35
|
+
deprecations
|
|
36
|
+
emoji
|
|
37
|
+
enablement
|
|
38
|
+
formatter
|
|
39
|
+
grapheme
|
|
40
|
+
hashable
|
|
41
|
+
incrementing
|
|
42
|
+
namespaced
|
|
43
|
+
natively
|
|
44
|
+
parsers
|
|
45
|
+
plugin
|
|
46
|
+
posix
|
|
47
|
+
postfix
|
|
48
|
+
pre
|
|
49
|
+
preprocess
|
|
50
|
+
preprocesses
|
|
51
|
+
preprocessing
|
|
52
|
+
preprocessor
|
|
53
|
+
prerelease
|
|
54
|
+
prereleases
|
|
55
|
+
refactored
|
|
56
|
+
reinstalling
|
|
57
|
+
reparsing
|
|
58
|
+
requesters
|
|
59
|
+
sublicense
|
|
60
|
+
subpattern
|
|
61
|
+
subpatterns
|
|
62
|
+
tuple
|
|
63
|
+
uncompiled
|
|
64
|
+
unescaped
|
|
65
|
+
unterminated
|
|
66
|
+
validator
|
|
67
|
+
whitespace
|
|
@@ -3,6 +3,27 @@ icon: lucide/scroll-text
|
|
|
3
3
|
---
|
|
4
4
|
# Changelog
|
|
5
5
|
|
|
6
|
+
## 8.2
|
|
7
|
+
|
|
8
|
+
- **NEW**: Drop Python 3.10.
|
|
9
|
+
- **FIX**: Fix error in replace templates when casing a character escape gives two characters (`\C\xdf\E`).
|
|
10
|
+
- **FIX**: Fix wrong text or errors when case conversion of a `\x` escape exceeds the byte range in replace templates.
|
|
11
|
+
- **FIX**: Fix an empty format specification (`{1:}`) raising an error in format replace templates.
|
|
12
|
+
- **FIX**: Fix Unicode properties, such as general category `\p{L}` and `\p{Bidi_Class=L}`, not matching ideographs,
|
|
13
|
+
Hangul syllables, and other characters Unicode defines as ranges.
|
|
14
|
+
- **FIX**: Fix `\g<...>` references in replace templates silently dropping invalid characters in a group name
|
|
15
|
+
instead of raising an error.
|
|
16
|
+
- **FIX**: Fix `\p{...}` Unicode properties rejecting the canonical dotted value form (e.g. `Age=1.1`),
|
|
17
|
+
which previously only worked through the alias form (e.g. `V1_1`).
|
|
18
|
+
- **FIX**: Fix issue where Unicode properties within character classes (e.g. `[x[:property:]]`) were incorrectly
|
|
19
|
+
detected with `[x[l]]` (@rahul05ranjan).
|
|
20
|
+
- **FIX**: Fix an undefined Unicode name in `\N{...}` raising a `KeyError` instead of `PatternError`
|
|
21
|
+
(@rahul05ranjan).
|
|
22
|
+
|
|
23
|
+
## 8.1
|
|
24
|
+
|
|
25
|
+
- **NEW**: Add official support for Python 3.15.
|
|
26
|
+
|
|
6
27
|
## 8.0
|
|
7
28
|
|
|
8
29
|
- **NEW**: Backrefs will preemptively fail, raising a `PatternError`, if parsing and finding an unterminated character
|
|
@@ -132,7 +153,7 @@ icon: lucide/scroll-text
|
|
|
132
153
|
to improve result.
|
|
133
154
|
- **NEW**: POSIX style properties now handle all existing Unicode properties.
|
|
134
155
|
- **NEW**: POSIX properties now follow the [Unicode specification for POSIX compatibility][unicode-posix].
|
|
135
|
-
Read the [documentation](../refs.md#posix-
|
|
156
|
+
Read the [documentation](../refs.md#posix-character-classes) to learn more.
|
|
136
157
|
- **NEW**: Unicode properties are now sensitive to the `ASCII` flag and will properly restrict the range of properties
|
|
137
158
|
to the ASCII range even in Unicode strings.
|
|
138
159
|
- **NEW**: Removed the old deprecated search references: `\l`, `\L`, `\c`, and `\C`. These are available in various
|
|
@@ -45,6 +45,12 @@ any tier you feel comfortable with. No amount is too little. We also accept one
|
|
|
45
45
|
Take part in reviewing pull requests and/or reviewing direct commits. Make suggestions to improve the code and discuss
|
|
46
46
|
solutions to overcome weakness in the algorithm.
|
|
47
47
|
|
|
48
|
+
## Answer Questions in Issues
|
|
49
|
+
|
|
50
|
+
Take time and answer questions and offer suggestions to people who've created issues in the issue tracker. Often people
|
|
51
|
+
will have questions that you might have an answer for. Or maybe you know how to help them accomplish a specific task
|
|
52
|
+
they are asking about. Feel free to share your experience to help others.
|
|
53
|
+
|
|
48
54
|
## Pull Requests
|
|
49
55
|
|
|
50
56
|
Pull requests are welcome, and if you plan on contributing directly to the code, there are a couple of things to be
|
|
@@ -55,23 +61,28 @@ tests will automatically be run, and the request must pass to be accepted. You
|
|
|
55
61
|
pull requesting. If it is not possible to run these tests locally, they will be run when the pull request is made, but
|
|
56
62
|
it is strongly suggested that requesters make an effort to verify before requesting to allow for a quick, smooth merge.
|
|
57
63
|
|
|
58
|
-
Feel free to use a virtual environment if you are concerned about installing any of the Python packages.
|
|
59
|
-
|
|
60
64
|
### Running Validation Tests
|
|
61
65
|
|
|
62
|
-
|
|
66
|
+
In order to preserve good code health, a test suite has been put together with `pytest` (@pytest-dev/pytest). There are
|
|
67
|
+
currently two kinds of tests: syntax and targeted. To run these tests, you can use the following command:
|
|
63
68
|
|
|
64
|
-
|
|
65
|
-
pip install tox
|
|
66
|
-
```
|
|
69
|
+
If you wish to run the tests locally, just run:
|
|
67
70
|
|
|
68
|
-
|
|
71
|
+
```console
|
|
72
|
+
$ hatch run +py=3.14 dev:tests
|
|
73
|
+
```
|
|
69
74
|
|
|
70
|
-
|
|
71
|
-
tox
|
|
72
|
-
```
|
|
75
|
+
Coding standards are enforced using @astral-sh/ruff. The environment can be setup and run as shown below.
|
|
73
76
|
|
|
74
|
-
|
|
77
|
+
```console
|
|
78
|
+
$ hatch run +py=3.14 dev:lint
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
We use @python/mypy to enforce typing. It can be run as shown below.
|
|
82
|
+
|
|
83
|
+
```console
|
|
84
|
+
$ hatch run +py=3.14 dev:mypy
|
|
85
|
+
```
|
|
75
86
|
|
|
76
87
|
## Documentation Improvements
|
|
77
88
|
|
|
@@ -80,7 +91,23 @@ you feel it is still lacking, show your appreciation for the plugin by helping t
|
|
|
80
91
|
documentation is always appreciated and can be done via pull requests. There shouldn't be any need to run validation
|
|
81
92
|
tests if only updating documentation.
|
|
82
93
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
94
|
+
Documents are in Markdown (with some additional syntax) and converted to HTML via Python Markdown and this extension
|
|
95
|
+
bundle. The documentation site is built with @zensical/zensical.
|
|
96
|
+
|
|
97
|
+
To build docs:
|
|
98
|
+
|
|
99
|
+
```console
|
|
100
|
+
$ hatch run docs:build
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
To serve docs and to live preview in a browser:
|
|
104
|
+
|
|
105
|
+
```console
|
|
106
|
+
$ hatch run docs:serve
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
To clean the documents:
|
|
110
|
+
|
|
111
|
+
```console
|
|
112
|
+
$ hatch run docs:clean
|
|
113
|
+
```
|
|
@@ -23,7 +23,7 @@ Back\ References | Description
|
|
|
23
23
|
`\pX` | Unicode property character class where `X` is the uppercase letter that represents the General Category property. For instance, `\pL` would be equivalent to `\p{L}` or `\p{Letter}`.
|
|
24
24
|
`\P{UnicodeProperty}` | Inverse Unicode property character class. Can be used in character classes `[]`. See [Unicode Properties](#unicode-properties) for more info.
|
|
25
25
|
`\PX` | Inverse Unicode property character class where `X` is the uppercase letter that represents the General Category property. For instance, `\PL` would be equivalent to `\P{L}` or `\P{Letter}`.
|
|
26
|
-
`[[:alnum:]]` | Though not really a back reference, support for POSIX style character classes is available. See [POSIX Style Properties](#posix-
|
|
26
|
+
`[[:alnum:]]` | Though not really a back reference, support for POSIX style character classes is available. See [POSIX Style Properties](#posix-character-classes) for more info.
|
|
27
27
|
`\N{UnicodeName}` | Named characters are normally ignored in Re, but Backrefs adds support for them.
|
|
28
28
|
`\m` | Start word boundary. Translates to `\b(?=\w)`.
|
|
29
29
|
`\M` | End word boundary. Translates to `\b(?<=\w)`.
|
|
@@ -92,7 +92,7 @@ implementation of Unicode properties. Some properties may not be available on ce
|
|
|
92
92
|
included Unicode build.
|
|
93
93
|
|
|
94
94
|
It is important to note that Backrefs handles Unicode properties by transforming them to character classes with all the
|
|
95
|
-
associated characters: `\p{Cs}` --> `[\ud800
|
|
95
|
+
associated characters: `\p{Cs}` --> `[\ud800-\udfff]`. Because of this, Backrefs can create
|
|
96
96
|
really large regular expressions that the underlying engine must walk through. In short, Re with Backrefs will never be
|
|
97
97
|
as efficient or fast as using Regex's Unicode properties, but it is very useful when you need or want to use Re.
|
|
98
98
|
|