PySerials 0.0.0.dev32__py3-none-any.whl → 0.0.0.dev34__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PySerials
3
- Version: 0.0.0.dev32
3
+ Version: 0.0.0.dev34
4
4
  Requires-Python: >=3.10
5
5
  Requires-Dist: jsonschema <5,>=4.21.0
6
6
  Requires-Dist: referencing >=0.35.1
@@ -8,7 +8,7 @@ Requires-Dist: jsonpath-ng <2,>=1.6.1
8
8
  Requires-Dist: ruamel.yaml <0.18,>=0.17.32
9
9
  Requires-Dist: ruamel.yaml.string <1,>=0.1.1
10
10
  Requires-Dist: tomlkit <0.12,>=0.11.8
11
- Requires-Dist: MDit ==0.0.0.dev29
12
- Requires-Dist: ExceptionMan ==0.0.0.dev29
11
+ Requires-Dist: MDit ==0.0.0.dev31
12
+ Requires-Dist: ExceptionMan ==0.0.0.dev31
13
13
  Requires-Dist: ProtocolMan ==0.0.0.dev2
14
14
 
@@ -1,9 +1,9 @@
1
1
  pyserials/__init__.py,sha256=-ySdqDuoUXdi2Pa8uuFa5m1CTAtbZS3SWc5qzaOdR5o,142
2
2
  pyserials/compare.py,sha256=j62A1UIiAm08_xONlbZmU2EcH1GMEpDyEQH66dZ2YMM,1297
3
3
  pyserials/format.py,sha256=dTukpab6WHSyVRQ9SteY5fhr3GFjWFboEl-1cw_udVY,1729
4
- pyserials/nested_dict.py,sha256=5LIeWIEW-m0h9BLh3VughakjQtD41CDEtISV3z6L83I,6326
4
+ pyserials/nested_dict.py,sha256=fj6nPbARw0XsYu8Ohym2DC_IYDTtdtOeV_FPE9Iylq8,4891
5
5
  pyserials/read.py,sha256=uucYQH1V4GStwRgRZ2eQIXkH4ukB5qz0EA885grwi68,6592
6
- pyserials/update.py,sha256=GjMpH36tzzXwuAjuOMMvVYqc7tUoagEBCyeKryUCi8U,20394
6
+ pyserials/update.py,sha256=tgkd7mywiYvm2tE_jPjuRWYsAoQ2Ap4NQ-OCI0ui9n0,21625
7
7
  pyserials/validate.py,sha256=ti0D_yLzB_HELvf1d5qrarx1Ac-opBMN1Xh5lADRAQU,3664
8
8
  pyserials/write.py,sha256=pN8w78qVsKJjZd_jvPUcZjYp_RJkP7uQzpiXvPOv4lM,1776
9
9
  pyserials/exception/__init__.py,sha256=ZhbggwJUMlTyBhifAivC8ZQxP1Na6lJAwzZs7_YjOSU,151
@@ -11,7 +11,7 @@ pyserials/exception/_base.py,sha256=IdaZwBPBYgiUaWnvN0eMXvQQBqLbN1t766034CK7Hlc,
11
11
  pyserials/exception/read.py,sha256=QyG6ulExXH9u8oDRjUfter70SMDVQqL4nig5s-JzWN4,9252
12
12
  pyserials/exception/update.py,sha256=P0js2-iY2fgO_KLdqedgWE3TTS5Xz15cusZY_wuKIW4,4222
13
13
  pyserials/exception/validate.py,sha256=7UkQEEqCa8HJ20gpTFnLDhT3P5OPLD2oD9fUK2Jcuns,7466
14
- PySerials-0.0.0.dev32.dist-info/METADATA,sha256=nbXlgbdES_rAnqwaGF_LiMMwR8r1xETVxO5mAeWNtr4,438
15
- PySerials-0.0.0.dev32.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
16
- PySerials-0.0.0.dev32.dist-info/top_level.txt,sha256=SAks7WjSjdkv3i9Hvt4gY_P7VQbhhYJN5mf5dqx1aao,10
17
- PySerials-0.0.0.dev32.dist-info/RECORD,,
14
+ PySerials-0.0.0.dev34.dist-info/METADATA,sha256=CbyD5MSSMGJg6z7oT64_ACTdEnZBi5uHxywjLZ1d1Pw,438
15
+ PySerials-0.0.0.dev34.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
16
+ PySerials-0.0.0.dev34.dist-info/top_level.txt,sha256=SAks7WjSjdkv3i9Hvt4gY_P7VQbhhYJN5mf5dqx1aao,10
17
+ PySerials-0.0.0.dev34.dist-info/RECORD,,
pyserials/nested_dict.py CHANGED
@@ -24,15 +24,17 @@ class NestedDict:
24
24
  end_list: str = "]]$",
25
25
  end_unpack: str = "}}*",
26
26
  end_code: str = "}}#",
27
- recursive: bool = True,
28
27
  raise_no_match: bool = True,
29
28
  leave_no_match: bool = False,
30
29
  no_match_value: Any = None,
31
30
  code_context: dict[str, Any] | None = None,
31
+ code_context_partial: dict[str, Callable | tuple[Callable, str]] | None = None,
32
+ code_context_call: dict[str, Callable[[Callable], Any]] | None = None,
32
33
  stringer: Callable[[str], str] = str,
33
34
  unpack_string_joiner: str = ", ",
34
35
  relative_template_keys: list[str] | None = None,
35
36
  implicit_root: bool = True,
37
+ getter_function_name: str = "get",
36
38
  ):
37
39
  self._data = data or {}
38
40
  self._templater = _ps.update.TemplateFiller(
@@ -47,29 +49,21 @@ class NestedDict:
47
49
  end_list=end_list,
48
50
  end_unpack=end_unpack,
49
51
  end_code=end_code,
52
+ raise_no_match=raise_no_match,
53
+ leave_no_match=leave_no_match,
54
+ no_match_value=no_match_value,
55
+ code_context=code_context,
56
+ code_context_partial=code_context_partial,
57
+ code_context_call=code_context_call,
58
+ stringer=stringer,
59
+ unpack_string_joiner=unpack_string_joiner,
60
+ relative_template_keys=relative_template_keys,
61
+ implicit_root=implicit_root,
62
+ getter_function_name=getter_function_name,
50
63
  )
51
- self._recursive = recursive
52
- self._raise_no_match = raise_no_match
53
- self._leave_no_match = leave_no_match
54
- self._no_match_value = no_match_value
55
- self._code_context = code_context or {}
56
- self._stringer = stringer
57
- self._unpack_string_joiner = unpack_string_joiner
58
- self._relative_template_keys = relative_template_keys or []
59
- self._implicit_root = implicit_root
60
64
  return
61
65
 
62
- def fill(
63
- self,
64
- path: str = "",
65
- recursive: bool | None = None,
66
- raise_no_match: bool | None = None,
67
- leave_no_match: bool | None = None,
68
- code_context: dict[str, Any] | None = None,
69
- stringer: Callable[[str], str] | None = None,
70
- unpack_string_joiner: str | None = None,
71
- level: int = 0,
72
- ):
66
+ def fill(self, path: str = ""):
73
67
  if not path:
74
68
  value = self._data
75
69
  else:
@@ -79,13 +73,6 @@ class NestedDict:
79
73
  filled_value = self.fill_data(
80
74
  data=value,
81
75
  current_path=path,
82
- recursive=recursive,
83
- raise_no_match=raise_no_match,
84
- leave_no_match=leave_no_match,
85
- code_context=code_context,
86
- stringer=stringer,
87
- unpack_string_joiner=unpack_string_joiner,
88
- level=level,
89
76
  )
90
77
  if not path:
91
78
  self._data = filled_value
@@ -93,32 +80,11 @@ class NestedDict:
93
80
  self.__setitem__(path, filled_value)
94
81
  return filled_value
95
82
 
96
- def fill_data(
97
- self,
98
- data,
99
- current_path: str = "",
100
- recursive: bool | None = None,
101
- raise_no_match: bool | None = None,
102
- leave_no_match: bool | None = None,
103
- stringer: Callable[[str], str] | None = None,
104
- code_context: dict[str, Any] | None = None,
105
- unpack_string_joiner: str | None = None,
106
- level: int = 0,
107
- ):
83
+ def fill_data(self, data, current_path: str = ""):
108
84
  return self._templater.fill(
109
- templated_data=data,
110
- source_data=self._data,
85
+ data=self._data,
86
+ template=data,
111
87
  current_path=current_path,
112
- recursive=recursive if recursive is not None else self._recursive,
113
- raise_no_match=raise_no_match if raise_no_match is not None else self._raise_no_match,
114
- leave_no_match=leave_no_match if leave_no_match is not None else self._leave_no_match,
115
- no_match_value=self._no_match_value,
116
- code_context=code_context if code_context is not None else self._code_context,
117
- stringer=stringer if stringer is not None else self._stringer,
118
- unpack_string_joiner=unpack_string_joiner if unpack_string_joiner is not None else self._unpack_string_joiner,
119
- relative_template_keys=self._relative_template_keys,
120
- implicit_root=self._implicit_root,
121
- level=level,
122
88
  )
123
89
 
124
90
  def __call__(self):
pyserials/update.py CHANGED
@@ -3,6 +3,7 @@ from __future__ import annotations as _annotations
3
3
  import re
4
4
  from typing import TYPE_CHECKING as _TYPE_CHECKING
5
5
  import re as _re
6
+ from functools import partial as _partial
6
7
 
7
8
  import jsonpath_ng as _jsonpath
8
9
  from jsonpath_ng import exceptions as _jsonpath_exceptions
@@ -116,6 +117,17 @@ class TemplateFiller:
116
117
  end_list: str = "]]$",
117
118
  end_unpack: str = "}}*",
118
119
  end_code: str = "}}#",
120
+ raise_no_match: bool = True,
121
+ leave_no_match: bool = False,
122
+ no_match_value: Any = None,
123
+ code_context: dict[str, Any] | None = None,
124
+ code_context_partial: dict[str, Callable | tuple[Callable, str]] | None = None,
125
+ code_context_call: dict[str, Callable[[Callable], Any]] | None = None,
126
+ stringer: Callable[[str], str] = str,
127
+ unpack_string_joiner: str = ", ",
128
+ relative_template_keys: list[str] | None = None,
129
+ implicit_root: bool = True,
130
+ getter_function_name: str = "get",
119
131
  ):
120
132
  self._marker_start_value = marker_start_value
121
133
  self._marker_end_value = marker_end_value
@@ -125,99 +137,64 @@ class TemplateFiller:
125
137
  self._pattern_list = _RegexPattern(start=start_list, end=end_list)
126
138
  self._pattern_unpack = _RegexPattern(start=start_unpack, end=end_unpack)
127
139
  self._pattern_code = _RegexPattern(start=start_code, end=end_code)
128
- self._add_prefix = True
129
-
130
- self._pattern_value: dict[int, _RegexPattern] = {}
131
- self._data = None
132
- self._source = None
133
- self._recursive = None
134
- self._path = None
135
- self._raise_no_match = None
136
- self._template_keys = None
137
- self._ignore_templates = True
138
- self._leave_no_match = False
139
- self._no_match_value = None
140
- self._code_context = {}
141
- self._stringer = str
142
- self._unpack_string_joiner = ", "
143
- self._path_history = []
144
- return
145
-
146
- def _get_value_regex_pattern(self, level: int = 0) -> _RegexPattern:
147
- level_patterns = self._pattern_value.setdefault(level, {})
148
- if level in level_patterns:
149
- return level_patterns[level]
150
- count = self._repeater_count_value + level
151
- pattern = _RegexPattern(
152
- start=f"{self._marker_start_value}{self._repeater_start_value * count} ",
153
- end=f" {self._repeater_end_value * count}{self._marker_end_value}",
154
- )
155
- level_patterns[level] = pattern
156
- return pattern
157
140
 
158
- def fill(
159
- self,
160
- templated_data: dict | list | str,
161
- source_data: dict | list,
162
- current_path: str = "",
163
- recursive: bool = True,
164
- raise_no_match: bool = True,
165
- leave_no_match: bool = False,
166
- no_match_value: Any = None,
167
- code_context: dict[str, Any] | None = None,
168
- stringer: Callable[[str], str] = str,
169
- unpack_string_joiner: str = ", ",
170
- relative_template_keys: list[str] | None = None,
171
- implicit_root: bool = True,
172
- level: int = 0,
173
- ):
174
- self._data = templated_data
175
- self._source = source_data
176
- self._recursive = recursive
177
141
  self._raise_no_match = raise_no_match
178
142
  self._leave_no_match = leave_no_match
179
143
  self._no_match_value = no_match_value
180
144
  self._code_context = code_context or {}
145
+ self._code_context_partial = code_context_partial or {}
146
+ self._code_context_call = code_context_call or {}
181
147
  self._stringer = stringer
182
148
  self._unpack_string_joiner = unpack_string_joiner
183
149
  self._add_prefix = implicit_root
184
150
  self._template_keys = relative_template_keys or []
185
- self._path_history = []
151
+ self._getter_function_name = getter_function_name
152
+
153
+ self._pattern_value: dict[int, _RegexPattern] = {}
154
+ self._data = None
155
+ return
156
+
157
+ def fill(
158
+ self,
159
+ data: dict | list,
160
+ template: dict | list | str | None = None,
161
+ current_path: str = "",
162
+ ):
163
+ self._data = data
186
164
  path = (f"$.{current_path}" if self._add_prefix else current_path) if current_path else "$"
187
- if not relative_template_keys:
188
- self._ignore_templates = False
189
- return self._recursive_subst(
190
- templ=self._data,
191
- current_path=path,
192
- relative_path_anchor=path,
193
- level=level,
194
- )
195
- self._ignore_templates = True
196
- first_pass = self._recursive_subst(
197
- templ=self._data,
198
- current_path=path,
199
- relative_path_anchor=path,
200
- level=level,
201
- )
202
- if self._data is self._source:
203
- self._source = first_pass
204
- self._data = first_pass
205
- self._ignore_templates = False
206
- self._path_history = []
207
165
  return self._recursive_subst(
208
- templ=self._data,
166
+ templ=template or data,
209
167
  current_path=path,
210
168
  relative_path_anchor=path,
211
- level=level,
169
+ level=0,
170
+ current_chain=[path],
212
171
  )
213
172
 
214
- def _recursive_subst(self, templ, current_path: str, relative_path_anchor: str, level: int, internal=False):
173
+ def _recursive_subst(self, templ, current_path: str, relative_path_anchor: str, level: int, current_chain: list[str]):
174
+
175
+ def get_code_value(match: _re.Match | str):
176
+
177
+ def getter_function(path: str, default: Any = None, search: bool = False):
178
+ value, matched = get_address_value(path, return_all_matches=search, from_code=True)
179
+ if matched:
180
+ return value
181
+ if search:
182
+ return []
183
+ return default
215
184
 
216
- def get_code_value(code_str: str):
185
+ code_str = match if isinstance(match, str) else match.group(1)
217
186
  code_lines = ["def __inline_code__():"]
218
187
  code_lines.extend([f" {line}" for line in code_str.strip("\n").splitlines()])
219
188
  code_str_full = "\n".join(code_lines)
220
- global_context = self._code_context.copy()
189
+ global_context = self._code_context.copy() | {self._getter_function_name: getter_function}
190
+ for name, partial_func_data in self._code_context_partial.items():
191
+ if isinstance(partial_func_data, tuple):
192
+ func, arg_name = partial_func_data
193
+ global_context[name] = _partial(func, **{arg_name: getter_function})
194
+ else:
195
+ global_context[name] = _partial(partial_func_data, getter_function)
196
+ for name, call_func in self._code_context_call.items():
197
+ global_context[name] = call_func(getter_function)
221
198
  local_context = {}
222
199
  try:
223
200
  exec(code_str_full, global_context, local_context)
@@ -228,8 +205,9 @@ class TemplateFiller:
228
205
  path_invalid=current_path,
229
206
  )
230
207
 
231
- def get_address_value(re_match, return_all_matches: bool = False):
232
- path, num_periods = self._remove_leading_periods(re_match.group(1).strip())
208
+ def get_address_value(match: _re.Match | str, return_all_matches: bool = False, from_code: bool = False):
209
+ raw_path = match if isinstance(match, str) else str(match.group(1))
210
+ path, num_periods = self._remove_leading_periods(raw_path.strip())
233
211
  if num_periods == 0:
234
212
  path = f"$.{path}" if self._add_prefix else path
235
213
  try:
@@ -239,13 +217,14 @@ class TemplateFiller:
239
217
  path_invalid=path,
240
218
  description_template="JSONPath expression {path_invalid} is invalid.",
241
219
  )
242
- if self._ignore_templates:
243
- path_fields = self._extract_fields(path_expr)
244
- has_template_key = any(field in self._template_keys for field in path_fields)
245
- if has_template_key:
246
- return re_match.string
247
220
  if num_periods:
248
- root_path_expr = _jsonpath.parse(relative_path_anchor)
221
+ if relative_path_anchor != current_path:
222
+ path_fields = self._extract_fields(_jsonpath.parse(current_path))
223
+ has_template_key = any(field in self._template_keys for field in path_fields)
224
+ anchor_path = relative_path_anchor if has_template_key else current_path
225
+ else:
226
+ anchor_path = current_path
227
+ root_path_expr = _jsonpath.parse(anchor_path)
249
228
  for period in range(num_periods):
250
229
  if isinstance(root_path_expr, _jsonpath.Root):
251
230
  raise_error(
@@ -256,17 +235,21 @@ class TemplateFiller:
256
235
  ),
257
236
  )
258
237
  root_path_expr = root_path_expr.left
259
- path_expr = _jsonpath.Child(root_path_expr, path_expr)
260
- value, matched = get_value(path_expr, return_all_matches)
238
+ path_expr = self._concat_json_paths(root_path_expr, path_expr)
239
+ value, matched = get_value(path_expr, return_all_matches, from_code)
240
+ if from_code:
241
+ return value, matched
261
242
  if matched:
262
243
  return value
263
244
  if self._leave_no_match:
264
- return re_match.group()
245
+ return match.group()
265
246
  return self._no_match_value
266
247
 
267
- def get_value(jsonpath, return_all_matches: bool) -> tuple[Any, bool]:
248
+ def get_value(jsonpath, return_all_matches: bool, from_code: bool) -> tuple[Any, bool]:
268
249
  matches = _rec_match(jsonpath)
269
250
  if not matches:
251
+ if from_code:
252
+ return None, False
270
253
  if return_all_matches:
271
254
  return [], True
272
255
  if self._raise_no_match:
@@ -277,8 +260,6 @@ class TemplateFiller:
277
260
  return None, False
278
261
  values = [m.value for m in matches]
279
262
  output = values if return_all_matches or len(values) > 1 else values[0]
280
- if not self._recursive:
281
- return output, True
282
263
  if relative_path_anchor == current_path:
283
264
  path_fields = self._extract_fields(jsonpath)
284
265
  has_template_key = any(field in self._template_keys for field in path_fields)
@@ -290,10 +271,11 @@ class TemplateFiller:
290
271
  current_path=str(jsonpath),
291
272
  relative_path_anchor=_rel_path_anchor,
292
273
  level=0,
274
+ current_chain=current_chain + [str(jsonpath)],
293
275
  ), True
294
276
 
295
277
  def _rec_match(expr) -> list:
296
- matches = expr.find(self._source)
278
+ matches = expr.find(self._data)
297
279
  if matches:
298
280
  return matches
299
281
  if isinstance(expr.left, _jsonpath.Root):
@@ -306,6 +288,7 @@ class TemplateFiller:
306
288
  current_path=str(expr.left),
307
289
  relative_path_anchor=str(expr.left),
308
290
  level=0,
291
+ current_chain=current_chain + [str(expr.left)],
309
292
  ) if isinstance(left_match.value, str) else left_match.value
310
293
  right_matches = expr.right.find(left_match_filled)
311
294
  whole_matches.extend(right_matches)
@@ -314,33 +297,44 @@ class TemplateFiller:
314
297
  def get_relative_path(new_path):
315
298
  return new_path if current_path == relative_path_anchor else relative_path_anchor
316
299
 
317
- def raise_error(
318
- path_invalid: str,
319
- description_template: str,
320
- ):
321
- raise _exception.update.PySerialsUpdateTemplatedDataError(
322
- description_template=description_template,
323
- path_invalid=path_invalid,
324
- path=current_path,
325
- data=templ,
326
- data_full=self._data,
327
- data_source=self._source,
328
- template_start=self._marker_start_value,
329
- template_end=self._marker_end_value,
300
+ def fill_nested_values(match: _re.Match | str):
301
+ pattern_nested = self._get_value_regex_pattern(level=level + 1)
302
+ return pattern_nested.sub(
303
+ lambda x: self._recursive_subst(
304
+ templ=x.group(),
305
+ current_path=current_path,
306
+ relative_path_anchor=get_relative_path(current_path),
307
+ level=level + 1,
308
+ current_chain=current_chain,
309
+ ),
310
+ match if isinstance(match, str) else match.group(1),
330
311
  )
331
312
 
332
313
  def string_filler_unpack(match: _re.Match):
333
- match_list = self._pattern_list.fullmatch(match.group(1).strip())
314
+ path = str(match.group(1)).strip()
315
+ match_list = self._pattern_list.fullmatch(path)
334
316
  if match_list:
335
317
  values = get_address_value(match_list, return_all_matches=True)
336
318
  else:
337
- match_code = self._pattern_code.fullmatch(match.group(1).strip())
319
+ match_code = self._pattern_code.fullmatch(path)
338
320
  if match_code:
339
- values = get_code_value(match_code.group(1))
321
+ values = get_code_value(match_code)
340
322
  else:
341
- values = get_address_value(match)
323
+ values = get_address_value(path)
342
324
  return self._unpack_string_joiner.join([self._stringer(val) for val in values])
343
325
 
326
+ def raise_error(path_invalid: str, description_template: str):
327
+ raise _exception.update.PySerialsUpdateTemplatedDataError(
328
+ description_template=description_template,
329
+ path_invalid=path_invalid,
330
+ path=current_path,
331
+ data=templ,
332
+ data_full=self._data,
333
+ data_source=self._data,
334
+ template_start=self._marker_start_value,
335
+ template_end=self._marker_end_value,
336
+ )
337
+
344
338
  # if not internal:
345
339
  # self._path_history.append(current_path)
346
340
  # loop = self._find_loop()
@@ -358,49 +352,47 @@ class TemplateFiller:
358
352
  # )
359
353
 
360
354
  if isinstance(templ, str):
361
- pattern_nested = self._get_value_regex_pattern(level=level + 1)
362
- templ_nested_filled = pattern_nested.sub(
363
- lambda x: self._recursive_subst(
364
- templ=x.group(),
365
- current_path=current_path,
366
- relative_path_anchor=get_relative_path(current_path),
367
- level=level+1,
368
- internal=True,
369
- ),
370
- templ
371
- )
355
+ # Handle value blocks
372
356
  pattern_value = self._get_value_regex_pattern(level=level)
373
- whole_match_value = pattern_value.fullmatch(templ_nested_filled)
374
- if whole_match_value:
375
- return get_address_value(whole_match_value)
376
- templ_values_filled = pattern_value.sub(
377
- lambda x: str(get_address_value(x)),
378
- templ_nested_filled
379
- )
380
- whole_match_list = self._pattern_list.fullmatch(templ_values_filled.strip())
381
- if whole_match_list:
382
- return get_address_value(whole_match_list, return_all_matches=True)
383
- whole_match_unpack = self._pattern_unpack.fullmatch(templ_values_filled.strip())
384
- if whole_match_unpack:
385
- submatch_list = self._pattern_list.fullmatch(whole_match_unpack.group(1).strip())
386
- if submatch_list:
387
- return get_address_value(submatch_list, return_all_matches=True)
388
- submatch_code = self._pattern_code.fullmatch(whole_match_unpack.group(1).strip())
357
+ match_value = pattern_value.fullmatch(templ)
358
+ if match_value:
359
+ return get_address_value(fill_nested_values(match_value))
360
+ # Handle list blocks
361
+ match_list = self._pattern_list.fullmatch(templ)
362
+ if match_list:
363
+ return get_address_value(fill_nested_values(match_list), return_all_matches=True)
364
+ # Handle code blocks
365
+ match_code = self._pattern_code.fullmatch(templ)
366
+ if match_code:
367
+ return get_code_value(match_code)
368
+ # Handle unpack blocks
369
+ match_unpack = self._pattern_unpack.fullmatch(templ)
370
+ if match_unpack:
371
+ unpack_value = match_unpack.group(1)
372
+ submatch_code = self._pattern_code.fullmatch(unpack_value)
389
373
  if submatch_code:
390
- return get_code_value(submatch_code.group(1))
391
- return get_address_value(whole_match_unpack)
392
- whole_match_code = self._pattern_code.fullmatch(templ_values_filled.strip())
393
- if whole_match_code:
394
- templ_list_filled = self._pattern_list.sub(
395
- lambda x: str(get_address_value(x, return_all_matches=True)),
396
- whole_match_code.group(1)
397
- )
398
- return get_code_value(templ_list_filled)
399
- unpacked_filled = self._pattern_unpack.sub(string_filler_unpack, templ_values_filled)
400
- return self._pattern_code.sub(
401
- lambda x: self._stringer(get_code_value(x.group(1))),
374
+ return get_code_value(submatch_code)
375
+ unpack_value = fill_nested_values(unpack_value)
376
+ submatch_list = self._pattern_list.fullmatch(unpack_value)
377
+ if submatch_list:
378
+ return get_address_value(unpack_value, return_all_matches=True)
379
+ return get_address_value(unpack_value)
380
+ # Handle strings
381
+ code_blocks_filled = self._pattern_code.sub(
382
+ lambda x: self._stringer(get_code_value(x)),
383
+ templ
384
+ )
385
+ nested_values_filled = fill_nested_values(code_blocks_filled)
386
+ unpacked_filled = self._pattern_unpack.sub(string_filler_unpack, nested_values_filled)
387
+ lists_filled = self._pattern_list.sub(
388
+ lambda x: self._stringer(get_address_value(x)),
402
389
  unpacked_filled
403
390
  )
391
+ templ_values_filled = pattern_value.sub(
392
+ lambda x: self._stringer(get_address_value(x)),
393
+ lists_filled
394
+ )
395
+ return templ_values_filled
404
396
 
405
397
  if isinstance(templ, list):
406
398
  out = []
@@ -411,8 +403,9 @@ class TemplateFiller:
411
403
  current_path=new_path,
412
404
  relative_path_anchor=get_relative_path(new_path),
413
405
  level=0,
406
+ current_chain=current_chain + [new_path],
414
407
  )
415
- if isinstance(elem, str) and self._pattern_unpack.fullmatch(elem.strip()):
408
+ if isinstance(elem, str) and self._pattern_unpack.fullmatch(elem):
416
409
  out.extend(elem_filled)
417
410
  else:
418
411
  out.append(elem_filled)
@@ -426,9 +419,9 @@ class TemplateFiller:
426
419
  current_path=current_path,
427
420
  relative_path_anchor=relative_path_anchor,
428
421
  level=0,
429
- internal=True,
422
+ current_chain=current_chain,
430
423
  )
431
- if isinstance(key, str) and self._pattern_unpack.fullmatch(key.strip()):
424
+ if isinstance(key, str) and self._pattern_unpack.fullmatch(key):
432
425
  new_dict.update(key_filled)
433
426
  continue
434
427
  if key_filled in self._template_keys:
@@ -440,20 +433,32 @@ class TemplateFiller:
440
433
  current_path=new_path,
441
434
  relative_path_anchor=get_relative_path(new_path),
442
435
  level=0,
436
+ current_chain=current_chain + [new_path],
443
437
  )
444
438
  return new_dict
445
439
  return templ
446
440
 
447
- def _find_loop(self):
448
- for pattern_length in range(1, len(self._path_history) // 2 + 1):
449
- # Slice the end of the list into two consecutive patterns
450
- pattern = self._path_history[-pattern_length:]
451
- previous_pattern = self._path_history[-2 * pattern_length:-pattern_length]
452
- # Check if the two patterns are the same
453
- if pattern == previous_pattern:
454
- pattern.insert(0, pattern[-1])
455
- return pattern
456
- return
441
+ # def _find_loop(self):
442
+ # for pattern_length in range(1, len(self._path_history) // 2 + 1):
443
+ # # Slice the end of the list into two consecutive patterns
444
+ # pattern = self._path_history[-pattern_length:]
445
+ # previous_pattern = self._path_history[-2 * pattern_length:-pattern_length]
446
+ # # Check if the two patterns are the same
447
+ # if pattern == previous_pattern:
448
+ # pattern.insert(0, pattern[-1])
449
+ # return pattern
450
+ # return
451
+
452
+ def _get_value_regex_pattern(self, level: int = 0) -> _RegexPattern:
453
+ if level in self._pattern_value:
454
+ return self._pattern_value[level]
455
+ count = self._repeater_count_value + level
456
+ pattern = _RegexPattern(
457
+ start=f"{self._marker_start_value}{self._repeater_start_value * count} ",
458
+ end=f" {self._repeater_end_value * count}{self._marker_end_value}",
459
+ )
460
+ self._pattern_value[level] = pattern
461
+ return pattern
457
462
 
458
463
  @staticmethod
459
464
  def _remove_leading_periods(s: str) -> (str, int):
@@ -481,6 +486,10 @@ class TemplateFiller:
481
486
  _recursive_extract(jsonpath)
482
487
  return fields
483
488
 
489
+ def _concat_json_paths(self, path1, path2):
490
+ if not isinstance(path2, _jsonpath.Child):
491
+ return _jsonpath.Child(path1, path2)
492
+ return _jsonpath.Child(self._concat_json_paths(path1, path2.left), path2.right)
484
493
 
485
494
  class _RegexPattern:
486
495
 
@@ -495,8 +504,8 @@ class _RegexPattern:
495
504
  matches = self.pattern.findall(string)
496
505
  if len(matches) == 1:
497
506
  # Verify the match spans the entire string
498
- return self.pattern.fullmatch(string)
507
+ return self.pattern.fullmatch(string.strip())
499
508
  return None
500
509
 
501
510
  def sub(self, repl, string: str) -> str:
502
- return self.pattern.sub(repl, string)
511
+ return self.pattern.sub(repl, string)