pathseq 0.1.0__tar.gz → 1.0.0__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.
- {pathseq-0.1.0 → pathseq-1.0.0}/PKG-INFO +84 -13
- pathseq-1.0.0/README.rst +178 -0
- {pathseq-0.1.0 → pathseq-1.0.0}/pyproject.toml +2 -1
- {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/__init__.py +7 -5
- {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_ast/__init__.py +3 -3
- pathseq-1.0.0/src/pathseq/_ast/_formatter.py +138 -0
- {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_ast/_loose_type.py +22 -24
- pathseq-1.0.0/src/pathseq/_ast/_ranges.py +72 -0
- {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_ast/_type.py +9 -10
- pathseq-1.0.0/src/pathseq/_ast/_util.py +98 -0
- {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_base.py +58 -23
- {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_error.py +0 -24
- pathseq-1.0.0/src/pathseq/_file_num_seq/__init__.py +7 -0
- {pathseq-0.1.0/src/pathseq → pathseq-1.0.0/src/pathseq/_file_num_seq}/_arithmetic_sequence.py +1 -1
- {pathseq-0.1.0/src/pathseq → pathseq-1.0.0/src/pathseq/_file_num_seq}/_decimal_range.py +13 -11
- {pathseq-0.1.0/src/pathseq → pathseq-1.0.0/src/pathseq/_file_num_seq}/_file_num_seq.py +5 -3
- {pathseq-0.1.0/src/pathseq → pathseq-1.0.0/src/pathseq/_file_num_seq}/_parse_file_num_seq.py +1 -1
- pathseq-0.1.0/src/pathseq/_formatter.py → pathseq-1.0.0/src/pathseq/_formatters.py +2 -2
- {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_from_disk.py +58 -21
- {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_loose_pure_path_sequence.py +10 -35
- {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_parse_loose_path_sequence.py +121 -74
- {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_parse_path_sequence.py +22 -19
- pathseq-1.0.0/src/pathseq/_pure_path_sequence.py +25 -0
- pathseq-0.1.0/README.rst +0 -107
- pathseq-0.1.0/src/pathseq/_ast/_base.py +0 -236
- pathseq-0.1.0/src/pathseq/_ast/_formatter.py +0 -38
- pathseq-0.1.0/src/pathseq/_pure_path_sequence.py +0 -58
- {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_loose_path_sequence.py +0 -0
- {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_path_sequence.py +0 -0
- {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pathseq
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: A pathlib-like library for working with file sequences.
|
|
5
5
|
Author: Ashley Whetter
|
|
6
6
|
Author-email: Ashley Whetter <ashley@awhetter.co.uk>
|
|
@@ -44,14 +44,12 @@ pathseq
|
|
|
44
44
|
:target: https://pypi.org/project/pathseq/
|
|
45
45
|
:alt: Supported Python Versions
|
|
46
46
|
|
|
47
|
-
A pathlib-first library for working with
|
|
47
|
+
A pathlib-first library for working with path sequences.
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
* Multi-dimension ranges (eg animated udims)
|
|
49
|
+
* Multi-dimension ranges (e.g. animated udims)
|
|
52
50
|
* pathlib-first API
|
|
53
51
|
* Support for UDIM tokens
|
|
54
|
-
|
|
52
|
+
|
|
55
53
|
|
|
56
54
|
Getting Started
|
|
57
55
|
---------------
|
|
@@ -63,20 +61,93 @@ Full documentation is available here: https://pathseq.readthedocs.io/en/latest/
|
|
|
63
61
|
Installation
|
|
64
62
|
~~~~~~~~~~~~
|
|
65
63
|
|
|
66
|
-
``pathseq`` can be installed
|
|
64
|
+
``pathseq`` can be installed from PyPI:
|
|
67
65
|
|
|
68
66
|
.. code-block:: bash
|
|
69
67
|
|
|
70
68
|
pip install pathseq
|
|
71
69
|
|
|
70
|
+
|
|
72
71
|
Usage
|
|
73
72
|
~~~~~
|
|
74
73
|
|
|
75
|
-
TODO
|
|
76
|
-
|
|
77
74
|
For more detailed usage, see the documentation:
|
|
78
75
|
https://pathseq.readthedocs.io/en/latest/
|
|
79
76
|
|
|
77
|
+
Now, let's get started:
|
|
78
|
+
|
|
79
|
+
.. code-block:: pycon
|
|
80
|
+
|
|
81
|
+
>>> from pathseq import PathSequence
|
|
82
|
+
>>> seq = PathSequence("tests/fixtures/simple/images.1-5####.exr")
|
|
83
|
+
>>> for path in seq:
|
|
84
|
+
... path
|
|
85
|
+
...
|
|
86
|
+
PosixPath('tests/fixtures/simple/images.0001.exr')
|
|
87
|
+
PosixPath('tests/fixtures/simple/images.0002.exr')
|
|
88
|
+
PosixPath('tests/fixtures/simple/images.0003.exr')
|
|
89
|
+
PosixPath('tests/fixtures/simple/images.0004.exr')
|
|
90
|
+
PosixPath('tests/fixtures/simple/images.0005.exr')
|
|
91
|
+
|
|
92
|
+
>>> seq = PathSequence("tests/fixtures/simple/images.1-5####.exr")
|
|
93
|
+
>>> for path in seq:
|
|
94
|
+
... path.touch(exist_ok=True)
|
|
95
|
+
...
|
|
96
|
+
>>> seq2 = PathSequence("tests/fixtures/simple/images.####.exr")
|
|
97
|
+
>>> for path in seq2.iter_existing_paths():
|
|
98
|
+
... path
|
|
99
|
+
...
|
|
100
|
+
PosixPath('tests/fixtures/simple/images.0001.exr')
|
|
101
|
+
PosixPath('tests/fixtures/simple/images.0002.exr')
|
|
102
|
+
PosixPath('tests/fixtures/simple/images.0003.exr')
|
|
103
|
+
PosixPath('tests/fixtures/simple/images.0004.exr')
|
|
104
|
+
PosixPath('tests/fixtures/simple/images.0005.exr')
|
|
105
|
+
|
|
106
|
+
>>> seq = PathSequence("tests/fixtures/simple/images.1-5####.exr")
|
|
107
|
+
>>> seq.parent
|
|
108
|
+
PosixPath('tests/fixtures/simple')
|
|
109
|
+
>>> seq.parent / PathSequence("images.1-5####.exr")
|
|
110
|
+
... PathSequence("tests/fixtures/simple/images.1-5####.exr")
|
|
111
|
+
|
|
112
|
+
>>> anim_udims = PathSequence("/path/to/textures.1011-1012<UDIM>_1-3#.tex")
|
|
113
|
+
>>> for path in anim_udims:
|
|
114
|
+
... path
|
|
115
|
+
...
|
|
116
|
+
PosixPath('/path/to/textures.1011_1.tex')
|
|
117
|
+
PosixPath('/path/to/textures.1011_2.tex')
|
|
118
|
+
PosixPath('/path/to/textures.1011_3.tex')
|
|
119
|
+
PosixPath('/path/to/textures.1012_1.tex')
|
|
120
|
+
PosixPath('/path/to/textures.1012_2.tex')
|
|
121
|
+
PosixPath('/path/to/textures.1012_3.tex')
|
|
122
|
+
|
|
123
|
+
A wide range of sequence string formats are supported:
|
|
124
|
+
|
|
125
|
+
.. code-block:: pycon
|
|
126
|
+
|
|
127
|
+
>>> from pathseq import LoosePathSequence
|
|
128
|
+
>>> seq = LoosePathSequence("/path/to/images.1-5####.exr")
|
|
129
|
+
|
|
130
|
+
>>> seq = LoosePathSequence("/path/to/1-5####_images.exr")
|
|
131
|
+
>>> seq[0]
|
|
132
|
+
PosixPath('/path/to/0001_images.exr')
|
|
133
|
+
>>> seq.suffixes
|
|
134
|
+
('.exr',)
|
|
135
|
+
|
|
136
|
+
>>> LoosePathSequence("/path/to/1-5####_archives.tar.gz").suffixes
|
|
137
|
+
('.tar', '.gz')
|
|
138
|
+
|
|
139
|
+
>>> seq = LoosePathSequence("/path/to/images.exr.1-5####")
|
|
140
|
+
>>> seq[0]
|
|
141
|
+
PosixPath('/path/to/images.exr.0001')
|
|
142
|
+
>>> seq.suffixes
|
|
143
|
+
('.exr',)
|
|
144
|
+
|
|
145
|
+
>>> LoosePathSequence("/path/to/images.1001-1005<UDIM>.exr")[0]
|
|
146
|
+
PosixPath('/path/to/images.1001.exr')
|
|
147
|
+
|
|
148
|
+
>>> LoosePathSequence("/path/to/images.1001-1005<UVTILE>.exr")[0]
|
|
149
|
+
PosixPath('/path/to/images.u1_v1.exr')
|
|
150
|
+
|
|
80
151
|
|
|
81
152
|
Contributing
|
|
82
153
|
------------
|
|
@@ -94,19 +165,19 @@ Tests are executed through `tox <https://tox.readthedocs.io/en/latest/>`_.
|
|
|
94
165
|
Code Style
|
|
95
166
|
~~~~~~~~~~
|
|
96
167
|
|
|
97
|
-
Code is formatted using `
|
|
168
|
+
Code is formatted using `ruff <https://docs.astral.sh/ruff/formatter/>`_.
|
|
98
169
|
|
|
99
|
-
You can check your formatting using
|
|
170
|
+
You can check your formatting using ruff's check mode:
|
|
100
171
|
|
|
101
172
|
.. code-block:: bash
|
|
102
173
|
|
|
103
174
|
tox -e format
|
|
104
175
|
|
|
105
|
-
You can also get
|
|
176
|
+
You can also get ruff to format your changes for you:
|
|
106
177
|
|
|
107
178
|
.. code-block:: bash
|
|
108
179
|
|
|
109
|
-
tox -e
|
|
180
|
+
tox -e autoformat
|
|
110
181
|
|
|
111
182
|
|
|
112
183
|
Release Notes
|
pathseq-1.0.0/README.rst
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
pathseq
|
|
2
|
+
=======
|
|
3
|
+
|
|
4
|
+
.. image:: https://readthedocs.org/projects/pathseq/badge/?version=latest
|
|
5
|
+
:target: https://pathseq.readthedocs.org
|
|
6
|
+
:alt: Documentation
|
|
7
|
+
|
|
8
|
+
.. image:: https://github.com/AWhetter/pathseq/actions/workflows/main.yml/badge.svg?branch=main
|
|
9
|
+
:target: https://github.com/AWhetter/pathseq/actions/workflows/main.yml?query=branch%3Amain
|
|
10
|
+
:alt: Github Build Status
|
|
11
|
+
|
|
12
|
+
.. image:: https://img.shields.io/pypi/v/pathseq.svg
|
|
13
|
+
:target: https://pypi.org/project/pathseq/
|
|
14
|
+
:alt: PyPI Version
|
|
15
|
+
|
|
16
|
+
.. image:: https://img.shields.io/pypi/pyversions/pathseq.svg
|
|
17
|
+
:target: https://pypi.org/project/pathseq/
|
|
18
|
+
:alt: Supported Python Versions
|
|
19
|
+
|
|
20
|
+
A pathlib-first library for working with path sequences.
|
|
21
|
+
|
|
22
|
+
* Multi-dimension ranges (e.g. animated udims)
|
|
23
|
+
* pathlib-first API
|
|
24
|
+
* Support for UDIM tokens
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
Getting Started
|
|
28
|
+
---------------
|
|
29
|
+
|
|
30
|
+
The following section demonstrates how to install ``pathseq`` and some basic usage.
|
|
31
|
+
Full documentation is available here: https://pathseq.readthedocs.io/en/latest/
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
Installation
|
|
35
|
+
~~~~~~~~~~~~
|
|
36
|
+
|
|
37
|
+
``pathseq`` can be installed from PyPI:
|
|
38
|
+
|
|
39
|
+
.. code-block:: bash
|
|
40
|
+
|
|
41
|
+
pip install pathseq
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
Usage
|
|
45
|
+
~~~~~
|
|
46
|
+
|
|
47
|
+
For more detailed usage, see the documentation:
|
|
48
|
+
https://pathseq.readthedocs.io/en/latest/
|
|
49
|
+
|
|
50
|
+
Now, let's get started:
|
|
51
|
+
|
|
52
|
+
.. code-block:: pycon
|
|
53
|
+
|
|
54
|
+
>>> from pathseq import PathSequence
|
|
55
|
+
>>> seq = PathSequence("tests/fixtures/simple/images.1-5####.exr")
|
|
56
|
+
>>> for path in seq:
|
|
57
|
+
... path
|
|
58
|
+
...
|
|
59
|
+
PosixPath('tests/fixtures/simple/images.0001.exr')
|
|
60
|
+
PosixPath('tests/fixtures/simple/images.0002.exr')
|
|
61
|
+
PosixPath('tests/fixtures/simple/images.0003.exr')
|
|
62
|
+
PosixPath('tests/fixtures/simple/images.0004.exr')
|
|
63
|
+
PosixPath('tests/fixtures/simple/images.0005.exr')
|
|
64
|
+
|
|
65
|
+
>>> seq = PathSequence("tests/fixtures/simple/images.1-5####.exr")
|
|
66
|
+
>>> for path in seq:
|
|
67
|
+
... path.touch(exist_ok=True)
|
|
68
|
+
...
|
|
69
|
+
>>> seq2 = PathSequence("tests/fixtures/simple/images.####.exr")
|
|
70
|
+
>>> for path in seq2.iter_existing_paths():
|
|
71
|
+
... path
|
|
72
|
+
...
|
|
73
|
+
PosixPath('tests/fixtures/simple/images.0001.exr')
|
|
74
|
+
PosixPath('tests/fixtures/simple/images.0002.exr')
|
|
75
|
+
PosixPath('tests/fixtures/simple/images.0003.exr')
|
|
76
|
+
PosixPath('tests/fixtures/simple/images.0004.exr')
|
|
77
|
+
PosixPath('tests/fixtures/simple/images.0005.exr')
|
|
78
|
+
|
|
79
|
+
>>> seq = PathSequence("tests/fixtures/simple/images.1-5####.exr")
|
|
80
|
+
>>> seq.parent
|
|
81
|
+
PosixPath('tests/fixtures/simple')
|
|
82
|
+
>>> seq.parent / PathSequence("images.1-5####.exr")
|
|
83
|
+
... PathSequence("tests/fixtures/simple/images.1-5####.exr")
|
|
84
|
+
|
|
85
|
+
>>> anim_udims = PathSequence("/path/to/textures.1011-1012<UDIM>_1-3#.tex")
|
|
86
|
+
>>> for path in anim_udims:
|
|
87
|
+
... path
|
|
88
|
+
...
|
|
89
|
+
PosixPath('/path/to/textures.1011_1.tex')
|
|
90
|
+
PosixPath('/path/to/textures.1011_2.tex')
|
|
91
|
+
PosixPath('/path/to/textures.1011_3.tex')
|
|
92
|
+
PosixPath('/path/to/textures.1012_1.tex')
|
|
93
|
+
PosixPath('/path/to/textures.1012_2.tex')
|
|
94
|
+
PosixPath('/path/to/textures.1012_3.tex')
|
|
95
|
+
|
|
96
|
+
A wide range of sequence string formats are supported:
|
|
97
|
+
|
|
98
|
+
.. code-block:: pycon
|
|
99
|
+
|
|
100
|
+
>>> from pathseq import LoosePathSequence
|
|
101
|
+
>>> seq = LoosePathSequence("/path/to/images.1-5####.exr")
|
|
102
|
+
|
|
103
|
+
>>> seq = LoosePathSequence("/path/to/1-5####_images.exr")
|
|
104
|
+
>>> seq[0]
|
|
105
|
+
PosixPath('/path/to/0001_images.exr')
|
|
106
|
+
>>> seq.suffixes
|
|
107
|
+
('.exr',)
|
|
108
|
+
|
|
109
|
+
>>> LoosePathSequence("/path/to/1-5####_archives.tar.gz").suffixes
|
|
110
|
+
('.tar', '.gz')
|
|
111
|
+
|
|
112
|
+
>>> seq = LoosePathSequence("/path/to/images.exr.1-5####")
|
|
113
|
+
>>> seq[0]
|
|
114
|
+
PosixPath('/path/to/images.exr.0001')
|
|
115
|
+
>>> seq.suffixes
|
|
116
|
+
('.exr',)
|
|
117
|
+
|
|
118
|
+
>>> LoosePathSequence("/path/to/images.1001-1005<UDIM>.exr")[0]
|
|
119
|
+
PosixPath('/path/to/images.1001.exr')
|
|
120
|
+
|
|
121
|
+
>>> LoosePathSequence("/path/to/images.1001-1005<UVTILE>.exr")[0]
|
|
122
|
+
PosixPath('/path/to/images.u1_v1.exr')
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
Contributing
|
|
126
|
+
------------
|
|
127
|
+
|
|
128
|
+
Running the tests
|
|
129
|
+
~~~~~~~~~~~~~~~~~
|
|
130
|
+
|
|
131
|
+
Tests are executed through `tox <https://tox.readthedocs.io/en/latest/>`_.
|
|
132
|
+
|
|
133
|
+
.. code-block:: bash
|
|
134
|
+
|
|
135
|
+
tox
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
Code Style
|
|
139
|
+
~~~~~~~~~~
|
|
140
|
+
|
|
141
|
+
Code is formatted using `ruff <https://docs.astral.sh/ruff/formatter/>`_.
|
|
142
|
+
|
|
143
|
+
You can check your formatting using ruff's check mode:
|
|
144
|
+
|
|
145
|
+
.. code-block:: bash
|
|
146
|
+
|
|
147
|
+
tox -e format
|
|
148
|
+
|
|
149
|
+
You can also get ruff to format your changes for you:
|
|
150
|
+
|
|
151
|
+
.. code-block:: bash
|
|
152
|
+
|
|
153
|
+
tox -e autoformat
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
Release Notes
|
|
157
|
+
~~~~~~~~~~~~~
|
|
158
|
+
|
|
159
|
+
Release notes are managed through `towncrier <https://towncrier.readthedocs.io/en/stable/index.html>`_.
|
|
160
|
+
When making a pull request you will need to create a news fragment to document your change:
|
|
161
|
+
|
|
162
|
+
.. code-block:: bash
|
|
163
|
+
|
|
164
|
+
tox -e release_notes -- create --help
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
Versioning
|
|
168
|
+
----------
|
|
169
|
+
|
|
170
|
+
We use `SemVer <https://semver.org/>`_ for versioning.
|
|
171
|
+
For the versions available, see the `tags on this repository <https://github.com/AWhetter/pathseq/tags>`_.
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
License
|
|
175
|
+
-------
|
|
176
|
+
|
|
177
|
+
This project is licensed under the MIT License.
|
|
178
|
+
See the `LICENSE.rst <LICENSE.rst>`_ file for details.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pathseq"
|
|
3
|
-
version = "
|
|
3
|
+
version = "1.0.0"
|
|
4
4
|
description = "A pathlib-like library for working with file sequences."
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Ashley Whetter", email = "ashley@awhetter.co.uk"},
|
|
@@ -78,6 +78,7 @@ directory = "doc/changes"
|
|
|
78
78
|
filename = "CHANGELOG.rst"
|
|
79
79
|
package = "pathseq"
|
|
80
80
|
title_format = "pathseq v{version} ({project_date})"
|
|
81
|
+
underlines = ["-", "^", "\""]
|
|
81
82
|
|
|
82
83
|
[tool.pytest.ini_options]
|
|
83
84
|
markers = [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""A pathlib-first library for working with file sequences."""
|
|
2
2
|
|
|
3
3
|
from ._ast import (
|
|
4
|
-
|
|
4
|
+
Formatter,
|
|
5
5
|
PaddedRange,
|
|
6
6
|
ParsedLooseSequence,
|
|
7
7
|
ParsedSequence,
|
|
@@ -11,21 +11,23 @@ from ._ast import (
|
|
|
11
11
|
RangesStartName,
|
|
12
12
|
)
|
|
13
13
|
from ._base import BasePathSequence, BasePurePathSequence, PathT_co, PurePathT_co
|
|
14
|
-
from ._error import
|
|
15
|
-
from ._file_num_seq import FileNumSequence
|
|
14
|
+
from ._error import NotASequenceError, ParseError
|
|
15
|
+
from ._file_num_seq import FileNumSequence, FileNumT
|
|
16
|
+
from ._from_disk import Completeness
|
|
16
17
|
from ._loose_path_sequence import LoosePathSequence
|
|
17
18
|
from ._loose_pure_path_sequence import LoosePurePathSequence
|
|
18
19
|
from ._path_sequence import PathSequence
|
|
19
20
|
from ._pure_path_sequence import PurePathSequence
|
|
20
21
|
|
|
21
|
-
__version__ = "
|
|
22
|
+
__version__ = "1.0.0"
|
|
22
23
|
|
|
23
24
|
__all__ = (
|
|
24
25
|
"BasePathSequence",
|
|
25
26
|
"BasePurePathSequence",
|
|
27
|
+
"Completeness",
|
|
26
28
|
"FileNumSequence",
|
|
27
29
|
"FileNumT",
|
|
28
|
-
"
|
|
30
|
+
"Formatter",
|
|
29
31
|
"LoosePathSequence",
|
|
30
32
|
"LoosePurePathSequence",
|
|
31
33
|
"NotASequenceError",
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from ._base import FileNum, FileNumT, PaddedRange, Ranges
|
|
2
1
|
from ._formatter import Formatter
|
|
3
2
|
from ._loose_type import (
|
|
4
3
|
ParsedLooseSequence,
|
|
@@ -6,11 +5,11 @@ from ._loose_type import (
|
|
|
6
5
|
RangesInName,
|
|
7
6
|
RangesEndName,
|
|
8
7
|
)
|
|
8
|
+
from ._ranges import PaddedRange, Ranges
|
|
9
9
|
from ._type import ParsedSequence
|
|
10
|
+
from ._util import non_recursive_asdict
|
|
10
11
|
|
|
11
12
|
__all__ = [
|
|
12
|
-
"FileNum",
|
|
13
|
-
"FileNumT",
|
|
14
13
|
"Formatter",
|
|
15
14
|
"PaddedRange",
|
|
16
15
|
"ParsedLooseSequence",
|
|
@@ -19,4 +18,5 @@ __all__ = [
|
|
|
19
18
|
"RangesStartName",
|
|
20
19
|
"RangesInName",
|
|
21
20
|
"RangesEndName",
|
|
21
|
+
"non_recursive_asdict",
|
|
22
22
|
]
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
import dataclasses
|
|
5
|
+
from decimal import Decimal
|
|
6
|
+
import typing
|
|
7
|
+
|
|
8
|
+
if typing.TYPE_CHECKING:
|
|
9
|
+
from ._loose_type import ParsedLooseSequence
|
|
10
|
+
from ._ranges import PaddedRange, Ranges
|
|
11
|
+
from ._type import ParsedSequence
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _splice_strings_onto_ranges(
|
|
15
|
+
strings: Iterable[str], inter_ranges: Iterable[str]
|
|
16
|
+
) -> str:
|
|
17
|
+
"""Format the given range strings and inter-range strings together.
|
|
18
|
+
|
|
19
|
+
Raises:
|
|
20
|
+
TypeError: If the given number of strings does not match
|
|
21
|
+
the number of inter-range strings minus one.
|
|
22
|
+
"""
|
|
23
|
+
result = ""
|
|
24
|
+
|
|
25
|
+
strs = iter(strings)
|
|
26
|
+
for inter_range in inter_ranges:
|
|
27
|
+
try:
|
|
28
|
+
result += next(strs)
|
|
29
|
+
except StopIteration:
|
|
30
|
+
break
|
|
31
|
+
|
|
32
|
+
result += inter_range
|
|
33
|
+
else:
|
|
34
|
+
try:
|
|
35
|
+
result += next(strs)
|
|
36
|
+
except StopIteration:
|
|
37
|
+
pass
|
|
38
|
+
else:
|
|
39
|
+
try:
|
|
40
|
+
next(strs)
|
|
41
|
+
except StopIteration:
|
|
42
|
+
return result
|
|
43
|
+
|
|
44
|
+
raise TypeError(
|
|
45
|
+
"The number of inter-range strings given does not match"
|
|
46
|
+
" the number of range strings given minus one."
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Formatter:
|
|
51
|
+
"""A :class:`Formatter` formats a parsed sequence into a string.
|
|
52
|
+
|
|
53
|
+
The purpose of the class is primarily for converting from a
|
|
54
|
+
:ref:`simple <simple-format>` or :ref:`loose <loose-format>` sequence
|
|
55
|
+
into other sequence formats by overriding this class.
|
|
56
|
+
|
|
57
|
+
Example:
|
|
58
|
+
|
|
59
|
+
.. code-block:: pycon
|
|
60
|
+
|
|
61
|
+
>>> class HoudiniFormatter(pathseq.Formatter):
|
|
62
|
+
... def range(self, range_):
|
|
63
|
+
... return "$F"
|
|
64
|
+
...
|
|
65
|
+
>>> seq = pathseq.PathSequence("image.1-5#.exr")
|
|
66
|
+
>>> HoudiniFormatter().format(seq.parsed)
|
|
67
|
+
'image.$F.exr'
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self) -> None:
|
|
71
|
+
self.include_all_suffixes = False
|
|
72
|
+
"""Whether to include all suffixes in the formatted sequence.
|
|
73
|
+
|
|
74
|
+
This is set by :meth:`~.Formatter.format` to indicate whether the
|
|
75
|
+
suffixes have been cleanly separated from the stem or not.
|
|
76
|
+
If this is ``False``, then the ``stem`` and ``suffixes`` will overlap
|
|
77
|
+
and both include all but the last suffix.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def stem(self, stem: str) -> str:
|
|
81
|
+
return stem
|
|
82
|
+
|
|
83
|
+
def pre_range(self, pre_range: str) -> str:
|
|
84
|
+
return pre_range
|
|
85
|
+
|
|
86
|
+
def range(self, range_: PaddedRange[int] | PaddedRange[Decimal]) -> str:
|
|
87
|
+
return str(range_)
|
|
88
|
+
|
|
89
|
+
def inter_range(self, inter_range: str) -> str:
|
|
90
|
+
return inter_range
|
|
91
|
+
|
|
92
|
+
def ranges(self, ranges: Ranges) -> str:
|
|
93
|
+
return self.splice_inter_ranges(
|
|
94
|
+
(self.range(range_) for range_ in ranges.ranges),
|
|
95
|
+
(self.inter_range(inter_range) for inter_range in ranges.inter_ranges),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def post_range(self, post_range: str) -> str:
|
|
99
|
+
return post_range
|
|
100
|
+
|
|
101
|
+
def suffixes(self, suffixes: tuple[str, ...]) -> str:
|
|
102
|
+
return "".join(suffixes if self.include_all_suffixes else suffixes[-1:])
|
|
103
|
+
|
|
104
|
+
def format(self, seq: ParsedSequence | ParsedLooseSequence) -> str:
|
|
105
|
+
"""Format the given path sequence into a string.
|
|
106
|
+
|
|
107
|
+
The parsed sequence is traversed, and each node is formatted
|
|
108
|
+
according to the implementation of the associated method on this class.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
seq: The path sequence to format into a string.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
The formatter path sequence.
|
|
115
|
+
"""
|
|
116
|
+
from ._loose_type import RangesInName
|
|
117
|
+
from ._type import ParsedSequence
|
|
118
|
+
|
|
119
|
+
self.include_all_suffixes = bool(
|
|
120
|
+
isinstance(seq, ParsedSequence)
|
|
121
|
+
or (isinstance(seq, RangesInName) and seq.ranges.ranges)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return "".join(
|
|
125
|
+
getattr(self, field.name)(getattr(seq, field.name))
|
|
126
|
+
for field in dataclasses.fields(seq)
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def splice_inter_ranges(
|
|
130
|
+
self, strings: Iterable[str], inter_ranges: Iterable[str]
|
|
131
|
+
) -> str:
|
|
132
|
+
"""Format the given range strings and inter-range strings together.
|
|
133
|
+
|
|
134
|
+
Raises:
|
|
135
|
+
TypeError: If the given number of strings does not match
|
|
136
|
+
the number of inter-range strings minus one.
|
|
137
|
+
"""
|
|
138
|
+
return _splice_strings_onto_ranges(strings, inter_ranges)
|
|
@@ -5,18 +5,16 @@ from typing import Literal, TypeAlias, Union
|
|
|
5
5
|
|
|
6
6
|
from typing_extensions import Self # PY311
|
|
7
7
|
|
|
8
|
-
from .
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Ranges,
|
|
12
|
-
)
|
|
8
|
+
from ._formatter import Formatter
|
|
9
|
+
from ._ranges import Ranges
|
|
10
|
+
from ._util import non_recursive_asdict
|
|
13
11
|
|
|
14
12
|
|
|
15
13
|
@dataclass(frozen=True)
|
|
16
14
|
class RangesStartName:
|
|
17
15
|
"""A parsed loose path sequence where the range starts a path's name."""
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
pre_range: Literal[""]
|
|
20
18
|
"""An optional single character separator between the ranges and the previous component.
|
|
21
19
|
|
|
22
20
|
This is always empty for file names where the ranges start the name,
|
|
@@ -25,7 +23,7 @@ class RangesStartName:
|
|
|
25
23
|
"""
|
|
26
24
|
ranges: Ranges
|
|
27
25
|
"""The file numbers of the files in the sequence and their formatting."""
|
|
28
|
-
|
|
26
|
+
post_range: Literal["_", ""]
|
|
29
27
|
"""An optional single character separator between the ranges and the next component.
|
|
30
28
|
|
|
31
29
|
In sequences where the ranges start the name,
|
|
@@ -33,7 +31,7 @@ class RangesStartName:
|
|
|
33
31
|
:ref:`stem <format-loose-stem>`.
|
|
34
32
|
"""
|
|
35
33
|
stem: str
|
|
36
|
-
"""The name of the sequence, without the
|
|
34
|
+
"""The name of the sequence, without the pre-range separator, ranges, post-range separator, and suffixes."""
|
|
37
35
|
suffixes: tuple[str, ...]
|
|
38
36
|
"""The file extensions of the files in the path sequence.
|
|
39
37
|
|
|
@@ -41,17 +39,17 @@ class RangesStartName:
|
|
|
41
39
|
"""
|
|
42
40
|
|
|
43
41
|
def __str__(self) -> str:
|
|
44
|
-
return
|
|
42
|
+
return Formatter().format(self)
|
|
45
43
|
|
|
46
44
|
def with_stem(self, stem: str) -> Self:
|
|
47
45
|
"""Return a new parsed sequence with the :attr:`~.RangesStartName.stem` changed.
|
|
48
46
|
|
|
49
|
-
If the stem is removed, the
|
|
47
|
+
If the stem is removed, the post-range separator will be as well.
|
|
50
48
|
"""
|
|
51
49
|
kwargs = non_recursive_asdict(self)
|
|
52
50
|
kwargs["stem"] = stem
|
|
53
51
|
if not stem:
|
|
54
|
-
kwargs["
|
|
52
|
+
kwargs["post_range"] = ""
|
|
55
53
|
return self.__class__(**kwargs)
|
|
56
54
|
|
|
57
55
|
def with_suffix(self, suffix: str) -> Self:
|
|
@@ -86,8 +84,8 @@ class RangesInName:
|
|
|
86
84
|
"""A parsed loose range sequence where the range follows a path's stem."""
|
|
87
85
|
|
|
88
86
|
stem: str
|
|
89
|
-
"""The name of the sequence, without the
|
|
90
|
-
|
|
87
|
+
"""The name of the sequence, without the pre-range separator, ranges, post-range separator, and suffixes."""
|
|
88
|
+
pre_range: str
|
|
91
89
|
"""An optional single character separator between the ranges and the previous component.
|
|
92
90
|
|
|
93
91
|
In sequences where the ranges are in the name,
|
|
@@ -96,8 +94,8 @@ class RangesInName:
|
|
|
96
94
|
"""
|
|
97
95
|
ranges: Ranges
|
|
98
96
|
"""The file numbers of the files in the sequence and their formatting."""
|
|
99
|
-
|
|
100
|
-
"""An optional
|
|
97
|
+
post_range: str
|
|
98
|
+
"""An optional separator between the ranges and the next component.
|
|
101
99
|
|
|
102
100
|
In sequences where the ranges are in the name,
|
|
103
101
|
this separates the :ref:`ranges <format-loose-range>` from the
|
|
@@ -110,17 +108,17 @@ class RangesInName:
|
|
|
110
108
|
"""
|
|
111
109
|
|
|
112
110
|
def __str__(self) -> str:
|
|
113
|
-
return
|
|
111
|
+
return Formatter().format(self)
|
|
114
112
|
|
|
115
113
|
def with_stem(self, stem: str) -> Self:
|
|
116
114
|
"""Return a new parsed sequence with the :attr:`~.RangesInName.stem` changed.
|
|
117
115
|
|
|
118
|
-
If the stem is removed, the
|
|
116
|
+
If the stem is removed, the pre-range separator will be as well.
|
|
119
117
|
"""
|
|
120
118
|
kwargs = non_recursive_asdict(self)
|
|
121
119
|
kwargs["stem"] = stem
|
|
122
120
|
if not stem and self.stem:
|
|
123
|
-
kwargs["
|
|
121
|
+
kwargs["pre_range"] = ""
|
|
124
122
|
return self.__class__(**kwargs)
|
|
125
123
|
|
|
126
124
|
def with_suffix(self, suffix: str) -> Self:
|
|
@@ -146,7 +144,7 @@ class RangesInName:
|
|
|
146
144
|
kwargs = non_recursive_asdict(self)
|
|
147
145
|
kwargs["suffixes"] = self.suffixes[:-1]
|
|
148
146
|
if not kwargs["suffixes"]:
|
|
149
|
-
kwargs["
|
|
147
|
+
kwargs["post_range"] = ""
|
|
150
148
|
return self.__class__(**kwargs)
|
|
151
149
|
|
|
152
150
|
return self
|
|
@@ -157,13 +155,13 @@ class RangesEndName:
|
|
|
157
155
|
"""A parsed loose range sequence where the range ends a path's name."""
|
|
158
156
|
|
|
159
157
|
stem: str
|
|
160
|
-
"""The name of the sequence, without the
|
|
158
|
+
"""The name of the sequence, without the pre-range separator, ranges, post-range separator, and suffixes."""
|
|
161
159
|
suffixes: tuple[str, ...]
|
|
162
160
|
"""The file extensions of the files in the path sequence.
|
|
163
161
|
|
|
164
162
|
Each suffix includes the leading "``.``".
|
|
165
163
|
"""
|
|
166
|
-
|
|
164
|
+
pre_range: str
|
|
167
165
|
"""An optional single character separator between the ranges and the previous component.
|
|
168
166
|
|
|
169
167
|
In sequences where the ranges are in the name,
|
|
@@ -172,8 +170,8 @@ class RangesEndName:
|
|
|
172
170
|
"""
|
|
173
171
|
ranges: Ranges
|
|
174
172
|
"""The file numbers of the files in the sequence and their formatting."""
|
|
175
|
-
|
|
176
|
-
"""An optional
|
|
173
|
+
post_range: Literal[""]
|
|
174
|
+
"""An optional separator between the ranges and the next component.
|
|
177
175
|
|
|
178
176
|
This is always empty for file names where the ranges end the name,
|
|
179
177
|
but is included so that :class:`~.RangesStartName`, :class:`~.RangesInName`,
|
|
@@ -181,7 +179,7 @@ class RangesEndName:
|
|
|
181
179
|
"""
|
|
182
180
|
|
|
183
181
|
def __str__(self) -> str:
|
|
184
|
-
return
|
|
182
|
+
return Formatter().format(self)
|
|
185
183
|
|
|
186
184
|
def with_stem(self, stem: str) -> Self:
|
|
187
185
|
"""Return a new parsed sequence with the :attr:`~.RangesEndName.stem` changed."""
|