backrefs 8.1__tar.gz → 8.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {backrefs-8.1 → backrefs-8.2.1}/.pyspelling.yml +2 -3
- {backrefs-8.1 → backrefs-8.2.1}/PKG-INFO +2 -3
- {backrefs-8.1 → backrefs-8.2.1}/backrefs/__meta__.py +1 -1
- {backrefs-8.1 → backrefs-8.2.1}/backrefs/_bre_parse.py +169 -56
- {backrefs-8.1 → backrefs-8.2.1}/backrefs/_bregex_parse.py +213 -125
- {backrefs-8.1 → backrefs-8.2.1}/backrefs/bre.py +3 -2
- {backrefs-8.1 → backrefs-8.2.1}/backrefs/bregex.py +3 -2
- {backrefs-8.1 → backrefs-8.2.1}/backrefs/util.py +10 -0
- backrefs-8.2.1/docs/src/dictionary/en-custom.txt +67 -0
- backrefs-8.2.1/docs/src/dictionary/en-python.txt +15 -0
- {backrefs-8.1 → backrefs-8.2.1}/docs/src/markdown/about/changelog.md +38 -0
- {backrefs-8.1 → backrefs-8.2.1}/docs/src/markdown/refs.md +1 -1
- {backrefs-8.1 → backrefs-8.2.1}/hatch_build.py +0 -1
- {backrefs-8.1 → backrefs-8.2.1}/pyproject.toml +6 -7
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_bidiclass.py +7 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_bre.py +240 -8
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_bregex.py +169 -4
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_generalcategory.py +8 -0
- {backrefs-8.1 → backrefs-8.2.1}/tools/unipropgen.py +18 -2
- backrefs-8.2.1/zensical.yml +178 -0
- backrefs-8.1/tools/unicodedata/13.0.0.zip +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/.coveragerc +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/.gitignore +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/LICENSE.md +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/README.md +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/backrefs/__init__.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/backrefs/_bregex_typing.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/backrefs/py.typed +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/backrefs/uniprops/__init__.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/docs/src/markdown/.snippets/links.md +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/docs/src/markdown/about/contributing.md +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/docs/src/markdown/about/license.md +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/docs/src/markdown/about/security.md +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/docs/src/markdown/index.md +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/docs/src/markdown/usage.md +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/docs/theme/announce.html +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/__init__.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_age.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_bidipairedbrackettype.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_binary.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_block.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_canonicalcombiningclass.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_decompositiontype.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_eastasianwidth.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_graphemeclusterbreak.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_hangulsyllabletype.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_indicpositionalcategory.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_indicsylabiccategory.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_joininggroup.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_joiningtype.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_linebreak.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_numerictype.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_numericvalue.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_quickcheck.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_script.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_scriptextensions.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_sentencebreak.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_uniprops.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_versions.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_verticalorientation.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tests/test_wordbreak.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tools/__init__.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tools/pycon.py +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tools/unicodedata/14.0.0.zip +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tools/unicodedata/15.0.0.zip +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tools/unicodedata/15.1.0.zip +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tools/unicodedata/16.0.0.zip +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tools/unicodedata/17.0.0.zip +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/tools/unicodedata/LICENSE +0 -0
- {backrefs-8.1 → backrefs-8.2.1}/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
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: backrefs
|
|
3
|
-
Version: 8.1
|
|
3
|
+
Version: 8.2.1
|
|
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.
|
|
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,
|
|
196
|
+
__version_info__ = Version(8, 2, 1, "final")
|
|
197
197
|
__version__ = __version_info__._get_canonical()
|
|
@@ -6,19 +6,13 @@ Copyright (c) 2011 - 2020 Isaac Muse <isaacmuse@gmail.com>
|
|
|
6
6
|
"""
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
import re as _re
|
|
9
|
-
import
|
|
9
|
+
import re._parser as _parser # type: ignore[import]
|
|
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
|
|
|
17
|
-
if sys.version_info >= (3, 11):
|
|
18
|
-
import re._parser as _parser # type: ignore[import]
|
|
19
|
-
else:
|
|
20
|
-
import sre_parse as _parser
|
|
21
|
-
|
|
22
16
|
__all__ = ("ReplaceTemplate", "PatternError")
|
|
23
17
|
|
|
24
18
|
_ASCII_LETTERS = frozenset(
|
|
@@ -37,7 +31,7 @@ _WORD = _LETTERS_UNDERSCORE | _DIGIT
|
|
|
37
31
|
_STANDARD_ESCAPES = frozenset(('a', 'b', 'f', 'n', 'r', 't', 'v'))
|
|
38
32
|
_CURLY_BRACKETS = frozenset(('{', '}'))
|
|
39
33
|
_PROPERTY_STRIP = frozenset((' ', '-', '_'))
|
|
40
|
-
_PROPERTY = _WORD | _DIGIT | _PROPERTY_STRIP
|
|
34
|
+
_PROPERTY = _WORD | _DIGIT | _PROPERTY_STRIP | frozenset(('.',))
|
|
41
35
|
_SCOPED_FLAGS = frozenset(('a', 'i', 'L', 'm', 'u', 's', 'x'))
|
|
42
36
|
_SCOPED_INV_FLAGS = frozenset(('i', 'm', 's', 'x'))
|
|
43
37
|
_SCOPED_END = frozenset((':', ')'))
|
|
@@ -77,11 +71,12 @@ class _SearchParser(Generic[AnyStr]):
|
|
|
77
71
|
_re_start_wb = r"\b(?=\w)"
|
|
78
72
|
_re_end_wb = r"\b(?<=\w)"
|
|
79
73
|
_line_break = r'(?:\r\n|(?!\r\n)[\n\v\f\r\x85\u2028\u2029])'
|
|
80
|
-
_bytes_line_break = r'(?>\r\n|[\n\v\f\r\x85])'
|
|
74
|
+
_bytes_line_break = r'(?>\r\n|[\n\v\f\r\x85])'
|
|
81
75
|
_grapheme_cluster = r'(?:{}{}*(?!{}))'
|
|
82
76
|
|
|
83
77
|
verbose: bool
|
|
84
78
|
unicode: bool
|
|
79
|
+
named_unicode: tuple[str, int]
|
|
85
80
|
ascii: bool # noqa: A003
|
|
86
81
|
is_bytes: bool
|
|
87
82
|
search: AnyStr
|
|
@@ -176,7 +171,7 @@ class _SearchParser(Generic[AnyStr]):
|
|
|
176
171
|
value = []
|
|
177
172
|
try:
|
|
178
173
|
c = next(i)
|
|
179
|
-
if c.upper() in _ASCII_LETTERS:
|
|
174
|
+
if not brackets and c.upper() in _ASCII_LETTERS:
|
|
180
175
|
prop.append(c)
|
|
181
176
|
elif (not brackets and c != '{') or (brackets and c != ':'):
|
|
182
177
|
raise PatternError(f"Unicode property missing '{{' at {i.index - 1}!")
|
|
@@ -284,6 +279,7 @@ class _SearchParser(Generic[AnyStr]):
|
|
|
284
279
|
current.extend(self.unicode_name(text, in_group))
|
|
285
280
|
if in_group:
|
|
286
281
|
self.found_named_unicode = True
|
|
282
|
+
self.named_unicode = (f'\\N{{{text}}}', ord(_util.unicode_char(text)))
|
|
287
283
|
else:
|
|
288
284
|
current.extend(["\\", t])
|
|
289
285
|
return current
|
|
@@ -419,27 +415,55 @@ class _SearchParser(Generic[AnyStr]):
|
|
|
419
415
|
self.verbose = verbose
|
|
420
416
|
self.unicode = unicode_flag
|
|
421
417
|
|
|
422
|
-
|
|
423
|
-
current.append(t)
|
|
418
|
+
current.append(t)
|
|
424
419
|
return current
|
|
425
420
|
|
|
426
421
|
def char_groups(self, t: str, i: _util.StringIter) -> list[str]:
|
|
427
422
|
"""Handle character groups."""
|
|
428
423
|
|
|
429
|
-
current = []
|
|
424
|
+
current = [] # type: list[str]
|
|
430
425
|
pos = i.index - 1
|
|
431
426
|
found = False
|
|
432
427
|
escaped = False
|
|
428
|
+
escape_index = 0
|
|
433
429
|
first = 0
|
|
434
430
|
found_property = False
|
|
431
|
+
# Index of an unescaped `-` in `current` that is acting as a range operator.
|
|
432
|
+
range_dash = -1
|
|
433
|
+
# Whether the last member could start a range if followed by `-`.
|
|
434
|
+
range_start = False
|
|
435
|
+
# The last member's original text, position, and code point if it is a named character
|
|
436
|
+
# beyond the byte range of a byte string.
|
|
437
|
+
start = ('', 0, None) # type: tuple[str, int, int | None]
|
|
435
438
|
self.found_property = False
|
|
436
439
|
self.found_named_unicode = False
|
|
437
440
|
|
|
441
|
+
def add_member(raw: str, index: int, value: int | None) -> None:
|
|
442
|
+
"""Track a member that was just added, which may start or end a range."""
|
|
443
|
+
|
|
444
|
+
nonlocal range_dash, range_start, start
|
|
445
|
+
|
|
446
|
+
if range_dash != -1:
|
|
447
|
+
# This member ends a range.
|
|
448
|
+
start_raw, start_index, start_value = start
|
|
449
|
+
if start_value is not None or value is not None:
|
|
450
|
+
end = self.get_named_byte_range(start_raw, raw, start_value, value, start_index)
|
|
451
|
+
if end is None:
|
|
452
|
+
current[range_dash] = ''
|
|
453
|
+
else:
|
|
454
|
+
current[-1] = end
|
|
455
|
+
range_dash = -1
|
|
456
|
+
range_start = False
|
|
457
|
+
else:
|
|
458
|
+
start = (raw, index, value)
|
|
459
|
+
range_start = True
|
|
460
|
+
|
|
438
461
|
try:
|
|
439
462
|
while True:
|
|
440
463
|
# Prevent POSIX/Unicode class from being part of a range.
|
|
441
464
|
if self.found_property and t == '-':
|
|
442
465
|
current.append(_re.escape(t))
|
|
466
|
+
add_member(t, i.index - 1, None)
|
|
443
467
|
pos += 1
|
|
444
468
|
t = next(i)
|
|
445
469
|
self.found_property = False
|
|
@@ -449,15 +473,26 @@ class _SearchParser(Generic[AnyStr]):
|
|
|
449
473
|
|
|
450
474
|
if not escaped and t == "\\":
|
|
451
475
|
escaped = True
|
|
476
|
+
escape_index = i.index - 1
|
|
452
477
|
elif escaped:
|
|
453
478
|
escaped = False
|
|
454
479
|
idx = len(current) - 1
|
|
455
480
|
current.extend(self.reference(t, i, True))
|
|
456
481
|
if self.found_property:
|
|
457
482
|
# Prevent Unicode class from being part of a range.
|
|
458
|
-
if idx
|
|
483
|
+
if idx == range_dash:
|
|
459
484
|
current[idx] = _re.escape('-')
|
|
460
485
|
found_property = True
|
|
486
|
+
range_dash = -1
|
|
487
|
+
range_start = False
|
|
488
|
+
elif t == 'N':
|
|
489
|
+
raw, code = self.named_unicode
|
|
490
|
+
add_member(raw, escape_index, code if self.is_bytes and code > 0xFF else None)
|
|
491
|
+
else:
|
|
492
|
+
digits = self.get_escape_digits(i, t)
|
|
493
|
+
if digits:
|
|
494
|
+
current.append(digits)
|
|
495
|
+
add_member(f'\\{t}{digits}', escape_index, None)
|
|
461
496
|
elif t == "[" and not found:
|
|
462
497
|
found = True
|
|
463
498
|
first = pos
|
|
@@ -471,22 +506,43 @@ class _SearchParser(Generic[AnyStr]):
|
|
|
471
506
|
i.rewind(i.index - index)
|
|
472
507
|
if prop is not None:
|
|
473
508
|
value = self.unicode_props(prop[0], prop[1], in_group=True)
|
|
474
|
-
|
|
509
|
+
# Prevent POSIX class from being part of a range.
|
|
510
|
+
if len(current) - 1 == range_dash:
|
|
475
511
|
current[-1] = _re.escape('-')
|
|
476
512
|
current.extend(value)
|
|
477
513
|
found_property = True
|
|
514
|
+
self.found_property = True
|
|
478
515
|
pos = i.index - 2
|
|
516
|
+
range_dash = -1
|
|
517
|
+
range_start = False
|
|
479
518
|
else:
|
|
480
519
|
current.append(t)
|
|
481
|
-
|
|
520
|
+
add_member(t, index - 1, None)
|
|
521
|
+
elif t == "^" and found and (pos == first + 1) and len(current) == 1:
|
|
522
|
+
# Only a `^` directly after the opening `[` negates the set.
|
|
482
523
|
first = pos
|
|
483
524
|
current.append(t)
|
|
484
525
|
elif t == "]" and found and (pos != first + 1):
|
|
485
526
|
found = False
|
|
486
527
|
current.append(t)
|
|
487
528
|
break
|
|
488
|
-
|
|
529
|
+
elif t == '-' and range_start:
|
|
530
|
+
# A `-` after a member starts a range, unless it is followed by `]`.
|
|
489
531
|
current.append(t)
|
|
532
|
+
range_dash = len(current) - 1
|
|
533
|
+
range_start = False
|
|
534
|
+
else:
|
|
535
|
+
if (
|
|
536
|
+
t == '^' and
|
|
537
|
+
(found_property or self.found_named_unicode) and
|
|
538
|
+
''.join(current) in ('[', '[^')
|
|
539
|
+
):
|
|
540
|
+
# Everything before this was a property that resolved to nothing,
|
|
541
|
+
# so escape `^` to prevent it from being treated as a negation.
|
|
542
|
+
current.append(_re.escape(t))
|
|
543
|
+
else:
|
|
544
|
+
current.append(t)
|
|
545
|
+
add_member(t, i.index - 1, None)
|
|
490
546
|
pos += 1
|
|
491
547
|
t = next(i)
|
|
492
548
|
except StopIteration as e:
|
|
@@ -533,7 +589,7 @@ class _SearchParser(Generic[AnyStr]):
|
|
|
533
589
|
def unicode_name(self, name: str, in_group: bool = False) -> list[str]:
|
|
534
590
|
"""Insert Unicode value by its name."""
|
|
535
591
|
|
|
536
|
-
value = ord(
|
|
592
|
+
value = ord(_util.unicode_char(name))
|
|
537
593
|
if self.is_bytes and value > 0xFF:
|
|
538
594
|
if not in_group:
|
|
539
595
|
return ['(?!)']
|
|
@@ -541,6 +597,60 @@ class _SearchParser(Generic[AnyStr]):
|
|
|
541
597
|
return ['']
|
|
542
598
|
return [f'\\{value:03o}' if value <= 0xFF else chr(value)]
|
|
543
599
|
|
|
600
|
+
def get_escape_digits(self, i: _util.StringIter, e: str) -> str:
|
|
601
|
+
r"""
|
|
602
|
+
Get the digits that are part of an escape in a character set (`\x`, `\u`, `\U`, or octal).
|
|
603
|
+
|
|
604
|
+
This allows an escape to be treated as a single member of the character set.
|
|
605
|
+
"""
|
|
606
|
+
|
|
607
|
+
if e == 'x':
|
|
608
|
+
count, valid = 2, _HEX
|
|
609
|
+
elif e == 'u' and not self.is_bytes:
|
|
610
|
+
count, valid = 4, _HEX
|
|
611
|
+
elif e == 'U' and not self.is_bytes:
|
|
612
|
+
count, valid = 8, _HEX
|
|
613
|
+
elif e in _OCTAL:
|
|
614
|
+
count, valid = 2, _OCTAL
|
|
615
|
+
else:
|
|
616
|
+
return ''
|
|
617
|
+
|
|
618
|
+
digits = []
|
|
619
|
+
while count:
|
|
620
|
+
try:
|
|
621
|
+
c = next(i)
|
|
622
|
+
except StopIteration:
|
|
623
|
+
break
|
|
624
|
+
if c.lower() not in valid:
|
|
625
|
+
i.rewind(1)
|
|
626
|
+
break
|
|
627
|
+
digits.append(c)
|
|
628
|
+
count -= 1
|
|
629
|
+
return ''.join(digits)
|
|
630
|
+
|
|
631
|
+
@staticmethod
|
|
632
|
+
def get_named_byte_range(
|
|
633
|
+
start: str,
|
|
634
|
+
end: str,
|
|
635
|
+
start_value: int | None,
|
|
636
|
+
end_value: int | None,
|
|
637
|
+
index: int
|
|
638
|
+
) -> str | None:
|
|
639
|
+
"""
|
|
640
|
+
Get the end of a range in a byte string where either end is a named character beyond the byte range.
|
|
641
|
+
|
|
642
|
+
`start_value` and `end_value` are the code points of the ends that are named characters beyond the byte
|
|
643
|
+
range, and `None` for ends within the byte range. `start` and `end` are the original text of each end,
|
|
644
|
+
and `index` is where the range starts, both used for errors.
|
|
645
|
+
|
|
646
|
+
A range that ends beyond the byte range includes everything up to the end of the byte range, so return
|
|
647
|
+
the end to use in its place. A range that starts beyond the byte range matches nothing, so return `None`.
|
|
648
|
+
"""
|
|
649
|
+
|
|
650
|
+
if start_value is not None and (end_value is None or end_value < start_value):
|
|
651
|
+
raise PatternError(f'bad character range {start}-{end} at position {index}')
|
|
652
|
+
return None if start_value is not None else '\\377'
|
|
653
|
+
|
|
544
654
|
def unicode_props(
|
|
545
655
|
self,
|
|
546
656
|
props: str,
|
|
@@ -755,7 +865,7 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
755
865
|
else:
|
|
756
866
|
width.append(fill)
|
|
757
867
|
fill = None
|
|
758
|
-
|
|
868
|
+
elif c != '}':
|
|
759
869
|
fill = c
|
|
760
870
|
c = self.format_next(i)
|
|
761
871
|
if fill == 's' and c == '}':
|
|
@@ -852,6 +962,18 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
852
962
|
|
|
853
963
|
return ''.join(value) if value else None
|
|
854
964
|
|
|
965
|
+
def append_chars(self, text: str, i: _util.StringIter) -> None:
|
|
966
|
+
"""Append each character of an escape value as a template escape."""
|
|
967
|
+
|
|
968
|
+
for c in text:
|
|
969
|
+
value = ord(c)
|
|
970
|
+
if self.use_format and value in _CURLY_BRACKETS_ORD:
|
|
971
|
+
self.handle_format(c, i)
|
|
972
|
+
elif value <= 0xFF:
|
|
973
|
+
self.result.append(f'\\{value:03o}')
|
|
974
|
+
else:
|
|
975
|
+
self.result.append(c)
|
|
976
|
+
|
|
855
977
|
def parse_octal(self, text: str, i: _util.StringIter) -> None:
|
|
856
978
|
"""Parse octal value."""
|
|
857
979
|
|
|
@@ -863,15 +985,12 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
863
985
|
single = self.get_single_stack()
|
|
864
986
|
if self.span_stack:
|
|
865
987
|
text = self.convert_case(chr(value), self.span_stack[-1])
|
|
866
|
-
|
|
988
|
+
text = self.convert_case(text, single) if single is not None else text
|
|
867
989
|
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}')
|
|
990
|
+
text = self.convert_case(chr(value), single)
|
|
873
991
|
else:
|
|
874
|
-
|
|
992
|
+
text = chr(value)
|
|
993
|
+
self.append_chars(text, i)
|
|
875
994
|
|
|
876
995
|
def get_named_unicode(self, i: _util.StringIter) -> str:
|
|
877
996
|
"""Get named Unicode."""
|
|
@@ -893,19 +1012,16 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
893
1012
|
def parse_named_unicode(self, i: _util.StringIter) -> None:
|
|
894
1013
|
"""Parse named Unicode."""
|
|
895
1014
|
|
|
896
|
-
value = ord(
|
|
1015
|
+
value = ord(_util.unicode_char(self.get_named_unicode(i)))
|
|
897
1016
|
single = self.get_single_stack()
|
|
898
1017
|
if self.span_stack:
|
|
899
1018
|
text = self.convert_case(chr(value), self.span_stack[-1])
|
|
900
|
-
|
|
1019
|
+
text = self.convert_case(text, single) if single is not None else text
|
|
901
1020
|
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}')
|
|
1021
|
+
text = self.convert_case(chr(value), single)
|
|
907
1022
|
else:
|
|
908
|
-
|
|
1023
|
+
text = chr(value)
|
|
1024
|
+
self.append_chars(text, i)
|
|
909
1025
|
|
|
910
1026
|
def get_wide_unicode(self, i: _util.StringIter) -> str:
|
|
911
1027
|
"""Get narrow Unicode."""
|
|
@@ -952,15 +1068,12 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
952
1068
|
single = self.get_single_stack()
|
|
953
1069
|
if self.span_stack:
|
|
954
1070
|
text = self.convert_case(chr(value), self.span_stack[-1])
|
|
955
|
-
|
|
1071
|
+
text = self.convert_case(text, single) if single is not None else text
|
|
956
1072
|
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}')
|
|
1073
|
+
text = self.convert_case(chr(value), single)
|
|
962
1074
|
else:
|
|
963
|
-
|
|
1075
|
+
text = chr(value)
|
|
1076
|
+
self.append_chars(text, i)
|
|
964
1077
|
|
|
965
1078
|
def get_byte(self, i: _util.StringIter) -> str:
|
|
966
1079
|
"""Get byte."""
|
|
@@ -981,13 +1094,12 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
981
1094
|
single = self.get_single_stack()
|
|
982
1095
|
if self.span_stack:
|
|
983
1096
|
text = self.convert_case(chr(value), self.span_stack[-1])
|
|
984
|
-
|
|
1097
|
+
text = self.convert_case(text, single) if single is not None else text
|
|
985
1098
|
elif single:
|
|
986
|
-
|
|
987
|
-
if self.use_format and value in _CURLY_BRACKETS_ORD:
|
|
988
|
-
self.handle_format(chr(value), i)
|
|
1099
|
+
text = self.convert_case(chr(value), single)
|
|
989
1100
|
else:
|
|
990
|
-
|
|
1101
|
+
text = chr(value)
|
|
1102
|
+
self.append_chars(text, i)
|
|
991
1103
|
|
|
992
1104
|
def get_named_group(self, t: str, i: _util.StringIter) -> str:
|
|
993
1105
|
"""Get group number."""
|
|
@@ -1004,16 +1116,18 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
1004
1116
|
value.append(c)
|
|
1005
1117
|
c = next(i)
|
|
1006
1118
|
while c != '>':
|
|
1007
|
-
if c in _DIGIT:
|
|
1008
|
-
|
|
1119
|
+
if c not in _DIGIT:
|
|
1120
|
+
raise PatternError(f"Bad character in group name at {i.index - 1}!")
|
|
1121
|
+
value.append(c)
|
|
1009
1122
|
c = next(i)
|
|
1010
1123
|
value.append(c)
|
|
1011
1124
|
elif c in _LETTERS_UNDERSCORE:
|
|
1012
1125
|
value.append(c)
|
|
1013
1126
|
c = next(i)
|
|
1014
1127
|
while c != '>':
|
|
1015
|
-
if c in _WORD:
|
|
1016
|
-
|
|
1128
|
+
if c not in _WORD:
|
|
1129
|
+
raise PatternError(f"Bad character in group name at {i.index - 1}!")
|
|
1130
|
+
value.append(c)
|
|
1017
1131
|
c = next(i)
|
|
1018
1132
|
value.append(c)
|
|
1019
1133
|
else:
|
|
@@ -1062,7 +1176,7 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
1062
1176
|
elif not self.is_bytes and t == "u":
|
|
1063
1177
|
value = chr(int(self.get_narrow_unicode(i), 16))
|
|
1064
1178
|
elif not self.is_bytes and t == "N":
|
|
1065
|
-
value =
|
|
1179
|
+
value = _util.unicode_char(self.get_named_unicode(i))
|
|
1066
1180
|
elif t == "x":
|
|
1067
1181
|
value = chr(int(self.get_byte(i), 16))
|
|
1068
1182
|
else:
|
|
@@ -1180,7 +1294,7 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
1180
1294
|
count = len(self.span_stack)
|
|
1181
1295
|
self.end_found = False
|
|
1182
1296
|
try:
|
|
1183
|
-
while
|
|
1297
|
+
while True:
|
|
1184
1298
|
t = next(i)
|
|
1185
1299
|
if self.use_format and t in _CURLY_BRACKETS:
|
|
1186
1300
|
self.handle_format(t, i)
|
|
@@ -1233,10 +1347,9 @@ class _ReplaceParser(Generic[AnyStr]):
|
|
|
1233
1347
|
except StopIteration:
|
|
1234
1348
|
self.result.append(t)
|
|
1235
1349
|
raise
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
self.result.append(self.convert_case(t, this_case))
|
|
1350
|
+
else:
|
|
1351
|
+
# The stack always has the case just added.
|
|
1352
|
+
self.result.append(self.convert_case(t, cast(int, self.get_single_stack())))
|
|
1240
1353
|
except StopIteration:
|
|
1241
1354
|
pass
|
|
1242
1355
|
|