plexus-python-pulse-common 1.1.75__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.
@@ -0,0 +1,234 @@
1
+ import os
2
+ from collections.abc import Callable, Generator
3
+
4
+ import pyparsing as pp
5
+ from plexus.common.utils.funcutils import singleton
6
+ from plexus.common.utils.strutils import is_blank
7
+
8
+ __all__ = [
9
+ "volume_template_parser",
10
+ "make_volume_parser",
11
+ "make_volume_generator",
12
+ "collect_volumed_filenames",
13
+ "populate_volumed_filenames",
14
+ ]
15
+
16
+
17
+ @singleton
18
+ def volume_template_parser() -> pp.ParserElement:
19
+ lbrace_token = pp.Char("{")
20
+ rbrace_token = pp.Char("}")
21
+ colon_token = pp.Char(":")
22
+ chars_token = pp.Regex(r"[^{}]*")
23
+ name_token = pp.Regex(r"[a-z][a-z0-9_]*")
24
+
25
+ parser_expr = pp.Combine(lbrace_token +
26
+ (name_token("name") + colon_token)[0, 1] +
27
+ chars_token("prefix") +
28
+ lbrace_token +
29
+ rbrace_token +
30
+ chars_token("suffix") +
31
+ rbrace_token)
32
+ return parser_expr
33
+
34
+
35
+ def make_trivial_volume_func() -> Callable[[str], int]:
36
+ """
37
+ Creates a trivial volume function that generates unique integer volumes for each name.
38
+
39
+ The function maintains a generator for each unique name and increments the volume
40
+ value each time the function is called with the same name. If a new name is provided,
41
+ a new generator is created for that name.
42
+
43
+ :return: a callable function that takes a name (str) and returns the next integer volume.
44
+ """
45
+
46
+ def trivial_volume_generator() -> Generator[int, None, None]:
47
+ volume = 0
48
+ while True:
49
+ yield volume
50
+ volume += 1
51
+
52
+ volume_generators = {}
53
+
54
+ def volume_func(name: str) -> int:
55
+ if name not in volume_generators:
56
+ volume_generators[name] = trivial_volume_generator()
57
+ return next(volume_generators[name])
58
+
59
+ return volume_func
60
+
61
+
62
+ def make_volume_parser(template: str) -> Callable[[str], dict[str, int]]:
63
+ """
64
+ Creates a parser function for extracting volume information from filenames based on a given template.
65
+
66
+ The template defines the structure of filenames, including placeholders for volume values.
67
+ The returned parser function takes a filename as input and extracts the volume values
68
+ as a dictionary where the keys are the placeholder names and the values are the corresponding integers.
69
+
70
+ :param template: a string template defining the filename structure with placeholders for volumes.
71
+ Placeholders are enclosed in double curly braces `{{}}` and can optionally include
72
+ a name, a prefix, and a suffix (e.g., `{name:prefix{}suffix}`).
73
+
74
+ :return: a callable function that takes a filename (str) and returns a dictionary mapping
75
+ placeholder names to their extracted integer values.
76
+
77
+ :raises ValueError: if the template is invalid or cannot be parsed.
78
+ """
79
+ try:
80
+ scan_result = list(volume_template_parser().scan_string(template, overlap=False))
81
+ except pp.ParseException as e:
82
+ raise ValueError(f"bad template '{template}'") from e
83
+
84
+ volume_token = pp.Regex(r"\d+")
85
+
86
+ parser_expr = pp.Literal("")
87
+ prev_end_pos = 0
88
+ index = 0
89
+ for parse_results, begin_pos, end_pos in scan_result:
90
+ if prev_end_pos < begin_pos:
91
+ parser_expr = parser_expr + pp.Literal(template[prev_end_pos:begin_pos])
92
+ prefix = parse_results.get("prefix")
93
+ suffix = parse_results.get("suffix")
94
+ name = parse_results.get("name")
95
+ if is_blank(name):
96
+ name = str(index)
97
+ index += 1
98
+ if not is_blank(prefix):
99
+ parser_expr = parser_expr + pp.Literal(prefix)
100
+ parser_expr = parser_expr + volume_token(name)
101
+ if not is_blank(suffix):
102
+ parser_expr = parser_expr + pp.Literal(suffix)
103
+ prev_end_pos = end_pos
104
+ if prev_end_pos < len(template):
105
+ parser_expr = parser_expr + pp.Literal(template[prev_end_pos:])
106
+
107
+ parser_expr = pp.Combine(pp.StringStart() + parser_expr + pp.StringEnd())
108
+
109
+ def parser(s: str) -> dict[str, int]:
110
+ parser_results = parser_expr.parse_string(s, parse_all=True)
111
+
112
+ volumes = {}
113
+ for name in parser_results.keys():
114
+ volume = parser_results.get(name)
115
+ volumes[name] = int(volume)
116
+
117
+ return volumes
118
+
119
+ return parser
120
+
121
+
122
+ def make_volume_generator(template: str) -> Callable[[Callable[[str], int]], tuple[str, dict[str, int]]]:
123
+ """
124
+ Creates a generator function for producing filenames and their associated volume values
125
+ based on a given template.
126
+
127
+ The template defines the structure of filenames, including placeholders for volume values.
128
+ The returned generator function takes a volume function as input, which determines the
129
+ volume values for each placeholder, and produces filenames with the corresponding volumes.
130
+
131
+ :param template: a string template defining the filename structure with placeholders for volumes.
132
+ Placeholders are enclosed in double curly braces `{{}}` and can optionally include
133
+ a name, a prefix, and a suffix (e.g., `{name:prefix{}suffix}`).
134
+
135
+ :return: a callable generator function that takes a volume function (Callable[[str], int]) as input
136
+ and returns a tuple containing the generated filename (str) and a dictionary mapping
137
+ placeholder names to their corresponding volume values (dict[str, int]).
138
+
139
+ :raises ValueError: if the template is invalid or cannot be parsed.
140
+ """
141
+ try:
142
+ scan_result = list(volume_template_parser().scan_string(template, overlap=False))
143
+ except pp.ParseException as e:
144
+ raise ValueError(f"bad template '{template}'") from e
145
+
146
+ def generator(volume_func: Callable[[str], int]) -> tuple[str, dict[str, int]]:
147
+ volumes = {}
148
+
149
+ volume_expr = ""
150
+ prev_end_pos = 0
151
+ index = 0
152
+ for parse_results, begin_pos, end_pos in scan_result:
153
+ if prev_end_pos < begin_pos:
154
+ volume_expr = volume_expr + template[prev_end_pos:begin_pos]
155
+ prefix = parse_results.get("prefix")
156
+ suffix = parse_results.get("suffix")
157
+ name = parse_results.get("name")
158
+ if is_blank(name):
159
+ name = str(index)
160
+ index += 1
161
+ if not is_blank(prefix):
162
+ volume_expr = volume_expr + prefix
163
+ volumes[name] = int(volume_func(name))
164
+ volume_expr = volume_expr + str(volumes[name])
165
+ if not is_blank(suffix):
166
+ volume_expr = volume_expr + suffix
167
+ prev_end_pos = end_pos
168
+ if prev_end_pos < len(template):
169
+ volume_expr = volume_expr + template[prev_end_pos:]
170
+
171
+ return volume_expr, volumes
172
+
173
+ return generator
174
+
175
+
176
+ def collect_volumed_filenames(template: str) -> Generator[tuple[str, dict[str, int]], None, None]:
177
+ """
178
+ Collects filenames in a folder that match a given template and extracts their volume information.
179
+
180
+ The template defines the structure of filenames, including placeholders for volume values.
181
+ This function scans the folder containing the template and attempts to parse filenames
182
+ to extract volume values based on the template.
183
+
184
+ :param template: a string template defining the filename structure with placeholders for volumes.
185
+ Placeholders are enclosed in double curly braces `{{}}` and can optionally include
186
+ a name, a prefix, and a suffix (e.g., `{name:prefix{}suffix}`).
187
+
188
+ :return: a generator yielding tuples where the first element is the full path of the filename
189
+ and the second element is a dictionary mapping placeholder names to their extracted
190
+ integer volume values.
191
+ """
192
+ folder = os.path.dirname(template)
193
+ basename = os.path.basename(template)
194
+
195
+ parser = make_volume_parser(basename)
196
+
197
+ for name in os.listdir(folder):
198
+ try:
199
+ volumes = parser(name)
200
+ yield os.path.join(folder, name), volumes
201
+ except pp.ParseException:
202
+ pass
203
+
204
+
205
+ def populate_volumed_filenames(
206
+ template: str,
207
+ *,
208
+ volume_func: Callable[[str], int] = None,
209
+ ) -> Generator[tuple[str, dict[str, int]], None, None]:
210
+ """
211
+ Generates filenames and their associated volume values based on a given template.
212
+
213
+ The template defines the structure of filenames, including placeholders for volume values.
214
+ This function uses a volume function to generate unique volume values for each placeholder
215
+ and produces filenames with the corresponding volumes.
216
+
217
+ :param template: a string template defining the filename structure with placeholders for volumes.
218
+ Placeholders are enclosed in double curly braces `{{}}` and can optionally include
219
+ a name, a prefix, and a suffix (e.g., `{name:prefix{}suffix}`).
220
+ :param volume_func: a callable function that takes a placeholder name (str) and returns the next
221
+ integer volume value. If not provided, a trivial volume function is used.
222
+
223
+ :return: a generator yielding tuples where the first element is the generated filename (str)
224
+ and the second element is a dictionary mapping placeholder names to their corresponding
225
+ """
226
+ folder = os.path.dirname(template)
227
+ basename = os.path.basename(template)
228
+
229
+ generator = make_volume_generator(basename)
230
+ volume_func = volume_func or make_trivial_volume_func()
231
+
232
+ while True:
233
+ name, volumes = generator(volume_func)
234
+ yield os.path.join(folder, name), volumes