mapres 3.dev18__tar.gz → 3.dev20__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 (23) hide show
  1. {mapres-3.dev18/src/mapres.egg-info → mapres-3.dev20}/PKG-INFO +1 -1
  2. {mapres-3.dev18 → mapres-3.dev20}/pyproject.toml +1 -1
  3. {mapres-3.dev18 → mapres-3.dev20}/src/mapres/datamap.py +1 -2
  4. {mapres-3.dev18 → mapres-3.dev20}/src/mapres/evaluator.py +100 -5
  5. {mapres-3.dev18 → mapres-3.dev20/src/mapres.egg-info}/PKG-INFO +1 -1
  6. {mapres-3.dev18 → mapres-3.dev20}/LICENSE +0 -0
  7. {mapres-3.dev18 → mapres-3.dev20}/README.md +0 -0
  8. {mapres-3.dev18 → mapres-3.dev20}/setup.cfg +0 -0
  9. {mapres-3.dev18 → mapres-3.dev20}/src/mapres/__init__.py +0 -0
  10. {mapres-3.dev18 → mapres-3.dev20}/src/mapres/ast.py +0 -0
  11. {mapres-3.dev18 → mapres-3.dev20}/src/mapres/cache.py +0 -0
  12. {mapres-3.dev18 → mapres-3.dev20}/src/mapres/exceptions.py +0 -0
  13. {mapres-3.dev18 → mapres-3.dev20}/src/mapres/layers.py +0 -0
  14. {mapres-3.dev18 → mapres-3.dev20}/src/mapres/maps/__init__.py +0 -0
  15. {mapres-3.dev18 → mapres-3.dev20}/src/mapres/maps/color.py +0 -0
  16. {mapres-3.dev18 → mapres-3.dev20}/src/mapres/maps/time.py +0 -0
  17. {mapres-3.dev18 → mapres-3.dev20}/src/mapres/parser.py +0 -0
  18. {mapres-3.dev18 → mapres-3.dev20}/src/mapres/resolver.py +0 -0
  19. {mapres-3.dev18 → mapres-3.dev20}/src/mapres/tokenizer.py +0 -0
  20. {mapres-3.dev18 → mapres-3.dev20}/src/mapres.egg-info/SOURCES.txt +0 -0
  21. {mapres-3.dev18 → mapres-3.dev20}/src/mapres.egg-info/dependency_links.txt +0 -0
  22. {mapres-3.dev18 → mapres-3.dev20}/src/mapres.egg-info/requires.txt +0 -0
  23. {mapres-3.dev18 → mapres-3.dev20}/src/mapres.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mapres
3
- Version: 3.dev18
3
+ Version: 3.dev20
4
4
  Summary: A powerfull mapping utility
5
5
  Author: iFamished
6
6
  License: MIT
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "mapres"
10
- version = "3-dev.18"
10
+ version = "3-dev.20"
11
11
  description = "A powerfull mapping utility"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.9"
@@ -95,8 +95,7 @@ class DataMap:
95
95
  return out
96
96
 
97
97
 
98
- def datamap(_cls=None, *, syntax=None, mode=None, recursive=None,
99
- missing_key=None, ignore_delimiters=None):
98
+ def datamap(_cls=None, *, syntax=None, mode=None, recursive=None, missing_key=None, ignore_delimiters=None):
100
99
 
101
100
  def wrap(cls):
102
101
  cls.__syntax__ = syntax
@@ -14,45 +14,73 @@ class Evaluator:
14
14
  self.ctx = ctx or {}
15
15
  self._seen = set() # cycle detection
16
16
 
17
+ # public api
17
18
  def evaluate(self, node):
18
19
  return self._eval(node)
19
20
 
21
+ # internal dispatch
20
22
  def _eval(self, node):
21
23
  if isinstance(node, TemplateNode):
22
24
  return self._eval_template(node)
25
+
23
26
  if isinstance(node, TextNode):
24
27
  return node.text
28
+
25
29
  if isinstance(node, IdentNode):
26
30
  return self._eval_ident(node)
31
+
27
32
  if isinstance(node, CallNode):
28
33
  return self._eval_call(node)
34
+
29
35
  raise EvaluationError(f'Unknown AST node: {node}')
30
36
 
37
+ # template
31
38
  def _eval_template(self, node: TemplateNode):
32
39
  parts = []
33
40
  for child in node.children:
34
41
  parts.append(self._eval(child))
35
42
  out = ''.join(parts)
36
43
 
37
- # recursion disabled for now
44
+ # recursion over full template output
45
+ if self._has_recursive_maps():
46
+ seen = set()
47
+ depth = 0
48
+ max_depth = self._get_max_depth()
49
+
50
+ while depth < max_depth:
51
+ new_out = self._re_resolve(out)
52
+ if new_out == out:
53
+ break
54
+ if new_out in seen:
55
+ raise EvaluationError('Cycle detected in template recursion')
56
+ seen.add(new_out)
57
+ out = new_out
58
+ depth += 1
59
+
38
60
  return out
39
61
 
62
+ # identifier
40
63
  def _eval_ident(self, node: IdentNode):
41
64
  name = node.name
42
65
 
66
+ # ignore_delimiters: treat token as literal for this syntax
67
+ if self._syntax_ignores_delimiters(node.syntax):
68
+ return self._literal_from_syntax(name, node.syntax)
69
+
70
+ # cycle detection
43
71
  if name in self._seen:
44
72
  raise EvaluationError(f"Cycle detected in identifier '{name}'")
45
73
  self._seen.add(name)
46
74
 
47
75
  parts = name.split('.')
48
76
 
49
- # ctx first
77
+ # 1. ctx first
50
78
  val = self._lookup_hierarchical(self.ctx, parts)
51
79
  if val is not None:
52
80
  self._seen.remove(name)
53
81
  return str(val)
54
82
 
55
- # syntax-bound maps
83
+ # 2. syntax-bound maps
56
84
  for m in self.layerstack.all_maps():
57
85
  if getattr(m, '__syntax__', None) not in (None, node.syntax):
58
86
  continue
@@ -66,9 +94,15 @@ class Evaluator:
66
94
  self._seen.remove(name)
67
95
  raise MissingKeyError(name)
68
96
 
97
+ # call (nested token)
69
98
  def _eval_call(self, node: CallNode):
70
99
  outer = node.name
71
100
 
101
+ # ignore_delimiters: treat call token as literal
102
+ if self._syntax_ignores_delimiters(node.syntax):
103
+ arg_text = self._eval(node.arg)
104
+ return self._literal_from_syntax(outer, node.syntax, is_call=True, arg_text=arg_text)
105
+
72
106
  if outer in self._seen:
73
107
  raise EvaluationError(f"Cycle detected in call '{outer}'")
74
108
  self._seen.add(outer)
@@ -96,6 +130,7 @@ class Evaluator:
96
130
  self._seen.remove(outer)
97
131
  raise MissingKeyError(f'{outer}({arg_value})')
98
132
 
133
+ # helpers
99
134
  def _map_to_dict(self, m):
100
135
  if hasattr(m, 'as_map'):
101
136
  return m.as_map()
@@ -113,14 +148,74 @@ class Evaluator:
113
148
  cur = cur[p]
114
149
  return cur
115
150
 
116
- # recursion helpers now unused
117
151
  def _has_recursive_maps(self):
152
+ for m in self.layerstack.all_maps():
153
+ if getattr(m, '__recursive__', False) is True:
154
+ return True
118
155
  return False
119
156
 
120
157
  def _get_max_depth(self):
121
- return 0
158
+ depths = [
159
+ getattr(m, '__max_depth__', 10)
160
+ for m in self.layerstack.all_maps()
161
+ if getattr(m, '__recursive__', False) is True
162
+ ]
163
+ return max(depths) if depths else 0
122
164
 
123
165
  def _re_resolve(self, text: str) -> str:
124
166
  tokens = Tokenizer(text).tokenize()
125
167
  ast = Parser(tokens).parse()
126
168
  return self.evaluate(ast)
169
+
170
+ def _syntax_ignores_delimiters(self, syntax_name: str) -> bool:
171
+ for m in self.layerstack.all_maps():
172
+ if getattr(m, '__syntax__', None) == syntax_name:
173
+ if getattr(m, '__ignore_delimiters__', False):
174
+ return True
175
+ return False
176
+
177
+ def _literal_from_syntax(self, name: str, syntax_name: str, is_call: bool = False, arg_text: str | None = None) -> str:
178
+ if not is_call:
179
+ if syntax_name == 'colons':
180
+ return f':{name}:'
181
+ if syntax_name == 'braces':
182
+ return f'{{{{{name}}}}}'
183
+ if syntax_name == 'dollars':
184
+ return f'${{{name}}}'
185
+ if syntax_name == 'angles':
186
+ return f'<{name}>'
187
+ if syntax_name == 'pipes':
188
+ return f'|{name}|'
189
+ if syntax_name == 'percents':
190
+ return f'%{name}%'
191
+ if syntax_name == 'paren_dollars':
192
+ return f'$({name})'
193
+ if syntax_name == 'at_tags':
194
+ return f'@{name}@'
195
+ if syntax_name == 'hash_tags':
196
+ return f'#{name}#'
197
+ return name
198
+
199
+ # call literal: syntax-specific wrapping of name(arg_text)
200
+ arg = arg_text or ''
201
+ inner = f'{name}({arg})'
202
+
203
+ if syntax_name == 'colons':
204
+ return f':{inner}:'
205
+ if syntax_name == 'braces':
206
+ return f'{{{{{inner}}}}}'
207
+ if syntax_name == 'dollars':
208
+ return f'${{{inner}}}'
209
+ if syntax_name == 'angles':
210
+ return f'<{inner}>'
211
+ if syntax_name == 'pipes':
212
+ return f'|{inner}|'
213
+ if syntax_name == 'percents':
214
+ return f'%{inner}%'
215
+ if syntax_name == 'paren_dollars':
216
+ return f'$({inner})'
217
+ if syntax_name == 'at_tags':
218
+ return f'@{inner}@'
219
+ if syntax_name == 'hash_tags':
220
+ return f'#{inner}#'
221
+ return inner
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mapres
3
- Version: 3.dev18
3
+ Version: 3.dev20
4
4
  Summary: A powerfull mapping utility
5
5
  Author: iFamished
6
6
  License: MIT
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes