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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.110.1
3
+ Version: 0.110.3
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=hplCmGbNVfruqTvCNUXlSrWUj9O7kQcWSuSnZNUbp0w,60
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=e2YuCj1DF4eecFFxsu20MknghXJ4SNkWCmbIAOXXr18,25942
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=wxqh4YwBfQ7nm249-F_6uqiLo1js9_xji9AVvUxZ5nI,17091
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=Ax_n-nY80_onWxag9M0PkmbaAqwyut9AEA9tEMd5lBs,6694
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.1.dist-info/METADATA,sha256=aP9GGGR2pc47dx2nhMCDkvuUbKPEEZyaGZI6mZtIibI,13004
91
- dycw_utilities-0.110.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
- dycw_utilities-0.110.1.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
93
- dycw_utilities-0.110.1.dist-info/RECORD,,
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
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.110.1"
3
+ __version__ = "0.110.3"
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__ = ["LIST_SEPARATOR", "PAIR_SEPARATOR", "parse_object", "serialize_object"]
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 re import IGNORECASE, Match, search
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
- pairs = [
127
- split_str(text_i, separator=pair_separator, n=2)
128
- for text_i in split_str(text, separator=list_separator)
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; got {self.inner!r}"
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(text: str, /, *, separator: str = ",", n: Literal[1]) -> tuple[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, /, *, separator: str = ",", n: Literal[2]
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, /, *, separator: str = ",", n: Literal[3]
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, /, *, separator: str = ",", n: Literal[4]
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, /, *, separator: str = ",", n: Literal[5]
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, /, *, separator: str = ",", n: int | None = None
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, /, *, separator: str = ",", n: int | None = None
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
- else:
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 SplitStrError(text=text, n=n, texts=texts)
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 = ",") -> 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",