pathseq 0.1.0__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.
- pathseq/__init__.py +44 -0
- pathseq/_arithmetic_sequence.py +154 -0
- pathseq/_ast/__init__.py +22 -0
- pathseq/_ast/_base.py +236 -0
- pathseq/_ast/_formatter.py +38 -0
- pathseq/_ast/_loose_type.py +219 -0
- pathseq/_ast/_type.py +64 -0
- pathseq/_base.py +510 -0
- pathseq/_decimal_range.py +172 -0
- pathseq/_error.py +72 -0
- pathseq/_file_num_seq.py +232 -0
- pathseq/_formatter.py +85 -0
- pathseq/_from_disk.py +73 -0
- pathseq/_loose_path_sequence.py +14 -0
- pathseq/_loose_pure_path_sequence.py +126 -0
- pathseq/_parse_file_num_seq.py +77 -0
- pathseq/_parse_loose_path_sequence.py +525 -0
- pathseq/_parse_path_sequence.py +327 -0
- pathseq/_path_sequence.py +14 -0
- pathseq/_pure_path_sequence.py +58 -0
- pathseq/py.typed +0 -0
- pathseq-0.1.0.dist-info/METADATA +134 -0
- pathseq-0.1.0.dist-info/RECORD +24 -0
- pathseq-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import collections
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from decimal import Decimal
|
|
6
|
+
import enum
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
from statemachine import StateMachine, State
|
|
10
|
+
|
|
11
|
+
from ._error import NotASequenceError, ParseError
|
|
12
|
+
from ._file_num_seq import FileNumSequence
|
|
13
|
+
from ._ast import (
|
|
14
|
+
PaddedRange,
|
|
15
|
+
ParsedLooseSequence,
|
|
16
|
+
Ranges,
|
|
17
|
+
RangesEndName,
|
|
18
|
+
RangesInName,
|
|
19
|
+
RangesStartName,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
_POSTFIX_SEPARATORS = {"_"}
|
|
23
|
+
_PREFIX_SEPARATORS = _POSTFIX_SEPARATORS | {"."}
|
|
24
|
+
RANGE_RE = re.compile(
|
|
25
|
+
r"""
|
|
26
|
+
-?\d+(?:\.\d+)?
|
|
27
|
+
(?: # optional range
|
|
28
|
+
- # range delimiter
|
|
29
|
+
-?\d+(?:\.\d+)? # end frame
|
|
30
|
+
(?: # optional stepping
|
|
31
|
+
x # step type
|
|
32
|
+
\d+(?:\.\d+)? # step value
|
|
33
|
+
)?
|
|
34
|
+
)?
|
|
35
|
+
""",
|
|
36
|
+
re.VERBOSE,
|
|
37
|
+
)
|
|
38
|
+
PAD_FORMAT_RE = re.compile(
|
|
39
|
+
r"""
|
|
40
|
+
(?:
|
|
41
|
+
\#+(?:\.\#+)?
|
|
42
|
+
| <UDIM>
|
|
43
|
+
| <UVTILE>
|
|
44
|
+
)
|
|
45
|
+
""",
|
|
46
|
+
flags=re.VERBOSE,
|
|
47
|
+
)
|
|
48
|
+
RANGES_RE = re.compile(
|
|
49
|
+
f"""
|
|
50
|
+
(
|
|
51
|
+
(?:{RANGE_RE.pattern} (?:, {RANGE_RE.pattern})*)?
|
|
52
|
+
{PAD_FORMAT_RE.pattern}
|
|
53
|
+
)
|
|
54
|
+
""",
|
|
55
|
+
flags=RANGE_RE.flags | PAD_FORMAT_RE.flags | re.VERBOSE,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TokenType(enum.Enum):
|
|
60
|
+
RANGE = enum.auto()
|
|
61
|
+
INTER_RANGE = enum.auto()
|
|
62
|
+
STEM = enum.auto()
|
|
63
|
+
PREFIX = enum.auto()
|
|
64
|
+
POSTFIX = enum.auto()
|
|
65
|
+
SUFFIXES = enum.auto()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class Token:
|
|
70
|
+
type: TokenType
|
|
71
|
+
value: str
|
|
72
|
+
column: int
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def end_column(self) -> int:
|
|
76
|
+
return self.column + len(self.value)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _tokenise(seq: str) -> list[Token]:
|
|
80
|
+
raw_tokens = re.split(RANGES_RE, seq)
|
|
81
|
+
|
|
82
|
+
starts_with_range = not bool(raw_tokens[0])
|
|
83
|
+
ends_with_range = not bool(raw_tokens[-1])
|
|
84
|
+
if ends_with_range:
|
|
85
|
+
raw_tokens.pop()
|
|
86
|
+
|
|
87
|
+
column = 0
|
|
88
|
+
tokens = []
|
|
89
|
+
for i, raw_token in enumerate(raw_tokens):
|
|
90
|
+
if i == 0:
|
|
91
|
+
if not raw_token:
|
|
92
|
+
pass
|
|
93
|
+
elif not ends_with_range:
|
|
94
|
+
separator = None
|
|
95
|
+
if raw_token != "." and any(
|
|
96
|
+
raw_token.endswith(sep) for sep in _PREFIX_SEPARATORS
|
|
97
|
+
):
|
|
98
|
+
separator = Token(
|
|
99
|
+
TokenType.PREFIX,
|
|
100
|
+
raw_token[-1],
|
|
101
|
+
column + len(raw_token) - 1,
|
|
102
|
+
)
|
|
103
|
+
raw_token = raw_token[:-1]
|
|
104
|
+
|
|
105
|
+
token = Token(
|
|
106
|
+
TokenType.STEM,
|
|
107
|
+
raw_token,
|
|
108
|
+
column,
|
|
109
|
+
)
|
|
110
|
+
tokens.append(token)
|
|
111
|
+
|
|
112
|
+
if separator:
|
|
113
|
+
tokens.append(separator)
|
|
114
|
+
else:
|
|
115
|
+
separator = None
|
|
116
|
+
if any(raw_token.endswith(sep) for sep in _PREFIX_SEPARATORS):
|
|
117
|
+
separator = Token(
|
|
118
|
+
TokenType.PREFIX,
|
|
119
|
+
raw_token[-1],
|
|
120
|
+
column + len(raw_token) - 1,
|
|
121
|
+
)
|
|
122
|
+
raw_token = raw_token[:-1]
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
suffix_dot = 0
|
|
126
|
+
if raw_token.startswith("."):
|
|
127
|
+
suffix_dot = 1
|
|
128
|
+
suffix_i = raw_token.index(".", suffix_dot)
|
|
129
|
+
except ValueError:
|
|
130
|
+
suffix_i = len(raw_token)
|
|
131
|
+
|
|
132
|
+
token = Token(
|
|
133
|
+
TokenType.STEM,
|
|
134
|
+
raw_token[:suffix_i],
|
|
135
|
+
column,
|
|
136
|
+
)
|
|
137
|
+
tokens.append(token)
|
|
138
|
+
|
|
139
|
+
if raw_token[suffix_i:]:
|
|
140
|
+
token = Token(
|
|
141
|
+
TokenType.SUFFIXES,
|
|
142
|
+
raw_token[suffix_i:],
|
|
143
|
+
column + suffix_i,
|
|
144
|
+
)
|
|
145
|
+
tokens.append(token)
|
|
146
|
+
|
|
147
|
+
if separator:
|
|
148
|
+
tokens.append(separator)
|
|
149
|
+
elif i % 2 == 1:
|
|
150
|
+
token = Token(
|
|
151
|
+
TokenType.RANGE,
|
|
152
|
+
raw_token,
|
|
153
|
+
column,
|
|
154
|
+
)
|
|
155
|
+
tokens.append(token)
|
|
156
|
+
elif i >= 2 and i != len(raw_tokens) - 1:
|
|
157
|
+
token = Token(
|
|
158
|
+
TokenType.INTER_RANGE,
|
|
159
|
+
raw_token,
|
|
160
|
+
column,
|
|
161
|
+
)
|
|
162
|
+
tokens.append(token)
|
|
163
|
+
elif i == len(raw_tokens) - 1:
|
|
164
|
+
if raw_token.startswith(".") and not raw_token.endswith("."):
|
|
165
|
+
token = Token(
|
|
166
|
+
TokenType.SUFFIXES,
|
|
167
|
+
raw_token,
|
|
168
|
+
column,
|
|
169
|
+
)
|
|
170
|
+
tokens.append(token)
|
|
171
|
+
elif starts_with_range:
|
|
172
|
+
if any(raw_token.startswith(sep) for sep in _POSTFIX_SEPARATORS):
|
|
173
|
+
token = Token(
|
|
174
|
+
TokenType.POSTFIX,
|
|
175
|
+
raw_token[0],
|
|
176
|
+
column,
|
|
177
|
+
)
|
|
178
|
+
tokens.append(token)
|
|
179
|
+
raw_token = raw_token[1:]
|
|
180
|
+
column += 1
|
|
181
|
+
|
|
182
|
+
if raw_token.endswith("."):
|
|
183
|
+
suffix_i = len(raw_token)
|
|
184
|
+
elif raw_token.startswith("."):
|
|
185
|
+
suffix_i = 0
|
|
186
|
+
else:
|
|
187
|
+
try:
|
|
188
|
+
suffix_i = raw_token.index(".")
|
|
189
|
+
except ValueError:
|
|
190
|
+
suffix_i = len(raw_token)
|
|
191
|
+
|
|
192
|
+
if raw_token[:suffix_i]:
|
|
193
|
+
token = Token(
|
|
194
|
+
TokenType.STEM,
|
|
195
|
+
raw_token[:suffix_i],
|
|
196
|
+
column,
|
|
197
|
+
)
|
|
198
|
+
tokens.append(token)
|
|
199
|
+
|
|
200
|
+
if raw_token[suffix_i:]:
|
|
201
|
+
token = Token(
|
|
202
|
+
TokenType.SUFFIXES,
|
|
203
|
+
raw_token[suffix_i:],
|
|
204
|
+
column + suffix_i,
|
|
205
|
+
)
|
|
206
|
+
tokens.append(token)
|
|
207
|
+
else:
|
|
208
|
+
if raw_token.endswith("."):
|
|
209
|
+
suffix_i = len(raw_token)
|
|
210
|
+
elif raw_token.startswith("."):
|
|
211
|
+
suffix_i = 0
|
|
212
|
+
else:
|
|
213
|
+
try:
|
|
214
|
+
suffix_i = raw_token.index(".")
|
|
215
|
+
except ValueError:
|
|
216
|
+
suffix_i = len(raw_token)
|
|
217
|
+
|
|
218
|
+
if raw_token[:suffix_i]:
|
|
219
|
+
token = Token(
|
|
220
|
+
TokenType.POSTFIX,
|
|
221
|
+
raw_token[:suffix_i],
|
|
222
|
+
column,
|
|
223
|
+
)
|
|
224
|
+
tokens.append(token)
|
|
225
|
+
|
|
226
|
+
if raw_token[suffix_i:]:
|
|
227
|
+
token = Token(
|
|
228
|
+
TokenType.SUFFIXES,
|
|
229
|
+
raw_token[suffix_i:],
|
|
230
|
+
column + suffix_i,
|
|
231
|
+
)
|
|
232
|
+
tokens.append(token)
|
|
233
|
+
|
|
234
|
+
column += len(raw_token)
|
|
235
|
+
|
|
236
|
+
assert all(isinstance(token, Token) for token in tokens)
|
|
237
|
+
if not any(token.type == TokenType.RANGE for token in tokens):
|
|
238
|
+
raise NotASequenceError(seq)
|
|
239
|
+
|
|
240
|
+
if __debug__:
|
|
241
|
+
type_counts = collections.Counter(token.type for token in tokens)
|
|
242
|
+
assert type_counts[TokenType.STEM] <= 1
|
|
243
|
+
num_pre_seps = type_counts[TokenType.PREFIX]
|
|
244
|
+
assert num_pre_seps <= 1
|
|
245
|
+
num_postfixes = type_counts[TokenType.POSTFIX]
|
|
246
|
+
assert num_postfixes <= 1
|
|
247
|
+
num_ranges = type_counts[TokenType.RANGE]
|
|
248
|
+
num_inter_ranges = type_counts[TokenType.INTER_RANGE]
|
|
249
|
+
assert num_inter_ranges == num_ranges - 1
|
|
250
|
+
assert type_counts[TokenType.SUFFIXES] <= 1
|
|
251
|
+
|
|
252
|
+
return tokens
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class SeqParser(StateMachine):
|
|
256
|
+
init = State(initial=True)
|
|
257
|
+
range_starts_name = State()
|
|
258
|
+
starts_inter_range = State()
|
|
259
|
+
starts_postfix = State()
|
|
260
|
+
starts_stem = State()
|
|
261
|
+
starts_suffixes = State()
|
|
262
|
+
|
|
263
|
+
range_later = State() # stem
|
|
264
|
+
|
|
265
|
+
in_prefix = State()
|
|
266
|
+
range_in_name = State()
|
|
267
|
+
in_inter_range = State()
|
|
268
|
+
in_postfix = State()
|
|
269
|
+
in_suffixes = State()
|
|
270
|
+
|
|
271
|
+
range_ends_name = State() # suffixes
|
|
272
|
+
ends_prefix = State()
|
|
273
|
+
ends_range = State()
|
|
274
|
+
ends_inter_range = State()
|
|
275
|
+
|
|
276
|
+
parsed = State(final=True)
|
|
277
|
+
|
|
278
|
+
pump = (
|
|
279
|
+
init.to(range_starts_name, cond="is_range")
|
|
280
|
+
| init.to(range_later, validators="expect_range_or_stem")
|
|
281
|
+
| range_starts_name.to(starts_inter_range, cond="is_inter_range")
|
|
282
|
+
| starts_inter_range.to(range_starts_name, cond="expect_range")
|
|
283
|
+
| range_starts_name.to.itself(on="on_blank_inter_range", cond="is_range") # type: ignore[no-untyped-call]
|
|
284
|
+
| range_starts_name.to(in_suffixes, cond="is_suffixes", on="switch_to_in_range")
|
|
285
|
+
| range_starts_name.to(starts_postfix, cond="is_postfix")
|
|
286
|
+
| range_starts_name.to(
|
|
287
|
+
starts_stem, validators="expect_inter_or_pre_sep_or_stem"
|
|
288
|
+
)
|
|
289
|
+
| starts_postfix.to(starts_stem, validators="expect_stem")
|
|
290
|
+
| starts_stem.to(starts_suffixes, validators="expect_suffixes")
|
|
291
|
+
| range_later.to(in_prefix, cond="is_prefix")
|
|
292
|
+
| range_later.to(range_in_name, cond="is_range")
|
|
293
|
+
| in_prefix.to(range_in_name, validators="expect_range")
|
|
294
|
+
| range_in_name.to(in_inter_range, cond="is_inter_range")
|
|
295
|
+
| in_inter_range.to(range_in_name, validators="expect_range")
|
|
296
|
+
| range_in_name.to(in_postfix, cond="is_postfix")
|
|
297
|
+
| in_postfix.to(in_suffixes, validators="expect_suffixes")
|
|
298
|
+
| range_in_name.to(
|
|
299
|
+
in_suffixes, validators="expect_inter_or_post_sep_or_suffixes"
|
|
300
|
+
)
|
|
301
|
+
| range_later.to(
|
|
302
|
+
range_ends_name, validators="expect_pre_sep_or_range_or_suffixes"
|
|
303
|
+
)
|
|
304
|
+
| range_ends_name.to(ends_prefix, cond="is_prefix")
|
|
305
|
+
| range_ends_name.to(ends_range, validators="expect_pre_sep_or_range")
|
|
306
|
+
| ends_prefix.to(ends_range, validators="expect_range")
|
|
307
|
+
| ends_range.to(ends_inter_range, validators="expect_inter_range")
|
|
308
|
+
| ends_inter_range.to(ends_range, validators="expect_range")
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
finalise = (
|
|
312
|
+
range_starts_name.to(parsed)
|
|
313
|
+
| starts_stem.to(parsed)
|
|
314
|
+
| starts_suffixes.to(parsed)
|
|
315
|
+
| range_in_name.to(parsed)
|
|
316
|
+
| in_postfix.to(parsed)
|
|
317
|
+
| in_suffixes.to(parsed)
|
|
318
|
+
| ends_range.to(parsed)
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
def __init__(self, seq: str) -> None:
|
|
322
|
+
super().__init__()
|
|
323
|
+
|
|
324
|
+
self._seq = seq
|
|
325
|
+
self._range_type: type[ParsedLooseSequence] | None = None
|
|
326
|
+
self._stem = ""
|
|
327
|
+
self._prefix = ""
|
|
328
|
+
self._ranges: list[PaddedRange[int] | PaddedRange[Decimal]] = []
|
|
329
|
+
self._inter_ranges: list[str] = []
|
|
330
|
+
self._postfix = ""
|
|
331
|
+
self._suffixes: tuple[str, ...] = ()
|
|
332
|
+
|
|
333
|
+
def is_range(self, token: Token) -> bool:
|
|
334
|
+
return token.type == TokenType.RANGE
|
|
335
|
+
|
|
336
|
+
def is_inter_range(self, token: Token) -> bool:
|
|
337
|
+
return token.type == TokenType.INTER_RANGE
|
|
338
|
+
|
|
339
|
+
def is_stem(self, token: Token) -> bool:
|
|
340
|
+
return token.type == TokenType.STEM
|
|
341
|
+
|
|
342
|
+
def is_prefix(self, token: Token) -> bool:
|
|
343
|
+
return token.type == TokenType.PREFIX
|
|
344
|
+
|
|
345
|
+
def is_postfix(self, token: Token) -> bool:
|
|
346
|
+
return token.type == TokenType.POSTFIX
|
|
347
|
+
|
|
348
|
+
def is_suffixes(self, token: Token) -> bool:
|
|
349
|
+
return token.type == TokenType.SUFFIXES
|
|
350
|
+
|
|
351
|
+
def expect_range_or_stem(self, token: Token) -> bool:
|
|
352
|
+
return self._validate_stem(token, "Expected a range or a stem")
|
|
353
|
+
|
|
354
|
+
def expect_inter_or_pre_sep_or_stem(self, token: Token) -> bool:
|
|
355
|
+
return self._validate_stem(
|
|
356
|
+
token, "Expected an inter-range string, a prefix separator, or a stem"
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
def expect_inter_or_post_sep_or_suffixes(self, token: Token) -> bool:
|
|
360
|
+
return self._validate_suffixes(
|
|
361
|
+
token, "Expected an inter-range string, a postfix, or file suffixes"
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
def expect_stem(self, token: Token) -> bool:
|
|
365
|
+
return self._validate_stem(token, "Expected a stem")
|
|
366
|
+
|
|
367
|
+
def expect_suffixes(self, token: Token) -> bool:
|
|
368
|
+
return self._validate_suffixes(token, "Expected the file suffixes")
|
|
369
|
+
|
|
370
|
+
def expect_range(self, token: Token) -> bool:
|
|
371
|
+
return self._validate_range(token, "Expected the ranges")
|
|
372
|
+
|
|
373
|
+
def expect_pre_sep_or_range(self, token: Token) -> bool:
|
|
374
|
+
return self._validate_range(token, "Expected a prefix separator, or a range")
|
|
375
|
+
|
|
376
|
+
def expect_inter_range(self, token: Token) -> bool:
|
|
377
|
+
return self._validate_inter_range(token, "Expected an inter-range string")
|
|
378
|
+
|
|
379
|
+
def expect_pre_sep_or_range_or_suffixes(self, token: Token) -> bool:
|
|
380
|
+
return self._validate_suffixes(
|
|
381
|
+
token, "Expected a prefix separator, a ranges, or file suffixes"
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
def _validate_range(self, token: Token, reason: str) -> bool:
|
|
385
|
+
if token.type != TokenType.RANGE:
|
|
386
|
+
raise ParseError(self._seq, token.column, token.end_column, reason=reason)
|
|
387
|
+
return True
|
|
388
|
+
|
|
389
|
+
def _validate_inter_range(self, token: Token, reason: str) -> bool:
|
|
390
|
+
if token.type != TokenType.INTER_RANGE:
|
|
391
|
+
raise ParseError(self._seq, token.column, token.end_column, reason=reason)
|
|
392
|
+
return True
|
|
393
|
+
|
|
394
|
+
def _validate_stem(self, token: Token, reason: str) -> bool:
|
|
395
|
+
if token.type != TokenType.STEM:
|
|
396
|
+
raise ParseError(self._seq, token.column, token.end_column, reason=reason)
|
|
397
|
+
return True
|
|
398
|
+
|
|
399
|
+
def _validate_suffixes(self, token: Token, reason: str) -> bool:
|
|
400
|
+
if token.type != TokenType.SUFFIXES:
|
|
401
|
+
raise ParseError(self._seq, token.column, token.end_column, reason=reason)
|
|
402
|
+
return True
|
|
403
|
+
|
|
404
|
+
def switch_to_in_range(self) -> None:
|
|
405
|
+
self._range_type = RangesInName
|
|
406
|
+
|
|
407
|
+
def on_enter_range_starts_name(self, token: Token) -> None:
|
|
408
|
+
self._range_type = RangesStartName
|
|
409
|
+
|
|
410
|
+
self._ranges.append(self._parse_padded_range(token))
|
|
411
|
+
|
|
412
|
+
def on_enter_starts_inter_range(self, token: Token) -> None:
|
|
413
|
+
self._inter_ranges.append(token.value)
|
|
414
|
+
|
|
415
|
+
def on_enter_in_inter_range(self, token: Token) -> None:
|
|
416
|
+
self._inter_ranges.append(token.value)
|
|
417
|
+
|
|
418
|
+
def on_enter_ends_inter_range(self, token: Token) -> None:
|
|
419
|
+
self._inter_ranges.append(token.value)
|
|
420
|
+
|
|
421
|
+
def on_blank_inter_range(self) -> None:
|
|
422
|
+
self._inter_ranges.append("")
|
|
423
|
+
|
|
424
|
+
def on_enter_starts_postfix(self, token: Token) -> None:
|
|
425
|
+
self._postfix = token.value
|
|
426
|
+
|
|
427
|
+
def on_enter_starts_stem(self, token: Token) -> None:
|
|
428
|
+
self._stem = token.value
|
|
429
|
+
|
|
430
|
+
def on_enter_starts_suffixes(self, token: Token) -> None:
|
|
431
|
+
self._suffixes = self._parse_suffixes(token)
|
|
432
|
+
|
|
433
|
+
def on_enter_range_later(self, token: Token) -> None:
|
|
434
|
+
self._stem = token.value
|
|
435
|
+
|
|
436
|
+
def on_enter_in_prefix(self, token: Token) -> None:
|
|
437
|
+
self._prefix = token.value
|
|
438
|
+
|
|
439
|
+
def _parse_padded_range(
|
|
440
|
+
self, token: Token
|
|
441
|
+
) -> PaddedRange[int] | PaddedRange[Decimal]:
|
|
442
|
+
match = PAD_FORMAT_RE.search(token.value)
|
|
443
|
+
if not match:
|
|
444
|
+
raise ParseError(
|
|
445
|
+
self._seq,
|
|
446
|
+
token.column,
|
|
447
|
+
token.end_column,
|
|
448
|
+
reason=f"Tokenised an invalid range: {token.value}",
|
|
449
|
+
)
|
|
450
|
+
pad_format = match.group(0)
|
|
451
|
+
seq_str = token.value[: -len(pad_format)]
|
|
452
|
+
file_nums = FileNumSequence.from_str(seq_str)
|
|
453
|
+
return PaddedRange(file_nums, pad_format) # type: ignore[misc]
|
|
454
|
+
|
|
455
|
+
def _parse_suffixes(self, token: Token) -> tuple[str, ...]:
|
|
456
|
+
if not token.value:
|
|
457
|
+
return ()
|
|
458
|
+
|
|
459
|
+
suffixes = []
|
|
460
|
+
buffer = token.value[0]
|
|
461
|
+
for char in token.value[1:]:
|
|
462
|
+
if char == ".":
|
|
463
|
+
suffixes.append(buffer)
|
|
464
|
+
buffer = char
|
|
465
|
+
else:
|
|
466
|
+
buffer += char
|
|
467
|
+
|
|
468
|
+
suffixes.append(buffer)
|
|
469
|
+
return tuple(suffixes)
|
|
470
|
+
|
|
471
|
+
def on_enter_range_in_name(self, token: Token) -> None:
|
|
472
|
+
self._range_type = RangesInName
|
|
473
|
+
|
|
474
|
+
self._ranges.append(self._parse_padded_range(token))
|
|
475
|
+
|
|
476
|
+
def on_enter_in_postfix(self, token: Token) -> None:
|
|
477
|
+
self._postfix = token.value
|
|
478
|
+
|
|
479
|
+
def on_enter_in_suffixes(self, token: Token) -> None:
|
|
480
|
+
self._suffixes = self._parse_suffixes(token)
|
|
481
|
+
|
|
482
|
+
def on_enter_range_ends_name(self, token: Token) -> None:
|
|
483
|
+
self._range_type = RangesEndName
|
|
484
|
+
|
|
485
|
+
self._suffixes = self._parse_suffixes(token)
|
|
486
|
+
|
|
487
|
+
def on_enter_ends_range(self, token: Token) -> None:
|
|
488
|
+
self._ranges.append(self._parse_padded_range(token))
|
|
489
|
+
|
|
490
|
+
def on_enter_ends_prefix(self, token: Token) -> None:
|
|
491
|
+
self._prefix = token.value
|
|
492
|
+
|
|
493
|
+
def on_finalise(self, source: State) -> ParsedLooseSequence:
|
|
494
|
+
if source == self.range_starts_name:
|
|
495
|
+
self._range_type = RangesInName
|
|
496
|
+
|
|
497
|
+
assert self._range_type is not None, "Failed to establish a range type"
|
|
498
|
+
assert self._range_type is not RangesStartName or self._prefix == ""
|
|
499
|
+
assert self._range_type is not RangesEndName or self._postfix == ""
|
|
500
|
+
|
|
501
|
+
return self._range_type(
|
|
502
|
+
stem=self._stem,
|
|
503
|
+
prefix=self._prefix, # type: ignore[arg-type]
|
|
504
|
+
ranges=Ranges(tuple(self._ranges), tuple(self._inter_ranges)),
|
|
505
|
+
postfix=self._postfix, # type: ignore[arg-type]
|
|
506
|
+
suffixes=self._suffixes,
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
@classmethod
|
|
510
|
+
def parse(cls, seq: str) -> ParsedLooseSequence:
|
|
511
|
+
machine = cls(seq)
|
|
512
|
+
tokens = collections.deque(_tokenise(seq))
|
|
513
|
+
while True:
|
|
514
|
+
try:
|
|
515
|
+
token = tokens.popleft()
|
|
516
|
+
except IndexError:
|
|
517
|
+
break
|
|
518
|
+
else:
|
|
519
|
+
machine.pump(token)
|
|
520
|
+
|
|
521
|
+
return machine.finalise() # type: ignore[no-any-return]
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def parse_path_sequence(seq: str) -> ParsedLooseSequence:
|
|
525
|
+
return SeqParser.parse(seq)
|