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,327 @@
|
|
|
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
|
+
ParsedSequence,
|
|
16
|
+
Ranges,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
_PREFIX_SEPARATORS = {".", "_"}
|
|
20
|
+
RANGE_RE = re.compile(
|
|
21
|
+
r"""
|
|
22
|
+
-?\d+(?:\.\d+)?
|
|
23
|
+
(?: # optional range
|
|
24
|
+
- # range delimiter
|
|
25
|
+
-?\d+(?:\.\d+)? # end frame
|
|
26
|
+
(?: # optional stepping
|
|
27
|
+
x # step type
|
|
28
|
+
\d+(?:\.\d+)? # step value
|
|
29
|
+
)?
|
|
30
|
+
)?
|
|
31
|
+
""",
|
|
32
|
+
re.VERBOSE,
|
|
33
|
+
)
|
|
34
|
+
PAD_FORMAT_RE = re.compile(
|
|
35
|
+
r"""
|
|
36
|
+
(?:
|
|
37
|
+
\#+(?:\.\#+)?
|
|
38
|
+
| <UDIM>
|
|
39
|
+
| <UVTILE>
|
|
40
|
+
)
|
|
41
|
+
""",
|
|
42
|
+
flags=re.VERBOSE,
|
|
43
|
+
)
|
|
44
|
+
RANGES_RE = re.compile(
|
|
45
|
+
f"""
|
|
46
|
+
(
|
|
47
|
+
(?:{RANGE_RE.pattern} (?:, {RANGE_RE.pattern})*)?
|
|
48
|
+
{PAD_FORMAT_RE.pattern}
|
|
49
|
+
)
|
|
50
|
+
""",
|
|
51
|
+
flags=RANGE_RE.flags | PAD_FORMAT_RE.flags | re.VERBOSE,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class TokenType(enum.Enum):
|
|
56
|
+
RANGE = enum.auto()
|
|
57
|
+
INTER_RANGE = enum.auto()
|
|
58
|
+
STEM = enum.auto()
|
|
59
|
+
PREFIX = enum.auto()
|
|
60
|
+
SUFFIXES = enum.auto()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class Token:
|
|
65
|
+
type: TokenType
|
|
66
|
+
value: str
|
|
67
|
+
column: int
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def end_column(self) -> int:
|
|
71
|
+
return self.column + len(self.value)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _tokenise_seq(seq: str) -> list[Token]:
|
|
75
|
+
raw_tokens = re.split(RANGES_RE, seq)
|
|
76
|
+
if len(raw_tokens) == 1:
|
|
77
|
+
raise NotASequenceError(seq)
|
|
78
|
+
|
|
79
|
+
return process_tokens(seq, raw_tokens)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def process_tokens(seq: str, raw_tokens: list[str]) -> list[Token]:
|
|
83
|
+
if seq.endswith("."):
|
|
84
|
+
raise ParseError(seq, len(seq) - 1, reason="Suffixes cannot end with a '.'")
|
|
85
|
+
|
|
86
|
+
starts_with_range = not bool(raw_tokens[0])
|
|
87
|
+
if starts_with_range:
|
|
88
|
+
raise ParseError(seq, 0, reason="Expected a stem but got a range")
|
|
89
|
+
|
|
90
|
+
ends_with_range = not bool(raw_tokens[-1])
|
|
91
|
+
if ends_with_range:
|
|
92
|
+
raise ParseError(
|
|
93
|
+
seq, len(seq), reason="Expected file suffixes but path ends with a range"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
column = 0
|
|
97
|
+
tokens: list[Token] = []
|
|
98
|
+
for i, raw_token in enumerate(raw_tokens):
|
|
99
|
+
if i == 0:
|
|
100
|
+
separator = None
|
|
101
|
+
if raw_token != "." and any(
|
|
102
|
+
raw_token.endswith(sep) for sep in _PREFIX_SEPARATORS
|
|
103
|
+
):
|
|
104
|
+
separator = Token(
|
|
105
|
+
TokenType.PREFIX,
|
|
106
|
+
raw_token[-1],
|
|
107
|
+
column + len(raw_token) - 1,
|
|
108
|
+
)
|
|
109
|
+
raw_token = raw_token[:-1]
|
|
110
|
+
|
|
111
|
+
token = Token(
|
|
112
|
+
TokenType.STEM,
|
|
113
|
+
raw_token,
|
|
114
|
+
column,
|
|
115
|
+
)
|
|
116
|
+
tokens.append(token)
|
|
117
|
+
|
|
118
|
+
if separator:
|
|
119
|
+
tokens.append(separator)
|
|
120
|
+
elif i % 2 == 1:
|
|
121
|
+
token = Token(
|
|
122
|
+
TokenType.RANGE,
|
|
123
|
+
raw_token,
|
|
124
|
+
column,
|
|
125
|
+
)
|
|
126
|
+
tokens.append(token)
|
|
127
|
+
elif i >= 2 and i != len(raw_tokens) - 1:
|
|
128
|
+
if not raw_token:
|
|
129
|
+
raise ParseError(
|
|
130
|
+
seq,
|
|
131
|
+
column + 1,
|
|
132
|
+
column + 2,
|
|
133
|
+
"Expected a non-empty inter-range separator",
|
|
134
|
+
)
|
|
135
|
+
if raw_token == ".":
|
|
136
|
+
raise ParseError(
|
|
137
|
+
seq,
|
|
138
|
+
column + 1,
|
|
139
|
+
column + 2,
|
|
140
|
+
"Cannot use '.' as an inter-range separator",
|
|
141
|
+
)
|
|
142
|
+
token = Token(
|
|
143
|
+
TokenType.INTER_RANGE,
|
|
144
|
+
raw_token,
|
|
145
|
+
column,
|
|
146
|
+
)
|
|
147
|
+
tokens.append(token)
|
|
148
|
+
elif i == len(raw_tokens) - 1:
|
|
149
|
+
if raw_token.startswith("."):
|
|
150
|
+
token = Token(
|
|
151
|
+
TokenType.SUFFIXES,
|
|
152
|
+
raw_token,
|
|
153
|
+
column,
|
|
154
|
+
)
|
|
155
|
+
tokens.append(token)
|
|
156
|
+
else:
|
|
157
|
+
raise ParseError(
|
|
158
|
+
seq,
|
|
159
|
+
column + 1,
|
|
160
|
+
column + 2,
|
|
161
|
+
"Expected a '.' to begin file suffixes",
|
|
162
|
+
)
|
|
163
|
+
else:
|
|
164
|
+
assert False, "Found a token that should be impossible"
|
|
165
|
+
|
|
166
|
+
column += len(raw_token)
|
|
167
|
+
|
|
168
|
+
assert all(isinstance(token, Token) for token in tokens)
|
|
169
|
+
if not any(token.type == TokenType.RANGE for token in tokens):
|
|
170
|
+
raise NotASequenceError(seq)
|
|
171
|
+
|
|
172
|
+
if __debug__:
|
|
173
|
+
type_counts = collections.Counter(token.type for token in tokens)
|
|
174
|
+
assert type_counts[TokenType.STEM] <= 1
|
|
175
|
+
num_pre_seps = type_counts[TokenType.PREFIX]
|
|
176
|
+
assert num_pre_seps <= 1
|
|
177
|
+
num_ranges = type_counts[TokenType.RANGE]
|
|
178
|
+
num_inter_ranges = type_counts[TokenType.INTER_RANGE]
|
|
179
|
+
assert num_inter_ranges == num_ranges - 1
|
|
180
|
+
assert type_counts[TokenType.SUFFIXES] <= 1
|
|
181
|
+
|
|
182
|
+
return tokens
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class _SeqParser(StateMachine):
|
|
186
|
+
init = State(initial=True)
|
|
187
|
+
stem = State()
|
|
188
|
+
|
|
189
|
+
in_prefix = State()
|
|
190
|
+
range_in_name = State()
|
|
191
|
+
in_inter_range = State()
|
|
192
|
+
in_suffixes = State()
|
|
193
|
+
|
|
194
|
+
parsed = State(final=True)
|
|
195
|
+
|
|
196
|
+
pump = (
|
|
197
|
+
init.to(stem, validators="expect_stem")
|
|
198
|
+
| stem.to(in_prefix, cond="is_prefix")
|
|
199
|
+
| stem.to(range_in_name, validators="expect_prefix_or_range")
|
|
200
|
+
| in_prefix.to(range_in_name, validators="expect_range")
|
|
201
|
+
| range_in_name.to(in_inter_range, cond="is_inter_range")
|
|
202
|
+
| in_inter_range.to(range_in_name, validators="expect_range")
|
|
203
|
+
| range_in_name.to(in_suffixes, validators="expect_inter_range_or_suffixes")
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
finalise = in_suffixes.to(parsed)
|
|
207
|
+
|
|
208
|
+
def __init__(self, seq: str) -> None:
|
|
209
|
+
super().__init__()
|
|
210
|
+
|
|
211
|
+
self._seq = seq
|
|
212
|
+
self._stem: str = ""
|
|
213
|
+
self._prefix = ""
|
|
214
|
+
self._ranges: list[PaddedRange[int] | PaddedRange[Decimal]] = []
|
|
215
|
+
self._inter_ranges: list[str] = []
|
|
216
|
+
self._suffixes: tuple[str, ...] = ()
|
|
217
|
+
|
|
218
|
+
def is_inter_range(self, token: Token) -> bool:
|
|
219
|
+
return token.type == TokenType.INTER_RANGE
|
|
220
|
+
|
|
221
|
+
def is_prefix(self, token: Token) -> bool:
|
|
222
|
+
return token.type == TokenType.PREFIX
|
|
223
|
+
|
|
224
|
+
def expect_stem(self, token: Token) -> bool:
|
|
225
|
+
return self._validate_stem(token, "Expected a stem")
|
|
226
|
+
|
|
227
|
+
def expect_prefix_or_range(self, token: Token) -> bool:
|
|
228
|
+
return self._validate_range(token, "Expected a prefix separator, or the ranges")
|
|
229
|
+
|
|
230
|
+
def expect_range(self, token: Token) -> bool:
|
|
231
|
+
return self._validate_range(token, "Expected the ranges")
|
|
232
|
+
|
|
233
|
+
def expect_inter_range_or_suffixes(self, token: Token) -> bool:
|
|
234
|
+
return self._validate_suffixes(
|
|
235
|
+
token, "Expected an inter-range string, or file suffixes"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def _validate_range(self, token: Token, reason: str) -> bool:
|
|
239
|
+
if token.type != TokenType.RANGE:
|
|
240
|
+
raise ParseError(self._seq, token.column, token.end_column, reason=reason)
|
|
241
|
+
return True
|
|
242
|
+
|
|
243
|
+
def _validate_stem(self, token: Token, reason: str) -> bool:
|
|
244
|
+
if token.type != TokenType.STEM:
|
|
245
|
+
raise ParseError(self._seq, token.column, token.end_column, reason=reason)
|
|
246
|
+
return True
|
|
247
|
+
|
|
248
|
+
def _validate_suffixes(self, token: Token, reason: str) -> bool:
|
|
249
|
+
if token.type != TokenType.SUFFIXES:
|
|
250
|
+
raise ParseError(self._seq, token.column, token.end_column, reason=reason)
|
|
251
|
+
return True
|
|
252
|
+
|
|
253
|
+
def on_enter_stem(self, token: Token) -> None:
|
|
254
|
+
self._stem = token.value
|
|
255
|
+
|
|
256
|
+
def on_enter_in_prefix(self, token: Token) -> None:
|
|
257
|
+
self._prefix = token.value
|
|
258
|
+
|
|
259
|
+
def _parse_padded_range(
|
|
260
|
+
self, token: Token
|
|
261
|
+
) -> PaddedRange[int] | PaddedRange[Decimal]:
|
|
262
|
+
match = PAD_FORMAT_RE.search(token.value)
|
|
263
|
+
if not match:
|
|
264
|
+
raise ParseError(
|
|
265
|
+
self._seq,
|
|
266
|
+
token.column,
|
|
267
|
+
token.end_column,
|
|
268
|
+
reason=f"Tokenised an invalid range: {token.value}",
|
|
269
|
+
)
|
|
270
|
+
pad_format = match.group(0)
|
|
271
|
+
seq_str = token.value[: -len(pad_format)]
|
|
272
|
+
file_nums = FileNumSequence.from_str(seq_str)
|
|
273
|
+
return PaddedRange(file_nums, pad_format) # type: ignore[misc]
|
|
274
|
+
|
|
275
|
+
def _parse_suffixes(self, token: Token) -> tuple[str, ...]:
|
|
276
|
+
if not token.value:
|
|
277
|
+
return ()
|
|
278
|
+
|
|
279
|
+
suffixes = []
|
|
280
|
+
buffer = token.value[0]
|
|
281
|
+
for char in token.value[1:]:
|
|
282
|
+
if char == ".":
|
|
283
|
+
if buffer == ".":
|
|
284
|
+
column = token.column + len(buffer)
|
|
285
|
+
raise ParseError(
|
|
286
|
+
self._seq,
|
|
287
|
+
column,
|
|
288
|
+
column + 1,
|
|
289
|
+
"Cannot have an empty file extension",
|
|
290
|
+
)
|
|
291
|
+
suffixes.append(buffer)
|
|
292
|
+
buffer = char
|
|
293
|
+
else:
|
|
294
|
+
buffer += char
|
|
295
|
+
|
|
296
|
+
suffixes.append(buffer)
|
|
297
|
+
return tuple(suffixes)
|
|
298
|
+
|
|
299
|
+
def on_enter_range_in_name(self, token: Token) -> None:
|
|
300
|
+
self._ranges.append(self._parse_padded_range(token))
|
|
301
|
+
|
|
302
|
+
def on_enter_in_inter_range(self, token: Token) -> None:
|
|
303
|
+
self._inter_ranges.append(token.value or "")
|
|
304
|
+
|
|
305
|
+
def on_enter_in_suffixes(self, token: Token) -> None:
|
|
306
|
+
self._suffixes = self._parse_suffixes(token)
|
|
307
|
+
|
|
308
|
+
def on_finalise(self) -> ParsedSequence:
|
|
309
|
+
return ParsedSequence(
|
|
310
|
+
stem=self._stem,
|
|
311
|
+
prefix=self._prefix,
|
|
312
|
+
ranges=Ranges(tuple(self._ranges), tuple(self._inter_ranges)),
|
|
313
|
+
suffixes=self._suffixes,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
@classmethod
|
|
317
|
+
def parse(cls, seq: str) -> ParsedSequence:
|
|
318
|
+
machine = cls(seq)
|
|
319
|
+
tokens = _tokenise_seq(seq)
|
|
320
|
+
for token in tokens:
|
|
321
|
+
machine.pump(token)
|
|
322
|
+
|
|
323
|
+
return machine.finalise() # type: ignore[no-any-return]
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def parse_path_sequence(seq: str) -> ParsedSequence:
|
|
327
|
+
return _SeqParser.parse(seq)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ._base import BasePathSequence, PathT_co
|
|
4
|
+
from ._pure_path_sequence import PurePathSequence
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PathSequence(PurePathSequence[PathT_co], BasePathSequence[PathT_co]):
|
|
8
|
+
"""A sequence of Path objects.
|
|
9
|
+
|
|
10
|
+
Raises:
|
|
11
|
+
NotASequenceError: When the given path does not represent a sequence,
|
|
12
|
+
but a regular path.
|
|
13
|
+
ParseError: When the given path is not a valid path sequence.
|
|
14
|
+
"""
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
|
|
5
|
+
from typing_extensions import (
|
|
6
|
+
Self, # PY311
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
from ._ast import PaddedRange, ParsedSequence, Ranges
|
|
10
|
+
from ._base import BasePurePathSequence, PathT_co
|
|
11
|
+
from ._file_num_seq import FileNumSequence
|
|
12
|
+
from ._parse_path_sequence import parse_path_sequence
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PurePathSequence(BasePurePathSequence[PathT_co]):
|
|
16
|
+
"""A sequence of PurePath objects.
|
|
17
|
+
|
|
18
|
+
Raises:
|
|
19
|
+
NotASequenceError: When the given path does not represent a sequence,
|
|
20
|
+
but a regular path.
|
|
21
|
+
ParseError: When the given path is not a valid path sequence.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
_parsed: ParsedSequence
|
|
25
|
+
|
|
26
|
+
def _parse(self, name: str) -> ParsedSequence:
|
|
27
|
+
return parse_path_sequence(name)
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def parsed(self) -> ParsedSequence:
|
|
31
|
+
"""The parsed sequence string, as a tree of objects."""
|
|
32
|
+
return self._parsed
|
|
33
|
+
|
|
34
|
+
def with_file_num_seqs(
|
|
35
|
+
self, *seqs: FileNumSequence[int] | FileNumSequence[Decimal]
|
|
36
|
+
) -> Self:
|
|
37
|
+
"""Return a new sequence with the file number sequences changed.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
TypeError: If the given number of file number sequences does not match
|
|
41
|
+
the sequence's number of file number sequences.
|
|
42
|
+
"""
|
|
43
|
+
if len(seqs) != len(self._parsed.ranges.ranges):
|
|
44
|
+
raise TypeError(
|
|
45
|
+
f"Need {len(self._parsed.ranges.ranges)} sequences, but got {len(seqs)}"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
new_ranges = tuple(
|
|
49
|
+
PaddedRange(seq, range_.pad_format)
|
|
50
|
+
for seq, range_ in zip(seqs, self._parsed.ranges.ranges)
|
|
51
|
+
)
|
|
52
|
+
new = self._parsed.__class__(
|
|
53
|
+
self._parsed.stem,
|
|
54
|
+
self._parsed.prefix,
|
|
55
|
+
Ranges(new_ranges, self._parsed.ranges.inter_ranges),
|
|
56
|
+
self._parsed.suffixes,
|
|
57
|
+
)
|
|
58
|
+
return self.__class__(self._path.with_name(str(new)))
|
pathseq/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: pathseq
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A pathlib-like library for working with file sequences.
|
|
5
|
+
Author: Ashley Whetter
|
|
6
|
+
Author-email: Ashley Whetter <ashley@awhetter.co.uk>
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Environment :: Plugins
|
|
9
|
+
Classifier: Framework :: Sphinx :: Extension
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Natural Language :: English
|
|
13
|
+
Classifier: Programming Language :: Python
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Requires-Dist: lark
|
|
21
|
+
Requires-Dist: python-statemachine
|
|
22
|
+
Requires-Dist: typing-extensions
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Project-URL: Documentation, https://pathseq.readthedocs.org
|
|
25
|
+
Project-URL: Source, https://github.com/AWhetter/pathseq
|
|
26
|
+
Description-Content-Type: text/x-rst
|
|
27
|
+
|
|
28
|
+
pathseq
|
|
29
|
+
=======
|
|
30
|
+
|
|
31
|
+
.. image:: https://readthedocs.org/projects/pathseq/badge/?version=latest
|
|
32
|
+
:target: https://pathseq.readthedocs.org
|
|
33
|
+
:alt: Documentation
|
|
34
|
+
|
|
35
|
+
.. image:: https://github.com/AWhetter/pathseq/actions/workflows/main.yml/badge.svg?branch=main
|
|
36
|
+
:target: https://github.com/AWhetter/pathseq/actions/workflows/main.yml?query=branch%3Amain
|
|
37
|
+
:alt: Github Build Status
|
|
38
|
+
|
|
39
|
+
.. image:: https://img.shields.io/pypi/v/pathseq.svg
|
|
40
|
+
:target: https://pypi.org/project/pathseq/
|
|
41
|
+
:alt: PyPI Version
|
|
42
|
+
|
|
43
|
+
.. image:: https://img.shields.io/pypi/pyversions/pathseq.svg
|
|
44
|
+
:target: https://pypi.org/project/pathseq/
|
|
45
|
+
:alt: Supported Python Versions
|
|
46
|
+
|
|
47
|
+
A pathlib-first library for working with file sequences.
|
|
48
|
+
|
|
49
|
+
TODO: Note special features
|
|
50
|
+
|
|
51
|
+
* Multi-dimension ranges (eg animated udims)
|
|
52
|
+
* pathlib-first API
|
|
53
|
+
* Support for UDIM tokens
|
|
54
|
+
* Sequence compression?
|
|
55
|
+
|
|
56
|
+
Getting Started
|
|
57
|
+
---------------
|
|
58
|
+
|
|
59
|
+
The following section demonstrates how to install ``pathseq`` and some basic usage.
|
|
60
|
+
Full documentation is available here: https://pathseq.readthedocs.io/en/latest/
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
Installation
|
|
64
|
+
~~~~~~~~~~~~
|
|
65
|
+
|
|
66
|
+
``pathseq`` can be installed through pip:
|
|
67
|
+
|
|
68
|
+
.. code-block:: bash
|
|
69
|
+
|
|
70
|
+
pip install pathseq
|
|
71
|
+
|
|
72
|
+
Usage
|
|
73
|
+
~~~~~
|
|
74
|
+
|
|
75
|
+
TODO
|
|
76
|
+
|
|
77
|
+
For more detailed usage, see the documentation:
|
|
78
|
+
https://pathseq.readthedocs.io/en/latest/
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
Contributing
|
|
82
|
+
------------
|
|
83
|
+
|
|
84
|
+
Running the tests
|
|
85
|
+
~~~~~~~~~~~~~~~~~
|
|
86
|
+
|
|
87
|
+
Tests are executed through `tox <https://tox.readthedocs.io/en/latest/>`_.
|
|
88
|
+
|
|
89
|
+
.. code-block:: bash
|
|
90
|
+
|
|
91
|
+
tox
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
Code Style
|
|
95
|
+
~~~~~~~~~~
|
|
96
|
+
|
|
97
|
+
Code is formatted using `black <https://github.com/python/black>`_.
|
|
98
|
+
|
|
99
|
+
You can check your formatting using black's check mode:
|
|
100
|
+
|
|
101
|
+
.. code-block:: bash
|
|
102
|
+
|
|
103
|
+
tox -e format
|
|
104
|
+
|
|
105
|
+
You can also get black to format your changes for you:
|
|
106
|
+
|
|
107
|
+
.. code-block:: bash
|
|
108
|
+
|
|
109
|
+
tox -e format -- src/ tests/
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
Release Notes
|
|
113
|
+
~~~~~~~~~~~~~
|
|
114
|
+
|
|
115
|
+
Release notes are managed through `towncrier <https://towncrier.readthedocs.io/en/stable/index.html>`_.
|
|
116
|
+
When making a pull request you will need to create a news fragment to document your change:
|
|
117
|
+
|
|
118
|
+
.. code-block:: bash
|
|
119
|
+
|
|
120
|
+
tox -e release_notes -- create --help
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
Versioning
|
|
124
|
+
----------
|
|
125
|
+
|
|
126
|
+
We use `SemVer <https://semver.org/>`_ for versioning.
|
|
127
|
+
For the versions available, see the `tags on this repository <https://github.com/AWhetter/pathseq/tags>`_.
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
License
|
|
131
|
+
-------
|
|
132
|
+
|
|
133
|
+
This project is licensed under the MIT License.
|
|
134
|
+
See the `LICENSE.rst <LICENSE.rst>`_ file for details.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
pathseq/__init__.py,sha256=Vfq5BvdPoY0rpQ_JLgGa6HEn69RmlMvBAbMAIfPpOPU,1121
|
|
2
|
+
pathseq/_arithmetic_sequence.py,sha256=9FiiHMRHPiSVuKh1IWOkHhapiJoeFDl6LKr15p_t790,4630
|
|
3
|
+
pathseq/_ast/__init__.py,sha256=vzv_VxfY8HFmKuVbPhzVrZ3Ii1DVskSlo7LhIHxtdcY,447
|
|
4
|
+
pathseq/_ast/_base.py,sha256=rDgCUF8m4RUBdYU7Q5C5r6SDWHzgC6e1yf3-dUSyN50,8241
|
|
5
|
+
pathseq/_ast/_formatter.py,sha256=xy6UKTZ8Nx2DAlzoe9VQzqAMskugDYNoWSD2AKVgU-A,1146
|
|
6
|
+
pathseq/_ast/_loose_type.py,sha256=yg0T1Xw-KWALTqmx7ZrHvw9RCR7cD2sI8A_9ek_HeWE,7574
|
|
7
|
+
pathseq/_ast/_type.py,sha256=VqIXf1Au-RMXrmCyDu5MofjCI1UPEydpPJbB4aQhR1A,1958
|
|
8
|
+
pathseq/_base.py,sha256=EOcxeb3Wv4v_DKXtkiFysv1eyKvO8VKke-9MDWhJwJM,17096
|
|
9
|
+
pathseq/_decimal_range.py,sha256=T6eqqVzRe5JfvnraO5UzqslGKke-64T2714Q08boyZM,5138
|
|
10
|
+
pathseq/_error.py,sha256=MNK5kazk3PsuE6hs9Hpcv9wnYqZZIqoIMuzj9IErUYw,2644
|
|
11
|
+
pathseq/_file_num_seq.py,sha256=VSqYAX4-tIJn3h5AD038_xpAP31Oj3ocoZfch-tClTY,7293
|
|
12
|
+
pathseq/_formatter.py,sha256=Sevv_B-AsOSbx0D4oYq-edgHQEySLpMoyZ94CK5ZMOY,2569
|
|
13
|
+
pathseq/_from_disk.py,sha256=iUqK3ma1m9vhIUso2vnLLpAa9tF_eQXYOZL4ch7oewU,2445
|
|
14
|
+
pathseq/_loose_path_sequence.py,sha256=0YoJM5H7nXO0zFFK09cCx3GYBvDlOcwk8VZLUaTqpxk,467
|
|
15
|
+
pathseq/_loose_pure_path_sequence.py,sha256=cc4dfStsCqtLrvJ40RuNQRO5OjUQLN2r-QkB581_0lc,4196
|
|
16
|
+
pathseq/_parse_file_num_seq.py,sha256=6Sd__7SEzjQaxXvVXj-0Yad8NsElHl7JzQukYpvS-KA,2102
|
|
17
|
+
pathseq/_parse_loose_path_sequence.py,sha256=8_v05ty-gn4S8TD0djduD1Sj7JPVkw9SNKBeLwEx_sI,17539
|
|
18
|
+
pathseq/_parse_path_sequence.py,sha256=FumrJ02XOpjfm_UMVEi786E3ZkWDXu1PhqW1qrfl55M,9957
|
|
19
|
+
pathseq/_path_sequence.py,sha256=glss3zoC3lMTxGrPIGQXputLOG65e-XSFM50BwhdpXk,446
|
|
20
|
+
pathseq/_pure_path_sequence.py,sha256=3-MgYD4peH-uNpm7xkTdLxYP1Kkz9ERiU4DRy9S32dA,1869
|
|
21
|
+
pathseq/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
pathseq-0.1.0.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
23
|
+
pathseq-0.1.0.dist-info/METADATA,sha256=8XaFeU9rKHVPkgtdpW2_BvPG-s7M5uX4pASwe0zbIa8,3421
|
|
24
|
+
pathseq-0.1.0.dist-info/RECORD,,
|