backrefs 8.1__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.
Files changed (70) hide show
  1. {backrefs-8.1 → backrefs-8.2}/.pyspelling.yml +2 -3
  2. {backrefs-8.1 → backrefs-8.2}/PKG-INFO +2 -3
  3. {backrefs-8.1 → backrefs-8.2}/backrefs/__meta__.py +1 -1
  4. {backrefs-8.1 → backrefs-8.2}/backrefs/_bre_parse.py +40 -37
  5. {backrefs-8.1 → backrefs-8.2}/backrefs/_bregex_parse.py +37 -34
  6. {backrefs-8.1 → backrefs-8.2}/backrefs/util.py +10 -0
  7. backrefs-8.2/docs/src/dictionary/en-custom.txt +67 -0
  8. backrefs-8.2/docs/src/dictionary/en-python.txt +15 -0
  9. {backrefs-8.1 → backrefs-8.2}/docs/src/markdown/about/changelog.md +17 -0
  10. {backrefs-8.1 → backrefs-8.2}/docs/src/markdown/refs.md +1 -1
  11. {backrefs-8.1 → backrefs-8.2}/hatch_build.py +0 -1
  12. {backrefs-8.1 → backrefs-8.2}/pyproject.toml +6 -7
  13. {backrefs-8.1 → backrefs-8.2}/tests/test_bidiclass.py +7 -0
  14. {backrefs-8.1 → backrefs-8.2}/tests/test_bre.py +103 -0
  15. {backrefs-8.1 → backrefs-8.2}/tests/test_bregex.py +61 -0
  16. {backrefs-8.1 → backrefs-8.2}/tests/test_generalcategory.py +8 -0
  17. {backrefs-8.1 → backrefs-8.2}/tools/unipropgen.py +18 -2
  18. backrefs-8.2/zensical.yml +178 -0
  19. {backrefs-8.1 → backrefs-8.2}/.coveragerc +0 -0
  20. {backrefs-8.1 → backrefs-8.2}/.gitignore +0 -0
  21. {backrefs-8.1 → backrefs-8.2}/LICENSE.md +0 -0
  22. {backrefs-8.1 → backrefs-8.2}/README.md +0 -0
  23. {backrefs-8.1 → backrefs-8.2}/backrefs/__init__.py +0 -0
  24. {backrefs-8.1 → backrefs-8.2}/backrefs/_bregex_typing.py +0 -0
  25. {backrefs-8.1 → backrefs-8.2}/backrefs/bre.py +0 -0
  26. {backrefs-8.1 → backrefs-8.2}/backrefs/bregex.py +0 -0
  27. {backrefs-8.1 → backrefs-8.2}/backrefs/py.typed +0 -0
  28. {backrefs-8.1 → backrefs-8.2}/backrefs/uniprops/__init__.py +0 -0
  29. {backrefs-8.1 → backrefs-8.2}/docs/src/markdown/.snippets/links.md +0 -0
  30. {backrefs-8.1 → backrefs-8.2}/docs/src/markdown/about/contributing.md +0 -0
  31. {backrefs-8.1 → backrefs-8.2}/docs/src/markdown/about/license.md +0 -0
  32. {backrefs-8.1 → backrefs-8.2}/docs/src/markdown/about/security.md +0 -0
  33. {backrefs-8.1 → backrefs-8.2}/docs/src/markdown/index.md +0 -0
  34. {backrefs-8.1 → backrefs-8.2}/docs/src/markdown/usage.md +0 -0
  35. {backrefs-8.1 → backrefs-8.2}/docs/theme/announce.html +0 -0
  36. {backrefs-8.1 → backrefs-8.2}/tests/__init__.py +0 -0
  37. {backrefs-8.1 → backrefs-8.2}/tests/test_age.py +0 -0
  38. {backrefs-8.1 → backrefs-8.2}/tests/test_bidipairedbrackettype.py +0 -0
  39. {backrefs-8.1 → backrefs-8.2}/tests/test_binary.py +0 -0
  40. {backrefs-8.1 → backrefs-8.2}/tests/test_block.py +0 -0
  41. {backrefs-8.1 → backrefs-8.2}/tests/test_canonicalcombiningclass.py +0 -0
  42. {backrefs-8.1 → backrefs-8.2}/tests/test_decompositiontype.py +0 -0
  43. {backrefs-8.1 → backrefs-8.2}/tests/test_eastasianwidth.py +0 -0
  44. {backrefs-8.1 → backrefs-8.2}/tests/test_graphemeclusterbreak.py +0 -0
  45. {backrefs-8.1 → backrefs-8.2}/tests/test_hangulsyllabletype.py +0 -0
  46. {backrefs-8.1 → backrefs-8.2}/tests/test_indicpositionalcategory.py +0 -0
  47. {backrefs-8.1 → backrefs-8.2}/tests/test_indicsylabiccategory.py +0 -0
  48. {backrefs-8.1 → backrefs-8.2}/tests/test_joininggroup.py +0 -0
  49. {backrefs-8.1 → backrefs-8.2}/tests/test_joiningtype.py +0 -0
  50. {backrefs-8.1 → backrefs-8.2}/tests/test_linebreak.py +0 -0
  51. {backrefs-8.1 → backrefs-8.2}/tests/test_numerictype.py +0 -0
  52. {backrefs-8.1 → backrefs-8.2}/tests/test_numericvalue.py +0 -0
  53. {backrefs-8.1 → backrefs-8.2}/tests/test_quickcheck.py +0 -0
  54. {backrefs-8.1 → backrefs-8.2}/tests/test_script.py +0 -0
  55. {backrefs-8.1 → backrefs-8.2}/tests/test_scriptextensions.py +0 -0
  56. {backrefs-8.1 → backrefs-8.2}/tests/test_sentencebreak.py +0 -0
  57. {backrefs-8.1 → backrefs-8.2}/tests/test_uniprops.py +0 -0
  58. {backrefs-8.1 → backrefs-8.2}/tests/test_versions.py +0 -0
  59. {backrefs-8.1 → backrefs-8.2}/tests/test_verticalorientation.py +0 -0
  60. {backrefs-8.1 → backrefs-8.2}/tests/test_wordbreak.py +0 -0
  61. {backrefs-8.1 → backrefs-8.2}/tools/__init__.py +0 -0
  62. {backrefs-8.1 → backrefs-8.2}/tools/pycon.py +0 -0
  63. {backrefs-8.1 → backrefs-8.2}/tools/unicodedata/13.0.0.zip +0 -0
  64. {backrefs-8.1 → backrefs-8.2}/tools/unicodedata/14.0.0.zip +0 -0
  65. {backrefs-8.1 → backrefs-8.2}/tools/unicodedata/15.0.0.zip +0 -0
  66. {backrefs-8.1 → backrefs-8.2}/tools/unicodedata/15.1.0.zip +0 -0
  67. {backrefs-8.1 → backrefs-8.2}/tools/unicodedata/16.0.0.zip +0 -0
  68. {backrefs-8.1 → backrefs-8.2}/tools/unicodedata/17.0.0.zip +0 -0
  69. {backrefs-8.1 → backrefs-8.2}/tools/unicodedata/LICENSE +0 -0
  70. {backrefs-8.1 → 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-issue'
25
-
23
+ - '.md-social__link, .magiclink'
24
+
26
25
  - pyspelling.filters.url:
27
26
 
28
27
  - name: markdown
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: backrefs
3
- Version: 8.1
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,7 +13,6 @@ 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
@@ -21,7 +20,7 @@ Classifier: Programming Language :: Python :: 3.14
21
20
  Classifier: Programming Language :: Python :: 3.15
22
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
22
  Classifier: Typing :: Typed
24
- Requires-Python: >=3.10
23
+ Requires-Python: >=3.11
25
24
  Provides-Extra: extras
26
25
  Requires-Dist: regex; extra == 'extras'
27
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, 1, 0, "final")
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(_unicodedata.lookup(name))
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
- else:
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
- value = ord(self.convert_case(text, single)) if single is not None else ord(text)
877
+ text = self.convert_case(text, single) if single is not None else text
867
878
  elif single:
868
- value = ord(self.convert_case(chr(value), single))
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
- self.result.append(chr(value))
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(_unicodedata.lookup(self.get_named_unicode(i)))
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
- value = ord(self.convert_case(text, single)) if single is not None else ord(text)
908
+ text = self.convert_case(text, single) if single is not None else text
901
909
  elif single:
902
- value = ord(self.convert_case(chr(value), single))
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
- self.result.append(chr(value))
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
- value = ord(self.convert_case(text, single)) if single is not None else ord(text)
960
+ text = self.convert_case(text, single) if single is not None else text
956
961
  elif single:
957
- value = ord(self.convert_case(chr(value), single))
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
- self.result.append(chr(value))
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
- value = ord(self.convert_case(text, single)) if single is not None else ord(text)
986
+ text = self.convert_case(text, single) if single is not None else text
985
987
  elif single:
986
- value = ord(self.convert_case(chr(value), single))
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
- self.result.append(f'\\{value:03o}')
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
- value.append(c)
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
- value.append(c)
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 = _unicodedata.lookup(self.get_named_unicode(i))
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
- else:
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
- value = ord(self.convert_case(text, single)) if single is not None else ord(text)
721
+ text = self.convert_case(text, single) if single is not None else text
711
722
  elif single:
712
- value = ord(self.convert_case(chr(value), single))
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
- self.result.append(chr(value))
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(_unicodedata.lookup(self.get_named_unicode(i)))
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
- value = ord(self.convert_case(text, single)) if single is not None else ord(text)
752
+ text = self.convert_case(text, single) if single is not None else text
745
753
  elif single:
746
- value = ord(self.convert_case(chr(value), single))
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
- self.result.append(chr(value))
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
- value = ord(self.convert_case(text, single)) if single is not None else ord(text)
804
+ text = self.convert_case(text, single) if single is not None else text
800
805
  elif single:
801
- value = ord(self.convert_case(chr(value), single))
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
- self.result.append(chr(value))
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
- value = ord(self.convert_case(text, single)) if single is not None else ord(text)
830
+ text = self.convert_case(text, single) if single is not None else text
829
831
  elif single:
830
- value = ord(self.convert_case(chr(value), single))
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
- self.result.append(f'\\{value:03o}')
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
- value.append(c)
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
- value.append(c)
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 = _unicodedata.lookup(self.get_named_unicode(i))
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
@@ -0,0 +1,15 @@
1
+ BOM
2
+ BOMs
3
+ Bre
4
+ Preprocess
5
+ UTF
6
+ backref
7
+ backrefs
8
+ bidi
9
+ bregex
10
+ builtin
11
+ enum
12
+ expander
13
+ regex
14
+ sortable
15
+ whitespace
@@ -3,6 +3,23 @@ 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
+
6
23
  ## 8.1
7
24
 
8
25
  - **NEW**: Add official support for Python 3.15.
@@ -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\udb7f-\udb80\udbff-\udc00\udfff]`. Because of this, Backrefs can create
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
 
@@ -74,7 +74,6 @@ class CustomMetadataHook(MetadataHookInterface):
74
74
  'License :: OSI Approved :: MIT License',
75
75
  'Operating System :: OS Independent',
76
76
  'Programming Language :: Python :: 3',
77
- 'Programming Language :: Python :: 3.10',
78
77
  'Programming Language :: Python :: 3.11',
79
78
  'Programming Language :: Python :: 3.12',
80
79
  'Programming Language :: Python :: 3.13',
@@ -9,7 +9,7 @@ name = "backrefs"
9
9
  description = "A wrapper around re and regex that adds additional back references."
10
10
  readme = "README.md"
11
11
  license = "MIT"
12
- requires-python = ">=3.10"
12
+ requires-python = ">=3.11"
13
13
  authors = [
14
14
  { name = "Isaac Muse", email = "Isaac.Muse@gmail.com" },
15
15
  ]
@@ -39,11 +39,10 @@ include = [
39
39
  "/docs/src/markdown/**/*.md",
40
40
  "/docs/src/markdown/**/*.gif",
41
41
  "/docs/src/markdown/**/*.png",
42
- "/docs/src/markdown/dictionary/*.txt",
42
+ "/docs/src/dictionary/*.txt",
43
43
  "/docs/theme/**/*.css",
44
44
  "/docs/theme/**/*.js",
45
45
  "/docs/theme/**/*.html",
46
- "/requirements/*.txt",
47
46
  "/backrefs/**/*.py",
48
47
  "/backrefs/py.typed",
49
48
  "/tests/**/*.py",
@@ -52,7 +51,7 @@ include = [
52
51
  "/tools/unicodedata/LICENSE",
53
52
  "/.pyspelling.yml",
54
53
  "/.coveragerc",
55
- "/mkdocs.yml"
54
+ "/zensical.yml"
56
55
  ]
57
56
 
58
57
  exclude = [
@@ -155,10 +154,10 @@ dependencies = [
155
154
  ]
156
155
 
157
156
  [[tool.hatch.envs.dev.matrix]]
158
- python = ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]
157
+ python = ["3.11", "3.12", "3.13", "3.14", "3.15"]
159
158
 
160
159
  [[tool.hatch.envs.dev-stdlib.matrix]]
161
- python = ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]
160
+ python = ["3.11", "3.12", "3.13", "3.14", "3.15"]
162
161
 
163
162
  [tool.hatch.envs.dev.scripts]
164
163
  types = """
@@ -196,4 +195,4 @@ build = """
196
195
  python tools/unidatadownload.py && \
197
196
  python tools/unipropgen.py backrefs/uniprops/unidata && \
198
197
  python -m zensical.main build -f zensical.yml --clean
199
- """
198
+ """
@@ -50,6 +50,13 @@ class TestBidiClass(unittest.TestCase):
50
50
  result = uniprops.get_unicode_property('bidiclass', k, mode=uniprops.MODE_ASCII)
51
51
  self.assertEqual(result, uniprops.fmt_string(v, True))
52
52
 
53
+ def test_bidiclass_range(self):
54
+ """Test characters `UnicodeData.txt` defines with a `First` and a `Last` line."""
55
+
56
+ for char in ('\uac00', '\u4e16', '\ud55c', '\ud7a3'):
57
+ result = re.match('[%s]' % uniprops.get_unicode_property('bidiclass', 'l'), char)
58
+ self.assertTrue(result is not None)
59
+
53
60
  def test_bad_bidiclass(self):
54
61
  """Test `Bidi Class` property with bad value."""
55
62
 
@@ -161,6 +161,19 @@ class TestSearchTemplate(unittest.TestCase):
161
161
  with pytest.warns(FutureWarning):
162
162
  self.assertTrue(bre.compile(r'[[:graph:a]').match('a') is not None)
163
163
 
164
+ def test_bare_letter_after_inner_bracket_is_literal(self):
165
+ """Test that a letter right after an inner `[` is a literal char, not a property."""
166
+
167
+ pattern = bre.compile(r'[x[l]]')
168
+ self.assertTrue(pattern.match('[]') is not None)
169
+ self.assertTrue(pattern.match('l]') is not None)
170
+ self.assertTrue(pattern.match('x]') is not None)
171
+ self.assertTrue(pattern.match('a]') is None)
172
+ pattern = bre.compile(r'[}[d]')
173
+ self.assertTrue(pattern.match('[') is not None)
174
+ self.assertTrue(pattern.match('d') is not None)
175
+ self.assertTrue(pattern.match('}') is not None)
176
+
164
177
  def test_unicode_property_failures(self):
165
178
  """Test Unicode property."""
166
179
 
@@ -200,6 +213,12 @@ class TestSearchTemplate(unittest.TestCase):
200
213
  with pytest.raises(PatternError):
201
214
  bre.compile(r'\N{A')
202
215
 
216
+ with pytest.raises(PatternError):
217
+ bre.compile(r'\N{Undefined character}')
218
+
219
+ with pytest.raises(PatternError):
220
+ bre.compile_replace(re.compile(r'(a)'), r'\N{Undefined character}')
221
+
203
222
  def test_word_boundary(self):
204
223
  """Test word boundary."""
205
224
 
@@ -484,6 +503,31 @@ class TestSearchTemplate(unittest.TestCase):
484
503
  pattern2 = bre.compile_search('Test [!-\\~]', re.ASCII)
485
504
  self.assertEqual(pattern.pattern, pattern2.pattern)
486
505
 
506
+ def test_age_canonical_value(self):
507
+ """Test the `UCD` canonical age value form (`1.1`, not just the `V1_1` alias)."""
508
+
509
+ pattern = bre.compile_search(r'\p{Age=1.1}')
510
+ pattern2 = bre.compile_search(r'\p{Age=V1_1}')
511
+ self.assertEqual(pattern.pattern, pattern2.pattern)
512
+
513
+ pattern = bre.compile_search(r'\p{Age:1.1}')
514
+ pattern2 = bre.compile_search(r'\p{Age:V1_1}')
515
+ self.assertEqual(pattern.pattern, pattern2.pattern)
516
+
517
+ def test_age_canonical_value_in_group(self):
518
+ """Test the canonical age value form inside a character group."""
519
+
520
+ pattern = bre.compile_search(r'[\p{Age=13.0}]')
521
+ pattern2 = bre.compile_search(r'[\p{Age=V13_0}]')
522
+ self.assertEqual(pattern.pattern, pattern2.pattern)
523
+
524
+ def test_age_canonical_value_negated(self):
525
+ """Test the canonical age value form with a negated property."""
526
+
527
+ pattern = bre.compile_search(r'\p{^Age=1.1}')
528
+ pattern2 = bre.compile_search(r'\p{^Age=V1_1}')
529
+ self.assertEqual(pattern.pattern, pattern2.pattern)
530
+
487
531
  def test_not_posix_at_start_group(self):
488
532
  """Test a situation that is not a POSIX at the start of a group."""
489
533
 
@@ -1857,6 +1901,8 @@ class TestReplaceTemplate(unittest.TestCase):
1857
1901
  self.assertEqual(pattern.subf(r'{1:1^30}', 'Testing'), '11111111111111Te11111111111111')
1858
1902
  self.assertEqual(pattern.subf(r'{1:<30s}', 'Testing'), 'Te ')
1859
1903
  self.assertEqual(pattern.subf(r'{1:s}', 'Testing'), 'Te')
1904
+ self.assertEqual(pattern.subf(r'{1:}', 'Testing'), 'Te')
1905
+ self.assertEqual(pattern.subf(r'{1:}-x', 'Testing'), 'Te-x')
1860
1906
  self.assertEqual(pattern.subf(r'{2!r}', 'Testing'), "'st'")
1861
1907
 
1862
1908
  with pytest.raises(PatternError):
@@ -1889,6 +1935,8 @@ class TestReplaceTemplate(unittest.TestCase):
1889
1935
  self.assertEqual(pattern.subf(br'{1:1^30}', b'Testing'), b'11111111111111Te11111111111111')
1890
1936
  self.assertEqual(pattern.subf(br'{1:<30s}', b'Testing'), b'Te ')
1891
1937
  self.assertEqual(pattern.subf(br'{1:s}', b'Testing'), b'Te')
1938
+ self.assertEqual(pattern.subf(br'{1:}', b'Testing'), b'Te')
1939
+ self.assertEqual(pattern.subf(br'{1:}-x', b'Testing'), b'Te-x')
1892
1940
  self.assertEqual(pattern.subf(br'{2!r}', b'Testing'), b"b'st'")
1893
1941
 
1894
1942
  with pytest.raises(PatternError):
@@ -1909,6 +1957,40 @@ class TestReplaceTemplate(unittest.TestCase):
1909
1957
  with pytest.raises(PatternError):
1910
1958
  pattern.subf(br'{:030}', b'Testing')
1911
1959
 
1960
+ def test_case_hex_above_byte_range(self):
1961
+ """Test case conversion of hex escapes beyond the byte range."""
1962
+
1963
+ pattern = re.compile('Test')
1964
+ for template, expected in (
1965
+ (r'\c\xb5', '\u039c'),
1966
+ (r'\C\xff\E', '\u0178'),
1967
+ (r'\C\l\xb5\E', '\u03bc'),
1968
+ (r'\C\x5c\E', '\\')
1969
+ ):
1970
+ with self.subTest(template=template):
1971
+ expand = bre.compile_replace(pattern, template)
1972
+ results = expand(pattern.match('Test'))
1973
+ self.assertEqual(expected, results)
1974
+
1975
+ def test_case_escape_multiple_chars(self):
1976
+ """Test case conversion of escapes that give more than one character."""
1977
+
1978
+ pattern = re.compile('Test')
1979
+ for template, expected in (
1980
+ (r'\C\xdf\E', 'SS'),
1981
+ (r'\c\337', 'SS'),
1982
+ (r'\C\N{LATIN SMALL LETTER SHARP S}\E', 'SS'),
1983
+ (r'\C\u00df\E', 'SS'),
1984
+ (r'\L\u0130\E', 'i\u0307'),
1985
+ (r'\C\l\460\E', 'i\u0307'),
1986
+ (r'\C\l\N{LATIN CAPITAL LETTER I WITH DOT ABOVE}\E', 'i\u0307'),
1987
+ (r'\C\l\u0130\E', 'i\u0307')
1988
+ ):
1989
+ with self.subTest(template=template):
1990
+ expand = bre.compile_replace(pattern, template)
1991
+ results = expand(pattern.match('Test'))
1992
+ self.assertEqual(expected, results)
1993
+
1912
1994
  def test_dont_case_special_refs(self):
1913
1995
  """Test that we don't case Unicode and bytes tokens, but case the character."""
1914
1996
 
@@ -1979,6 +2061,27 @@ class TestReplaceTemplate(unittest.TestCase):
1979
2061
  class TestExceptions(unittest.TestCase):
1980
2062
  """Test Exceptions."""
1981
2063
 
2064
+ def test_bad_char_in_numeric_group_name(self):
2065
+ """Test bad character in a numeric group name."""
2066
+
2067
+ p = bre.compile_search(r'(a)(b)')
2068
+ with pytest.raises(PatternError):
2069
+ bre.compile_replace(p, r'Replace \g<1X>')
2070
+
2071
+ def test_bad_char_in_group_name(self):
2072
+ """Test bad character in a named group reference."""
2073
+
2074
+ p = bre.compile_search(r'(?P<word>a)(b)')
2075
+ with pytest.raises(PatternError):
2076
+ bre.compile_replace(p, r'Replace \g<w-ord>')
2077
+
2078
+ def test_bad_char_in_group_name_spacing(self):
2079
+ """Test spacing in a group name reference."""
2080
+
2081
+ p = bre.compile_search(r'(?P<word>a)(b)')
2082
+ with pytest.raises(PatternError):
2083
+ bre.compile_replace(p, r'Replace \g<word >')
2084
+
1982
2085
  def test_bad_flag(self):
1983
2086
  """Test bad flag."""
1984
2087
 
@@ -722,6 +722,9 @@ class TestReplaceTemplate(unittest.TestCase):
722
722
  with pytest.raises(PatternError):
723
723
  bregex.sub('test', r'\N{A', 'test')
724
724
 
725
+ with pytest.raises(PatternError):
726
+ bregex.sub('test', r'\N{Undefined character}', 'test')
727
+
725
728
  def test_group_failures(self):
726
729
  """Test group failures."""
727
730
 
@@ -1538,6 +1541,8 @@ class TestReplaceTemplate(unittest.TestCase):
1538
1541
  self.assertEqual(pattern.subf(r'{1:1^30}', 'Testing'), '11111111111111Te11111111111111')
1539
1542
  self.assertEqual(pattern.subf(r'{1:<30s}', 'Testing'), 'Te ')
1540
1543
  self.assertEqual(pattern.subf(r'{1:s}', 'Testing'), 'Te')
1544
+ self.assertEqual(pattern.subf(r'{1:}', 'Testing'), 'Te')
1545
+ self.assertEqual(pattern.subf(r'{1:}-x', 'Testing'), 'Te-x')
1541
1546
  self.assertEqual(pattern.subf(r'{2!r}', 'Testing'), "'st'")
1542
1547
 
1543
1548
  with pytest.raises(PatternError):
@@ -1570,6 +1575,8 @@ class TestReplaceTemplate(unittest.TestCase):
1570
1575
  self.assertEqual(pattern.subf(br'{1:1^30}', b'Testing'), b'11111111111111Te11111111111111')
1571
1576
  self.assertEqual(pattern.subf(br'{1:<30s}', b'Testing'), b'Te ')
1572
1577
  self.assertEqual(pattern.subf(br'{1:s}', b'Testing'), b'Te')
1578
+ self.assertEqual(pattern.subf(br'{1:}', b'Testing'), b'Te')
1579
+ self.assertEqual(pattern.subf(br'{1:}-x', b'Testing'), b'Te-x')
1573
1580
  self.assertEqual(pattern.subf(br'{2!r}', b'Testing'), b"b'st'")
1574
1581
 
1575
1582
  with pytest.raises(PatternError):
@@ -1590,6 +1597,39 @@ class TestReplaceTemplate(unittest.TestCase):
1590
1597
  with pytest.raises(ValueError):
1591
1598
  pattern.subf(br'{:030}', b'Testing')
1592
1599
 
1600
+ def test_case_hex_above_byte_range(self):
1601
+ """Test case conversion of hex escapes beyond the byte range."""
1602
+
1603
+ pattern = regex.compile('Test')
1604
+ for template, expected in (
1605
+ (r'\c\xb5', '\u039c'),
1606
+ (r'\C\l\xb5\E', '\u03bc'),
1607
+ (r'\C\x5c\E', '\\')
1608
+ ):
1609
+ with self.subTest(template=template):
1610
+ expand = bregex.compile_replace(pattern, template)
1611
+ results = expand(pattern.match('Test'))
1612
+ self.assertEqual(expected, results)
1613
+
1614
+ def test_case_escape_multiple_chars(self):
1615
+ """Test case conversion of escapes that give more than one character."""
1616
+
1617
+ pattern = regex.compile('Test')
1618
+ for template, expected in (
1619
+ (r'\C\xdf\E', 'SS'),
1620
+ (r'\c\337', 'SS'),
1621
+ (r'\C\N{LATIN SMALL LETTER SHARP S}\E', 'SS'),
1622
+ (r'\C\u00df\E', 'SS'),
1623
+ (r'\L\u0130\E', 'i\u0307'),
1624
+ (r'\C\l\460\E', 'i\u0307'),
1625
+ (r'\C\l\N{LATIN CAPITAL LETTER I WITH DOT ABOVE}\E', 'i\u0307'),
1626
+ (r'\C\l\u0130\E', 'i\u0307')
1627
+ ):
1628
+ with self.subTest(template=template):
1629
+ expand = bregex.compile_replace(pattern, template)
1630
+ results = expand(pattern.match('Test'))
1631
+ self.assertEqual(expected, results)
1632
+
1593
1633
  def test_dont_case_special_refs(self):
1594
1634
  """Test that we don't case Unicode and bytes tokens, but case the character."""
1595
1635
 
@@ -1660,6 +1700,27 @@ class TestReplaceTemplate(unittest.TestCase):
1660
1700
  class TestExceptions(unittest.TestCase):
1661
1701
  """Test Exceptions."""
1662
1702
 
1703
+ def test_bad_char_in_numeric_group_name(self):
1704
+ """Test bad character in a numeric group name."""
1705
+
1706
+ p = bregex.compile_search(r'(a)(b)')
1707
+ with pytest.raises(PatternError):
1708
+ bregex.compile_replace(p, r'Replace \g<1X>')
1709
+
1710
+ def test_bad_char_in_group_name(self):
1711
+ """Test bad character in a named group reference."""
1712
+
1713
+ p = bregex.compile_search(r'(?P<word>a)(b)')
1714
+ with pytest.raises(PatternError):
1715
+ bregex.compile_replace(p, r'Replace \g<w-ord>')
1716
+
1717
+ def test_bad_char_in_group_name_spacing(self):
1718
+ """Test spacing in a group name reference."""
1719
+
1720
+ p = bregex.compile_search(r'(?P<word>a)(b)')
1721
+ with pytest.raises(PatternError):
1722
+ bregex.compile_replace(p, r'Replace \g<word >')
1723
+
1663
1724
  def test_format_existing_group_no_match_with_index(self):
1664
1725
  """Test format group with no match and attempt at indexing."""
1665
1726
 
@@ -125,6 +125,14 @@ class TestGeneralCategory(unittest.TestCase):
125
125
  result = uniprops.get_unicode_property(category, mode=uniprops.MODE_ASCII)
126
126
  self.assertEqual(result, uniprops.fmt_string(v2, True))
127
127
 
128
+ def test_gc_range(self):
129
+ """Test characters `UnicodeData.txt` defines with a `First` and a `Last` line."""
130
+
131
+ for char in ('\uac00', '\u4e16', '\ud55c', '\ud7a3'):
132
+ for category, match in (('lo', True), ('l', True), ('cn', False), ('^cn', True)):
133
+ result = re.match('[%s]' % uniprops.get_unicode_property(category), char)
134
+ self.assertEqual(result is not None, match)
135
+
128
136
  def test_bad_single_subcategory(self):
129
137
  """Test `Joining Group` property with bad value."""
130
138
 
@@ -60,6 +60,22 @@ def create_span(unirange, is_bytes=False):
60
60
  return list(range(unirange[0], unirange[1] + 1))
61
61
 
62
62
 
63
+ def expand_ranges(lines):
64
+ """Give each code point in a `First`/`Last` range of `UnicodeData.txt` a line of its own."""
65
+
66
+ first = None
67
+ for line in lines:
68
+ code, name, fields = line.split(';', 2)
69
+ if name.endswith(', First>'):
70
+ first = int(code, 16)
71
+ elif first is None:
72
+ yield line
73
+ else:
74
+ for i in range(first, int(code, 16) + 1):
75
+ yield '%04X;%s;%s' % (i, name, fields)
76
+ first = None
77
+
78
+
63
79
  def not_explicitly_defined(table, name, is_bytes=False):
64
80
  """Compose a table with the specified entry name of values not explicitly defined."""
65
81
 
@@ -780,7 +796,7 @@ def gen_bidi(output, ascii_props=False, append=False, prefix="", aliases=None):
780
796
 
781
797
  max_range = MAXVALIDASCII if ascii_props else MAXUNICODE
782
798
  with open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'UnicodeData.txt'), 'r', encoding='utf-8') as uf:
783
- for line in uf:
799
+ for line in expand_ranges(uf):
784
800
  data = line.strip().split(';')
785
801
  if data:
786
802
  bidi = data[4].strip().lower()
@@ -1042,7 +1058,7 @@ def gen_properties(output, files, aliases, ascii_props=False, append=False):
1042
1058
  table = {'l': {'c': []}}
1043
1059
  itable = {'l': {}}
1044
1060
  with open(os.path.join(HOME, 'unicodedata', UNIVERSION, 'UnicodeData.txt'), 'r', encoding='utf-8') as uf:
1045
- for line in uf:
1061
+ for line in expand_ranges(uf):
1046
1062
  data = line.strip().split(';')
1047
1063
  if data:
1048
1064
  i = int(data[0], 16)
@@ -0,0 +1,178 @@
1
+ site_name: Backrefs Documentation
2
+ site_url: https://facelessuser.github.io/backrefs
3
+ repo_url: https://github.com/facelessuser/backrefs
4
+ edit_uri: tree/master/docs/src/markdown
5
+ site_description: A library to add additional back references to regular expressions.
6
+ copyright: |
7
+ Copyright &copy; 2015 - 2026 <a href="https://github.com/facelessuser" target="_blank" rel="noopener">Isaac Muse</a>
8
+
9
+ docs_dir: docs/src/markdown
10
+ theme:
11
+ name: zensical_pymdownx
12
+ custom_dir: docs/theme
13
+ features:
14
+ - announce.dismiss
15
+ - navigation.tabs
16
+ - navigation.top
17
+ - navigation.instant
18
+ - navigation.indexes
19
+ - navigation.path
20
+ - navigation.footer
21
+ - toc.follow
22
+ - content.tabs.link
23
+ - search.share
24
+ - search.highlight
25
+ - search.suggest
26
+ - content.code.copy
27
+ - content.tabs.link
28
+ - content.code.annotations
29
+
30
+ nav:
31
+ - Getting Started:
32
+ - Installation: index.md
33
+ - Usage: usage.md
34
+ - Supported References: refs.md
35
+ - About:
36
+ - Contributing &amp; Support: about/contributing.md
37
+ - Security Vulnerabilities: about/security.md
38
+ - Changelog: about/changelog.md
39
+ - License: about/license.md
40
+
41
+ markdown_extensions:
42
+ - markdown.extensions.toc:
43
+ slugify: !!python/object/apply:pymdownx.slugs.slugify {kwds: {case: lower}}
44
+ permalink: ""
45
+ - markdown.extensions.smarty:
46
+ smart_quotes: false
47
+ - pymdownx.betterem:
48
+ - markdown.extensions.attr_list:
49
+ - markdown.extensions.tables:
50
+ - markdown.extensions.abbr:
51
+ - markdown.extensions.md_in_html:
52
+ - pymdownx.superfences:
53
+ custom_fences:
54
+ - name: py
55
+ class: 'highlight'
56
+ format: !!python/object/apply:tools.pycon.py_command_formatter
57
+ kwds:
58
+ init: |
59
+ import backrefs
60
+ from backrefs import bre, bregex
61
+ import re
62
+ import regex
63
+ validator: !!python/name:tools.pycon.py_command_validator
64
+ reset: !!python/name:tools.pycon.py_command_reset
65
+ - name: python
66
+ class: 'highlight'
67
+ format: !!python/object/apply:tools.pycon.py_command_formatter
68
+ kwds:
69
+ init: |
70
+ import backrefs
71
+ from backrefs import bre, bregex
72
+ import re
73
+ import regex
74
+ validator: !!python/name:tools.pycon.py_command_validator
75
+ reset: !!python/name:tools.pycon.py_command_reset
76
+ - pymdownx.highlight:
77
+ line_spans: __codeline
78
+ line_anchors: __codelineno
79
+ anchor_linenums: True
80
+ extend_pygments_lang:
81
+ - name: php-inline
82
+ lang: php
83
+ options:
84
+ startinline: true
85
+ - name: pycon3
86
+ lang: pycon
87
+ options:
88
+ python3: true
89
+ - pymdownx.inlinehilite:
90
+ - pymdownx.magiclink:
91
+ repo_url_shortener: true
92
+ repo_url_shorthand: true
93
+ social_url_shorthand: true
94
+ user: facelessuser
95
+ repo: backrefs
96
+ - pymdownx.tilde:
97
+ - pymdownx.caret:
98
+ - pymdownx.smartsymbols:
99
+ - pymdownx.emoji:
100
+ emoji_index: !!python/name:material.extensions.emoji.twemoji
101
+ emoji_generator: !!python/name:material.extensions.emoji.to_svg
102
+ - pymdownx.escapeall:
103
+ hardbreak: True
104
+ nbsp: True
105
+ - pymdownx.tasklist:
106
+ custom_checkbox: true
107
+ - pymdownx.progressbar:
108
+ - pymdownx.mark:
109
+ - pymdownx.striphtml:
110
+ - pymdownx.snippets:
111
+ base_path:
112
+ - docs/src/markdown/.snippets
113
+ - LICENSE.md
114
+ - SECURITY.md
115
+ auto_append:
116
+ - links.md
117
+ - pymdownx.keys:
118
+ separator: "\uff0b"
119
+ - pymdownx.saneheaders:
120
+ - pymdownx.blocks.admonition:
121
+ types:
122
+ - new
123
+ - settings
124
+ - note
125
+ - abstract
126
+ - info
127
+ - tip
128
+ - success
129
+ - question
130
+ - warning
131
+ - failure
132
+ - danger
133
+ - bug
134
+ - example
135
+ - quote
136
+ - pymdownx.blocks.details:
137
+ types:
138
+ - name: details-new
139
+ class: new
140
+ - name: details-settings
141
+ class: settings
142
+ - name: details-note
143
+ class: note
144
+ - name: details-abstract
145
+ class: abstract
146
+ - name: details-info
147
+ class: info
148
+ - name: details-tip
149
+ class: tip
150
+ - name: details-success
151
+ class: success
152
+ - name: details-question
153
+ class: question
154
+ - name: details-warning
155
+ class: warning
156
+ - name: details-failure
157
+ class: failure
158
+ - name: details-danger
159
+ class: danger
160
+ - name: details-bug
161
+ class: bug
162
+ - name: details-example
163
+ class: example
164
+ - name: details-quote
165
+ class: quote
166
+ - pymdownx.blocks.html:
167
+ - pymdownx.blocks.definition:
168
+ - pymdownx.blocks.tab:
169
+ alternate_style: True
170
+ - pymdownx.quotes:
171
+ callouts: true
172
+
173
+ extra:
174
+ social:
175
+ - icon: fontawesome/brands/github
176
+ link: https://github.com/facelessuser
177
+ zensical_pymdownx:
178
+ sponsor: https://github.com/sponsors/facelessuser
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