dycw-utilities 0.110.1__py3-none-any.whl → 0.110.3__py3-none-any.whl
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.
- {dycw_utilities-0.110.1.dist-info → dycw_utilities-0.110.3.dist-info}/METADATA +1 -1
- {dycw_utilities-0.110.1.dist-info → dycw_utilities-0.110.3.dist-info}/RECORD +8 -8
- utilities/__init__.py +1 -1
- utilities/dataclasses.py +3 -7
- utilities/parse.py +22 -6
- utilities/text.py +171 -26
- {dycw_utilities-0.110.1.dist-info → dycw_utilities-0.110.3.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.110.1.dist-info → dycw_utilities-0.110.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=SPilOBQUT3LssunOfCSklrHeCvASfNJWhSufL04nwh8,60
|
2
2
|
utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
|
3
3
|
utilities/astor.py,sha256=xuDUkjq0-b6fhtwjhbnebzbqQZAjMSHR1IIS5uOodVg,777
|
4
4
|
utilities/asyncio.py,sha256=41oQUurWMvadFK5gFnaG21hMM0Vmfn2WS6OpC0R9mas,14757
|
@@ -11,7 +11,7 @@ utilities/contextlib.py,sha256=OOIIEa5lXKGzFAnauaul40nlQnQko6Na4ryiMJcHkIg,478
|
|
11
11
|
utilities/contextvars.py,sha256=RsSGGrbQqqZ67rOydnM7WWIsM2lIE31UHJLejnHJPWY,505
|
12
12
|
utilities/cryptography.py,sha256=HyOewI20cl3uRXsKivhIaeLVDInQdzgXZGaly7hS5dE,771
|
13
13
|
utilities/cvxpy.py,sha256=Rv1-fD-XYerosCavRF8Pohop2DBkU3AlFaGTfD8AEAA,13776
|
14
|
-
utilities/dataclasses.py,sha256=
|
14
|
+
utilities/dataclasses.py,sha256=pIRUFMD9JDrwudl16w4a242hoM4A6kCQqirUfsYRcL0,25925
|
15
15
|
utilities/datetime.py,sha256=GOs-MIEW_A49kzqa1yhIoeNeSqqPVgGO-h2AThtgTDk,37326
|
16
16
|
utilities/enum.py,sha256=HoRwVCWzsnH0vpO9ZEcAAIZLMv0Sn2vJxxA4sYMQgDs,5793
|
17
17
|
utilities/errors.py,sha256=BtSNP0JC3ik536ddPyTerLomCRJV9f6kdMe6POz0QHM,361
|
@@ -41,7 +41,7 @@ utilities/operator.py,sha256=0M2yZJ0PODH47ogFEnkGMBe_cfxwZR02T_92LZVZvHo,3715
|
|
41
41
|
utilities/optuna.py,sha256=loyJGWTzljgdJaoLhP09PT8Jz6o_pwBOwehY33lHkhw,1923
|
42
42
|
utilities/orjson.py,sha256=Wj5pzG_VdgoAy14a7Luhem-BgYrRtRFvvl_POiszRd0,36930
|
43
43
|
utilities/os.py,sha256=D_FyyT-6TtqiN9KSS7c9g1fnUtgxmyMtzAjmYLkk46A,3587
|
44
|
-
utilities/parse.py,sha256=
|
44
|
+
utilities/parse.py,sha256=RRRTu3TOTDWiO-vVjP6VIg5Z-BojN2dRwPHQGxDQCGg,17758
|
45
45
|
utilities/pathlib.py,sha256=31WPMXdLIyXgYOMMl_HOI2wlo66MGSE-cgeelk-Lias,1410
|
46
46
|
utilities/period.py,sha256=ikHXsWtDLr553cfH6p9mMaiCnIAP69B7q84ckWV3HaA,10884
|
47
47
|
utilities/pickle.py,sha256=Bhvd7cZl-zQKQDFjUerqGuSKlHvnW1K2QXeU5UZibtg,657
|
@@ -73,7 +73,7 @@ utilities/streamlit.py,sha256=U9PJBaKP1IdSykKhPZhIzSPTZsmLsnwbEPZWzNhJPKk,2955
|
|
73
73
|
utilities/sys.py,sha256=h0Xr7Vj86wNalvwJVP1wj5Y0kD_VWm1vzuXZ_jw94mE,2743
|
74
74
|
utilities/tempfile.py,sha256=VqmZJAhTJ1OaVywFzk5eqROV8iJbW9XQ_QYAV0bpdRo,1384
|
75
75
|
utilities/tenacity.py,sha256=1PUvODiBVgeqIh7G5TRt5WWMSqjLYkEqP53itT97WQc,4914
|
76
|
-
utilities/text.py,sha256=
|
76
|
+
utilities/text.py,sha256=0rdjh4_qqGf_wbJyCKQDIbqrb0fQRke2pJEEr_TAWoM,10918
|
77
77
|
utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
|
78
78
|
utilities/timer.py,sha256=Rkc49KSpHuC8s7vUxGO9DU55U9I6yDKnchsQqrUCVBs,4075
|
79
79
|
utilities/traceback.py,sha256=KwHPLdEbdj0fFhXo8MBfxcvem8A-VXYDwFMNJ6f0cTM,27328
|
@@ -87,7 +87,7 @@ utilities/warnings.py,sha256=yUgjnmkCRf6QhdyAXzl7u0qQFejhQG3PrjoSwxpbHrs,1819
|
|
87
87
|
utilities/whenever.py,sha256=TjoTAJ1R27-rKXiXzdE4GzPidmYqm0W58XydDXp-QZM,17786
|
88
88
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
89
89
|
utilities/zoneinfo.py,sha256=-DQz5a0Ikw9jfSZtL0BEQkXOMC9yGn_xiJYNCLMiqEc,1989
|
90
|
-
dycw_utilities-0.110.
|
91
|
-
dycw_utilities-0.110.
|
92
|
-
dycw_utilities-0.110.
|
93
|
-
dycw_utilities-0.110.
|
90
|
+
dycw_utilities-0.110.3.dist-info/METADATA,sha256=0rjOLcXZr0om0AtgTlgw0_4Zh_Lbh8k7ic_EqfmwP5s,13004
|
91
|
+
dycw_utilities-0.110.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
92
|
+
dycw_utilities-0.110.3.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
93
|
+
dycw_utilities-0.110.3.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/dataclasses.py
CHANGED
@@ -21,15 +21,11 @@ from utilities.functions import (
|
|
21
21
|
)
|
22
22
|
from utilities.iterables import OneStrEmptyError, OneStrNonUniqueError, one_str
|
23
23
|
from utilities.operator import is_equal
|
24
|
-
from utilities.parse import
|
25
|
-
LIST_SEPARATOR,
|
26
|
-
PAIR_SEPARATOR,
|
27
|
-
ParseObjectError,
|
28
|
-
parse_object,
|
29
|
-
serialize_object,
|
30
|
-
)
|
24
|
+
from utilities.parse import ParseObjectError, parse_object, serialize_object
|
31
25
|
from utilities.sentinel import Sentinel, sentinel
|
32
26
|
from utilities.text import (
|
27
|
+
LIST_SEPARATOR,
|
28
|
+
PAIR_SEPARATOR,
|
33
29
|
_SplitKeyValuePairsDuplicateKeysError,
|
34
30
|
_SplitKeyValuePairsSplitError,
|
35
31
|
join_strs,
|
utilities/parse.py
CHANGED
@@ -20,6 +20,9 @@ from utilities.math import ParseNumberError, parse_number
|
|
20
20
|
from utilities.re import ExtractGroupError, extract_group
|
21
21
|
from utilities.sentinel import ParseSentinelError, Sentinel, parse_sentinel
|
22
22
|
from utilities.text import (
|
23
|
+
BRACKETS,
|
24
|
+
LIST_SEPARATOR,
|
25
|
+
PAIR_SEPARATOR,
|
23
26
|
ParseBoolError,
|
24
27
|
ParseNoneError,
|
25
28
|
join_strs,
|
@@ -43,14 +46,10 @@ from utilities.typing import (
|
|
43
46
|
from utilities.version import ParseVersionError, Version, parse_version
|
44
47
|
|
45
48
|
if TYPE_CHECKING:
|
46
|
-
from collections.abc import Mapping, Sequence
|
49
|
+
from collections.abc import Iterable, Mapping, Sequence
|
47
50
|
from collections.abc import Set as AbstractSet
|
48
51
|
|
49
52
|
|
50
|
-
LIST_SEPARATOR = ","
|
51
|
-
PAIR_SEPARATOR = "="
|
52
|
-
|
53
|
-
|
54
53
|
def parse_object(
|
55
54
|
type_: Any,
|
56
55
|
text: str,
|
@@ -58,6 +57,7 @@ def parse_object(
|
|
58
57
|
*,
|
59
58
|
list_separator: str = LIST_SEPARATOR,
|
60
59
|
pair_separator: str = PAIR_SEPARATOR,
|
60
|
+
brackets: Iterable[tuple[str, str]] | None = BRACKETS,
|
61
61
|
head: bool = False,
|
62
62
|
case_sensitive: bool = False,
|
63
63
|
extra: ParseObjectExtra | None = None,
|
@@ -78,6 +78,7 @@ def parse_object(
|
|
78
78
|
text,
|
79
79
|
list_separator=list_separator,
|
80
80
|
pair_separator=pair_separator,
|
81
|
+
brackets=brackets,
|
81
82
|
head=head,
|
82
83
|
case_sensitive=case_sensitive,
|
83
84
|
extra=extra,
|
@@ -89,6 +90,7 @@ def parse_object(
|
|
89
90
|
text,
|
90
91
|
list_separator=list_separator,
|
91
92
|
pair_separator=pair_separator,
|
93
|
+
brackets=brackets,
|
92
94
|
head=head,
|
93
95
|
case_sensitive=case_sensitive,
|
94
96
|
extra=extra,
|
@@ -100,6 +102,7 @@ def parse_object(
|
|
100
102
|
text,
|
101
103
|
list_separator=list_separator,
|
102
104
|
pair_separator=pair_separator,
|
105
|
+
brackets=brackets,
|
103
106
|
head=head,
|
104
107
|
case_sensitive=case_sensitive,
|
105
108
|
extra=extra,
|
@@ -116,6 +119,7 @@ def parse_object(
|
|
116
119
|
text,
|
117
120
|
list_separator=list_separator,
|
118
121
|
pair_separator=pair_separator,
|
122
|
+
brackets=brackets,
|
119
123
|
head=head,
|
120
124
|
case_sensitive=case_sensitive,
|
121
125
|
extra=extra,
|
@@ -128,6 +132,7 @@ def parse_object(
|
|
128
132
|
text,
|
129
133
|
list_separator=list_separator,
|
130
134
|
pair_separator=pair_separator,
|
135
|
+
brackets=brackets,
|
131
136
|
head=head,
|
132
137
|
case_sensitive=case_sensitive,
|
133
138
|
extra=extra,
|
@@ -138,6 +143,7 @@ def parse_object(
|
|
138
143
|
text,
|
139
144
|
list_separator=list_separator,
|
140
145
|
pair_separator=pair_separator,
|
146
|
+
brackets=brackets,
|
141
147
|
head=head,
|
142
148
|
case_sensitive=case_sensitive,
|
143
149
|
extra=extra,
|
@@ -244,6 +250,7 @@ def _parse_object_dict_type(
|
|
244
250
|
*,
|
245
251
|
list_separator: str = LIST_SEPARATOR,
|
246
252
|
pair_separator: str = PAIR_SEPARATOR,
|
253
|
+
brackets: Iterable[tuple[str, str]] | None = BRACKETS,
|
247
254
|
head: bool = False,
|
248
255
|
case_sensitive: bool = False,
|
249
256
|
extra: ParseObjectExtra | None = None,
|
@@ -257,6 +264,7 @@ def _parse_object_dict_type(
|
|
257
264
|
inner_text,
|
258
265
|
list_separator=list_separator,
|
259
266
|
pair_separator=pair_separator,
|
267
|
+
brackets=brackets,
|
260
268
|
mapping=True,
|
261
269
|
)
|
262
270
|
keys = (
|
@@ -265,6 +273,7 @@ def _parse_object_dict_type(
|
|
265
273
|
k,
|
266
274
|
list_separator=list_separator,
|
267
275
|
pair_separator=pair_separator,
|
276
|
+
brackets=brackets,
|
268
277
|
head=head,
|
269
278
|
case_sensitive=case_sensitive,
|
270
279
|
extra=extra,
|
@@ -277,6 +286,7 @@ def _parse_object_dict_type(
|
|
277
286
|
v,
|
278
287
|
list_separator=list_separator,
|
279
288
|
pair_separator=pair_separator,
|
289
|
+
brackets=brackets,
|
280
290
|
head=head,
|
281
291
|
case_sensitive=case_sensitive,
|
282
292
|
extra=extra,
|
@@ -296,6 +306,7 @@ def _parse_object_list_type(
|
|
296
306
|
*,
|
297
307
|
list_separator: str = LIST_SEPARATOR,
|
298
308
|
pair_separator: str = PAIR_SEPARATOR,
|
309
|
+
brackets: Iterable[tuple[str, str]] | None = BRACKETS,
|
299
310
|
head: bool = False,
|
300
311
|
case_sensitive: bool = False,
|
301
312
|
extra: ParseObjectExtra | None = None,
|
@@ -313,6 +324,7 @@ def _parse_object_list_type(
|
|
313
324
|
t,
|
314
325
|
list_separator=list_separator,
|
315
326
|
pair_separator=pair_separator,
|
327
|
+
brackets=brackets,
|
316
328
|
head=head,
|
317
329
|
case_sensitive=case_sensitive,
|
318
330
|
extra=extra,
|
@@ -330,6 +342,7 @@ def _parse_object_set_type(
|
|
330
342
|
*,
|
331
343
|
list_separator: str = LIST_SEPARATOR,
|
332
344
|
pair_separator: str = PAIR_SEPARATOR,
|
345
|
+
brackets: Iterable[tuple[str, str]] | None = BRACKETS,
|
333
346
|
head: bool = False,
|
334
347
|
case_sensitive: bool = False,
|
335
348
|
extra: ParseObjectExtra | None = None,
|
@@ -347,6 +360,7 @@ def _parse_object_set_type(
|
|
347
360
|
t,
|
348
361
|
list_separator=list_separator,
|
349
362
|
pair_separator=pair_separator,
|
363
|
+
brackets=brackets,
|
350
364
|
head=head,
|
351
365
|
case_sensitive=case_sensitive,
|
352
366
|
extra=extra,
|
@@ -389,6 +403,7 @@ def _parse_object_tuple_type(
|
|
389
403
|
*,
|
390
404
|
list_separator: str = LIST_SEPARATOR,
|
391
405
|
pair_separator: str = PAIR_SEPARATOR,
|
406
|
+
brackets: Iterable[tuple[str, str]] | None = BRACKETS,
|
392
407
|
head: bool = False,
|
393
408
|
case_sensitive: bool = False,
|
394
409
|
extra: ParseObjectExtra | None = None,
|
@@ -408,6 +423,7 @@ def _parse_object_tuple_type(
|
|
408
423
|
text,
|
409
424
|
list_separator=list_separator,
|
410
425
|
pair_separator=pair_separator,
|
426
|
+
brackets=brackets,
|
411
427
|
head=head,
|
412
428
|
case_sensitive=case_sensitive,
|
413
429
|
extra=extra,
|
@@ -569,4 +585,4 @@ def _serialize_object_tuple(
|
|
569
585
|
return f"({joined})"
|
570
586
|
|
571
587
|
|
572
|
-
__all__ = ["
|
588
|
+
__all__ = ["parse_object", "serialize_object"]
|
utilities/text.py
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import re
|
4
|
+
from collections import deque
|
4
5
|
from dataclasses import dataclass
|
5
|
-
from
|
6
|
+
from itertools import chain
|
7
|
+
from re import IGNORECASE, Match, escape, search
|
6
8
|
from textwrap import dedent
|
7
9
|
from typing import TYPE_CHECKING, Any, Literal, overload, override
|
8
10
|
|
9
|
-
from utilities.iterables import CheckDuplicatesError, check_duplicates
|
11
|
+
from utilities.iterables import CheckDuplicatesError, check_duplicates, transpose
|
10
12
|
from utilities.reprlib import get_repr
|
11
13
|
|
12
14
|
if TYPE_CHECKING:
|
@@ -15,6 +17,12 @@ if TYPE_CHECKING:
|
|
15
17
|
from utilities.types import StrStrMapping
|
16
18
|
|
17
19
|
|
20
|
+
DEFAULT_SEPARATOR = ","
|
21
|
+
|
22
|
+
|
23
|
+
##
|
24
|
+
|
25
|
+
|
18
26
|
def parse_bool(text: str, /) -> bool:
|
19
27
|
"""Parse text into a boolean value."""
|
20
28
|
if text == "0" or search("false", text, flags=IGNORECASE):
|
@@ -86,13 +94,19 @@ def _snake_case_title(match: Match[str], /) -> str:
|
|
86
94
|
##
|
87
95
|
|
88
96
|
|
97
|
+
LIST_SEPARATOR = DEFAULT_SEPARATOR
|
98
|
+
PAIR_SEPARATOR = "="
|
99
|
+
BRACKETS = [("(", ")"), ("[", "]"), ("{", "}")]
|
100
|
+
|
101
|
+
|
89
102
|
@overload
|
90
103
|
def split_key_value_pairs(
|
91
104
|
text: str,
|
92
105
|
/,
|
93
106
|
*,
|
94
|
-
list_separator: str =
|
95
|
-
pair_separator: str =
|
107
|
+
list_separator: str = DEFAULT_SEPARATOR,
|
108
|
+
pair_separator: str = PAIR_SEPARATOR,
|
109
|
+
brackets: Iterable[tuple[str, str]] | None = BRACKETS,
|
96
110
|
mapping: Literal[True],
|
97
111
|
) -> StrStrMapping: ...
|
98
112
|
@overload
|
@@ -100,8 +114,9 @@ def split_key_value_pairs(
|
|
100
114
|
text: str,
|
101
115
|
/,
|
102
116
|
*,
|
103
|
-
list_separator: str =
|
104
|
-
pair_separator: str =
|
117
|
+
list_separator: str = DEFAULT_SEPARATOR,
|
118
|
+
pair_separator: str = PAIR_SEPARATOR,
|
119
|
+
brackets: Iterable[tuple[str, str]] | None = BRACKETS,
|
105
120
|
mapping: Literal[False] = False,
|
106
121
|
) -> Sequence[tuple[str, str]]: ...
|
107
122
|
@overload
|
@@ -109,24 +124,27 @@ def split_key_value_pairs(
|
|
109
124
|
text: str,
|
110
125
|
/,
|
111
126
|
*,
|
112
|
-
list_separator: str =
|
113
|
-
pair_separator: str =
|
127
|
+
list_separator: str = DEFAULT_SEPARATOR,
|
128
|
+
pair_separator: str = PAIR_SEPARATOR,
|
129
|
+
brackets: Iterable[tuple[str, str]] | None = BRACKETS,
|
114
130
|
mapping: bool = False,
|
115
131
|
) -> Sequence[tuple[str, str]] | StrStrMapping: ...
|
116
132
|
def split_key_value_pairs(
|
117
133
|
text: str,
|
118
134
|
/,
|
119
135
|
*,
|
120
|
-
list_separator: str =
|
121
|
-
pair_separator: str =
|
136
|
+
list_separator: str = DEFAULT_SEPARATOR,
|
137
|
+
pair_separator: str = PAIR_SEPARATOR,
|
138
|
+
brackets: Iterable[tuple[str, str]] | None = BRACKETS,
|
122
139
|
mapping: bool = False,
|
123
140
|
) -> Sequence[tuple[str, str]] | StrStrMapping:
|
124
141
|
"""Split a string into key-value pairs."""
|
125
142
|
try:
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
143
|
+
texts = split_str(text, separator=list_separator, brackets=brackets)
|
144
|
+
except SplitStrError as error:
|
145
|
+
raise _SplitKeyValuePairsSplitError(text=text, inner=error.text) from None
|
146
|
+
try:
|
147
|
+
pairs = [split_str(text_i, separator=pair_separator, n=2) for text_i in texts]
|
130
148
|
except SplitStrError as error:
|
131
149
|
raise _SplitKeyValuePairsSplitError(text=text, inner=error.text) from None
|
132
150
|
if not mapping:
|
@@ -151,7 +169,7 @@ class _SplitKeyValuePairsSplitError(SplitKeyValuePairsError):
|
|
151
169
|
|
152
170
|
@override
|
153
171
|
def __str__(self) -> str:
|
154
|
-
return f"Unable to split {self.text!r} into key-value pairs
|
172
|
+
return f"Unable to split {self.text!r} into key-value pairs"
|
155
173
|
|
156
174
|
|
157
175
|
@dataclass(kw_only=True, slots=True)
|
@@ -167,47 +185,138 @@ class _SplitKeyValuePairsDuplicateKeysError(SplitKeyValuePairsError):
|
|
167
185
|
|
168
186
|
|
169
187
|
@overload
|
170
|
-
def split_str(
|
188
|
+
def split_str(
|
189
|
+
text: str,
|
190
|
+
/,
|
191
|
+
*,
|
192
|
+
separator: str = DEFAULT_SEPARATOR,
|
193
|
+
brackets: Iterable[tuple[str, str]] | None = None,
|
194
|
+
n: Literal[1],
|
195
|
+
) -> tuple[str]: ...
|
171
196
|
@overload
|
172
197
|
def split_str(
|
173
|
-
text: str,
|
198
|
+
text: str,
|
199
|
+
/,
|
200
|
+
*,
|
201
|
+
separator: str = DEFAULT_SEPARATOR,
|
202
|
+
brackets: Iterable[tuple[str, str]] | None = None,
|
203
|
+
n: Literal[2],
|
174
204
|
) -> tuple[str, str]: ...
|
175
205
|
@overload
|
176
206
|
def split_str(
|
177
|
-
text: str,
|
207
|
+
text: str,
|
208
|
+
/,
|
209
|
+
*,
|
210
|
+
separator: str = DEFAULT_SEPARATOR,
|
211
|
+
brackets: Iterable[tuple[str, str]] | None = None,
|
212
|
+
n: Literal[3],
|
178
213
|
) -> tuple[str, str, str]: ...
|
179
214
|
@overload
|
180
215
|
def split_str(
|
181
|
-
text: str,
|
216
|
+
text: str,
|
217
|
+
/,
|
218
|
+
*,
|
219
|
+
separator: str = DEFAULT_SEPARATOR,
|
220
|
+
brackets: Iterable[tuple[str, str]] | None = None,
|
221
|
+
n: Literal[4],
|
182
222
|
) -> tuple[str, str, str, str]: ...
|
183
223
|
@overload
|
184
224
|
def split_str(
|
185
|
-
text: str,
|
225
|
+
text: str,
|
226
|
+
/,
|
227
|
+
*,
|
228
|
+
separator: str = DEFAULT_SEPARATOR,
|
229
|
+
brackets: Iterable[tuple[str, str]] | None = None,
|
230
|
+
n: Literal[5],
|
186
231
|
) -> tuple[str, str, str, str, str]: ...
|
187
232
|
@overload
|
188
233
|
def split_str(
|
189
|
-
text: str,
|
234
|
+
text: str,
|
235
|
+
/,
|
236
|
+
*,
|
237
|
+
separator: str = DEFAULT_SEPARATOR,
|
238
|
+
brackets: Iterable[tuple[str, str]] | None = None,
|
239
|
+
n: int | None = None,
|
190
240
|
) -> Sequence[str]: ...
|
191
241
|
def split_str(
|
192
|
-
text: str,
|
242
|
+
text: str,
|
243
|
+
/,
|
244
|
+
*,
|
245
|
+
separator: str = DEFAULT_SEPARATOR,
|
246
|
+
brackets: Iterable[tuple[str, str]] | None = None,
|
247
|
+
n: int | None = None,
|
193
248
|
) -> Sequence[str]:
|
194
249
|
"""Split a string, with a special provision for the empty string."""
|
195
250
|
if text == "":
|
196
251
|
texts = []
|
197
252
|
elif text == _escape_separator(separator=separator):
|
198
253
|
texts = [""]
|
199
|
-
|
254
|
+
elif brackets is None:
|
200
255
|
texts = text.split(separator)
|
256
|
+
else:
|
257
|
+
texts = _split_str_brackets(text, brackets, separator=separator)
|
201
258
|
if n is None:
|
202
259
|
return texts
|
203
260
|
if len(texts) != n:
|
204
|
-
raise
|
261
|
+
raise _SplitStrCountError(text=text, n=n, texts=texts)
|
205
262
|
return tuple(texts)
|
206
263
|
|
207
264
|
|
265
|
+
def _split_str_brackets(
|
266
|
+
text: str,
|
267
|
+
brackets: Iterable[tuple[str, str]],
|
268
|
+
/,
|
269
|
+
*,
|
270
|
+
separator: str = DEFAULT_SEPARATOR,
|
271
|
+
) -> Sequence[str]:
|
272
|
+
brackets = list(brackets)
|
273
|
+
opens, closes = transpose(brackets)
|
274
|
+
close_to_open = {close: open_ for open_, close in brackets}
|
275
|
+
|
276
|
+
escapes = map(escape, chain(chain.from_iterable(brackets), [separator]))
|
277
|
+
pattern = re.compile("|".join(escapes))
|
278
|
+
|
279
|
+
results: Sequence[str] = []
|
280
|
+
stack: deque[tuple[str, int]] = deque()
|
281
|
+
last = 0
|
282
|
+
|
283
|
+
for match in pattern.finditer(text):
|
284
|
+
token, position = match.group(), match.start()
|
285
|
+
if token in opens:
|
286
|
+
stack.append((token, position))
|
287
|
+
elif token in closes:
|
288
|
+
if len(stack) == 0:
|
289
|
+
raise _SplitStrClosingBracketUnmatchedError(
|
290
|
+
text=text, token=token, position=position
|
291
|
+
)
|
292
|
+
open_token, open_position = stack.pop()
|
293
|
+
if open_token != close_to_open[token]:
|
294
|
+
raise _SplitStrClosingBracketMismatchedError(
|
295
|
+
text=text,
|
296
|
+
opening_token=open_token,
|
297
|
+
opening_position=open_position,
|
298
|
+
closing_token=token,
|
299
|
+
closing_position=position,
|
300
|
+
)
|
301
|
+
elif (token == separator) and (len(stack) == 0):
|
302
|
+
results.append(text[last:position].strip())
|
303
|
+
last = position + 1
|
304
|
+
results.append(text[last:].strip())
|
305
|
+
if len(stack) >= 1:
|
306
|
+
token, position = stack.pop()
|
307
|
+
raise _SplitStrOpeningBracketUnmatchedError(
|
308
|
+
text=text, token=token, position=position
|
309
|
+
)
|
310
|
+
return results
|
311
|
+
|
312
|
+
|
208
313
|
@dataclass(kw_only=True, slots=True)
|
209
314
|
class SplitStrError(Exception):
|
210
315
|
text: str
|
316
|
+
|
317
|
+
|
318
|
+
@dataclass(kw_only=True, slots=True)
|
319
|
+
class _SplitStrCountError(SplitStrError):
|
211
320
|
n: int
|
212
321
|
texts: Sequence[str]
|
213
322
|
|
@@ -216,8 +325,40 @@ class SplitStrError(Exception):
|
|
216
325
|
return f"Unable to split {self.text!r} into {self.n} part(s); got {len(self.texts)}"
|
217
326
|
|
218
327
|
|
328
|
+
@dataclass(kw_only=True, slots=True)
|
329
|
+
class _SplitStrClosingBracketMismatchedError(SplitStrError):
|
330
|
+
opening_token: str
|
331
|
+
opening_position: int
|
332
|
+
closing_token: str
|
333
|
+
closing_position: int
|
334
|
+
|
335
|
+
@override
|
336
|
+
def __str__(self) -> str:
|
337
|
+
return f"Unable to split {self.text!r}; got mismatched {self.opening_token!r} at position {self.opening_position} and {self.closing_token!r} at position {self.closing_position}"
|
338
|
+
|
339
|
+
|
340
|
+
@dataclass(kw_only=True, slots=True)
|
341
|
+
class _SplitStrClosingBracketUnmatchedError(SplitStrError):
|
342
|
+
token: str
|
343
|
+
position: int
|
344
|
+
|
345
|
+
@override
|
346
|
+
def __str__(self) -> str:
|
347
|
+
return f"Unable to split {self.text!r}; got unmatched {self.token!r} at position {self.position}"
|
348
|
+
|
349
|
+
|
350
|
+
@dataclass(kw_only=True, slots=True)
|
351
|
+
class _SplitStrOpeningBracketUnmatchedError(SplitStrError):
|
352
|
+
token: str
|
353
|
+
position: int
|
354
|
+
|
355
|
+
@override
|
356
|
+
def __str__(self) -> str:
|
357
|
+
return f"Unable to split {self.text!r}; got unmatched {self.token!r} at position {self.position}"
|
358
|
+
|
359
|
+
|
219
360
|
def join_strs(
|
220
|
-
texts: Iterable[str], /, *, sort: bool = False, separator: str =
|
361
|
+
texts: Iterable[str], /, *, sort: bool = False, separator: str = DEFAULT_SEPARATOR
|
221
362
|
) -> str:
|
222
363
|
"""Join a collection of strings, with a special provision for the empty list."""
|
223
364
|
texts = list(texts)
|
@@ -230,7 +371,7 @@ def join_strs(
|
|
230
371
|
return separator.join(texts)
|
231
372
|
|
232
373
|
|
233
|
-
def _escape_separator(*, separator: str =
|
374
|
+
def _escape_separator(*, separator: str = DEFAULT_SEPARATOR) -> str:
|
234
375
|
return f"\\{separator}"
|
235
376
|
|
236
377
|
|
@@ -252,6 +393,10 @@ def strip_and_dedent(text: str, /, *, trailing: bool = False) -> str:
|
|
252
393
|
|
253
394
|
|
254
395
|
__all__ = [
|
396
|
+
"BRACKETS",
|
397
|
+
"DEFAULT_SEPARATOR",
|
398
|
+
"LIST_SEPARATOR",
|
399
|
+
"PAIR_SEPARATOR",
|
255
400
|
"ParseBoolError",
|
256
401
|
"ParseNoneError",
|
257
402
|
"SplitKeyValuePairsError",
|
File without changes
|
File without changes
|