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.
Files changed (30) hide show
  1. {pathseq-0.1.0 → pathseq-1.0.0}/PKG-INFO +84 -13
  2. pathseq-1.0.0/README.rst +178 -0
  3. {pathseq-0.1.0 → pathseq-1.0.0}/pyproject.toml +2 -1
  4. {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/__init__.py +7 -5
  5. {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_ast/__init__.py +3 -3
  6. pathseq-1.0.0/src/pathseq/_ast/_formatter.py +138 -0
  7. {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_ast/_loose_type.py +22 -24
  8. pathseq-1.0.0/src/pathseq/_ast/_ranges.py +72 -0
  9. {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_ast/_type.py +9 -10
  10. pathseq-1.0.0/src/pathseq/_ast/_util.py +98 -0
  11. {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_base.py +58 -23
  12. {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_error.py +0 -24
  13. pathseq-1.0.0/src/pathseq/_file_num_seq/__init__.py +7 -0
  14. {pathseq-0.1.0/src/pathseq → pathseq-1.0.0/src/pathseq/_file_num_seq}/_arithmetic_sequence.py +1 -1
  15. {pathseq-0.1.0/src/pathseq → pathseq-1.0.0/src/pathseq/_file_num_seq}/_decimal_range.py +13 -11
  16. {pathseq-0.1.0/src/pathseq → pathseq-1.0.0/src/pathseq/_file_num_seq}/_file_num_seq.py +5 -3
  17. {pathseq-0.1.0/src/pathseq → pathseq-1.0.0/src/pathseq/_file_num_seq}/_parse_file_num_seq.py +1 -1
  18. pathseq-0.1.0/src/pathseq/_formatter.py → pathseq-1.0.0/src/pathseq/_formatters.py +2 -2
  19. {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_from_disk.py +58 -21
  20. {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_loose_pure_path_sequence.py +10 -35
  21. {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_parse_loose_path_sequence.py +121 -74
  22. {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_parse_path_sequence.py +22 -19
  23. pathseq-1.0.0/src/pathseq/_pure_path_sequence.py +25 -0
  24. pathseq-0.1.0/README.rst +0 -107
  25. pathseq-0.1.0/src/pathseq/_ast/_base.py +0 -236
  26. pathseq-0.1.0/src/pathseq/_ast/_formatter.py +0 -38
  27. pathseq-0.1.0/src/pathseq/_pure_path_sequence.py +0 -58
  28. {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_loose_path_sequence.py +0 -0
  29. {pathseq-0.1.0 → pathseq-1.0.0}/src/pathseq/_path_sequence.py +0 -0
  30. {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: 0.1.0
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 file sequences.
47
+ A pathlib-first library for working with path sequences.
48
48
 
49
- TODO: Note special features
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
- * Sequence compression?
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 through pip:
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 `black <https://github.com/python/black>`_.
168
+ Code is formatted using `ruff <https://docs.astral.sh/ruff/formatter/>`_.
98
169
 
99
- You can check your formatting using black's check mode:
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 black to format your changes for you:
176
+ You can also get ruff to format your changes for you:
106
177
 
107
178
  .. code-block:: bash
108
179
 
109
- tox -e format -- src/ tests/
180
+ tox -e autoformat
110
181
 
111
182
 
112
183
  Release Notes
@@ -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 = "0.1.0"
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
- FileNumT,
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 IncompleteDimensionError, NotASequenceError, ParseError
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__ = "0.1.0"
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
- "IncompleteDimensionError",
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 ._base import (
9
- non_recursive_asdict,
10
- stringify_parsed_sequence,
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
- prefix: Literal[""]
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
- postfix: str
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 prefix, ranges, postfix, and suffixes."""
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 stringify_parsed_sequence(self)
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 postfix will be as well.
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["postfix"] = ""
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 prefix, ranges, postfix, and suffixes."""
90
- prefix: str
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
- postfix: str
100
- """An optional single character separator between the ranges and the next component.
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 stringify_parsed_sequence(self)
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 prefix will be as well.
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["prefix"] = ""
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["postfix"] = ""
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 prefix, ranges, postfix, and suffixes."""
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
- prefix: str
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
- postfix: Literal[""]
176
- """An optional single character separator between the ranges and the next component.
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 stringify_parsed_sequence(self)
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."""