mapres 3.dev20__tar.gz → 3.dev21__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 (24) hide show
  1. {mapres-3.dev20/src/mapres.egg-info → mapres-3.dev21}/PKG-INFO +1 -1
  2. {mapres-3.dev20 → mapres-3.dev21}/pyproject.toml +1 -1
  3. {mapres-3.dev20 → mapres-3.dev21}/src/mapres/datamap.py +5 -11
  4. mapres-3.dev21/src/mapres/evaluator.py +123 -0
  5. {mapres-3.dev20 → mapres-3.dev21/src/mapres.egg-info}/PKG-INFO +1 -1
  6. mapres-3.dev20/src/mapres/evaluator.py +0 -221
  7. {mapres-3.dev20 → mapres-3.dev21}/LICENSE +0 -0
  8. {mapres-3.dev20 → mapres-3.dev21}/README.md +0 -0
  9. {mapres-3.dev20 → mapres-3.dev21}/setup.cfg +0 -0
  10. {mapres-3.dev20 → mapres-3.dev21}/src/mapres/__init__.py +0 -0
  11. {mapres-3.dev20 → mapres-3.dev21}/src/mapres/ast.py +0 -0
  12. {mapres-3.dev20 → mapres-3.dev21}/src/mapres/cache.py +0 -0
  13. {mapres-3.dev20 → mapres-3.dev21}/src/mapres/exceptions.py +0 -0
  14. {mapres-3.dev20 → mapres-3.dev21}/src/mapres/layers.py +0 -0
  15. {mapres-3.dev20 → mapres-3.dev21}/src/mapres/maps/__init__.py +0 -0
  16. {mapres-3.dev20 → mapres-3.dev21}/src/mapres/maps/color.py +0 -0
  17. {mapres-3.dev20 → mapres-3.dev21}/src/mapres/maps/time.py +0 -0
  18. {mapres-3.dev20 → mapres-3.dev21}/src/mapres/parser.py +0 -0
  19. {mapres-3.dev20 → mapres-3.dev21}/src/mapres/resolver.py +0 -0
  20. {mapres-3.dev20 → mapres-3.dev21}/src/mapres/tokenizer.py +0 -0
  21. {mapres-3.dev20 → mapres-3.dev21}/src/mapres.egg-info/SOURCES.txt +0 -0
  22. {mapres-3.dev20 → mapres-3.dev21}/src/mapres.egg-info/dependency_links.txt +0 -0
  23. {mapres-3.dev20 → mapres-3.dev21}/src/mapres.egg-info/requires.txt +0 -0
  24. {mapres-3.dev20 → mapres-3.dev21}/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.dev20
3
+ Version: 3.dev21
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.20"
10
+ version = "3-dev.21"
11
11
  description = "A powerfull mapping utility"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.9"
@@ -1,4 +1,3 @@
1
- import re
2
1
  from dataclasses import dataclass, fields, is_dataclass
3
2
 
4
3
  # syntax identifiers
@@ -30,15 +29,13 @@ class DataMap:
30
29
 
31
30
  Mode metadata (__mode__) controls behavior:
32
31
  - None: plain static map
33
- - "dynamic": values may be provided by callables/providers
34
- - "config": config-style maps (no dynamic evaluation)
35
- - "callable": map exposes callable methods for nested calls
32
+ - 'dynamic': values may be provided by callables/providers
33
+ - 'config': config-style maps (no dynamic evaluation)
34
+ - 'callable': map exposes callable methods for nested calls
36
35
  '''
37
36
  __syntax__: str = None
38
- __mode__: str | None = None # "dynamic", "config", "callable", or None
39
- __recursive__: bool = False
37
+ __mode__: str | None = None # 'dynamic', 'config', 'callable', or None
40
38
  __missing_key__: str = 'error' # 'error', 'silent', 'placeholder'
41
- __ignore_delimiters__: bool = False
42
39
 
43
40
  def as_map(self):
44
41
  '''
@@ -95,14 +92,11 @@ class DataMap:
95
92
  return out
96
93
 
97
94
 
98
- def datamap(_cls=None, *, syntax=None, mode=None, recursive=None, missing_key=None, ignore_delimiters=None):
99
-
95
+ def datamap(_cls=None, *, syntax=None, mode=None, missing_key=None):
100
96
  def wrap(cls):
101
97
  cls.__syntax__ = syntax
102
98
  cls.__mode__ = mode
103
- cls.__recursive__ = recursive
104
99
  cls.__missing_key__ = missing_key or 'error'
105
- cls.__ignore_delimiters__ = ignore_delimiters or False
106
100
 
107
101
  namespace = dict(cls.__dict__)
108
102
  namespace['__dict__'] = {}
@@ -0,0 +1,123 @@
1
+ from .ast import TemplateNode, TextNode, IdentNode, CallNode
2
+ from .tokenizer import Tokenizer
3
+ from .parser import Parser
4
+ from .exceptions import MapResError, MissingKeyError
5
+
6
+
7
+ class EvaluationError(MapResError):
8
+ pass
9
+
10
+
11
+ class Evaluator:
12
+ def __init__(self, layerstack, ctx=None):
13
+ self.layerstack = layerstack
14
+ self.ctx = ctx or {}
15
+ self._seen = set() # cycle detection
16
+
17
+ # public api
18
+ def evaluate(self, node):
19
+ return self._eval(node)
20
+
21
+ # internal dispatch
22
+ def _eval(self, node):
23
+ if isinstance(node, TemplateNode):
24
+ return self._eval_template(node)
25
+
26
+ if isinstance(node, TextNode):
27
+ return node.text
28
+
29
+ if isinstance(node, IdentNode):
30
+ return self._eval_ident(node)
31
+
32
+ if isinstance(node, CallNode):
33
+ return self._eval_call(node)
34
+
35
+ raise EvaluationError(f'Unknown AST node: {node}')
36
+
37
+ # template
38
+ def _eval_template(self, node: TemplateNode):
39
+ parts = []
40
+ for child in node.children:
41
+ parts.append(self._eval(child))
42
+ out = ''.join(parts)
43
+ return out
44
+
45
+ # identifier
46
+ def _eval_ident(self, node: IdentNode):
47
+ name = node.name
48
+
49
+ # cycle detection
50
+ if name in self._seen:
51
+ raise EvaluationError(f"Cycle detected in identifier '{name}'")
52
+ self._seen.add(name)
53
+
54
+ parts = name.split('.')
55
+
56
+ # 1. ctx first
57
+ val = self._lookup_hierarchical(self.ctx, parts)
58
+ if val is not None:
59
+ self._seen.remove(name)
60
+ return str(val)
61
+
62
+ # 2. syntax-bound maps
63
+ for m in self.layerstack.all_maps():
64
+ if getattr(m, '__syntax__', None) not in (None, node.syntax):
65
+ continue
66
+
67
+ d = self._map_to_dict(m)
68
+ val = self._lookup_hierarchical(d, parts)
69
+ if val is not None:
70
+ self._seen.remove(name)
71
+ return str(val)
72
+
73
+ self._seen.remove(name)
74
+ raise MissingKeyError(name)
75
+
76
+ # call (nested token)
77
+ def _eval_call(self, node: CallNode):
78
+ outer = node.name
79
+
80
+ if outer in self._seen:
81
+ raise EvaluationError(f"Cycle detected in call '{outer}'")
82
+ self._seen.add(outer)
83
+
84
+ arg_value = self._eval(node.arg)
85
+ parts = [outer, arg_value]
86
+
87
+ # ctx first
88
+ val = self._lookup_hierarchical(self.ctx, parts)
89
+ if val is not None:
90
+ self._seen.remove(outer)
91
+ return str(val)
92
+
93
+ # syntax-bound maps
94
+ for m in self.layerstack.all_maps():
95
+ if getattr(m, '__syntax__', None) not in (None, node.syntax):
96
+ continue
97
+
98
+ d = self._map_to_dict(m)
99
+ val = self._lookup_hierarchical(d, parts)
100
+ if val is not None:
101
+ self._seen.remove(outer)
102
+ return str(val)
103
+
104
+ self._seen.remove(outer)
105
+ raise MissingKeyError(f'{outer}({arg_value})')
106
+
107
+ # helpers
108
+ def _map_to_dict(self, m):
109
+ if hasattr(m, 'as_map'):
110
+ return m.as_map()
111
+ if isinstance(m, dict):
112
+ return m
113
+ raise EvaluationError(f'Invalid map object: {m}')
114
+
115
+ def _lookup_hierarchical(self, root, parts):
116
+ cur = root
117
+ for p in parts:
118
+ if not isinstance(cur, dict):
119
+ return None
120
+ if p not in cur:
121
+ return None
122
+ cur = cur[p]
123
+ return cur
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mapres
3
- Version: 3.dev20
3
+ Version: 3.dev21
4
4
  Summary: A powerfull mapping utility
5
5
  Author: iFamished
6
6
  License: MIT
@@ -1,221 +0,0 @@
1
- from .ast import TemplateNode, TextNode, IdentNode, CallNode
2
- from .tokenizer import Tokenizer
3
- from .parser import Parser
4
- from .exceptions import MapResError, MissingKeyError
5
-
6
-
7
- class EvaluationError(MapResError):
8
- pass
9
-
10
-
11
- class Evaluator:
12
- def __init__(self, layerstack, ctx=None):
13
- self.layerstack = layerstack
14
- self.ctx = ctx or {}
15
- self._seen = set() # cycle detection
16
-
17
- # public api
18
- def evaluate(self, node):
19
- return self._eval(node)
20
-
21
- # internal dispatch
22
- def _eval(self, node):
23
- if isinstance(node, TemplateNode):
24
- return self._eval_template(node)
25
-
26
- if isinstance(node, TextNode):
27
- return node.text
28
-
29
- if isinstance(node, IdentNode):
30
- return self._eval_ident(node)
31
-
32
- if isinstance(node, CallNode):
33
- return self._eval_call(node)
34
-
35
- raise EvaluationError(f'Unknown AST node: {node}')
36
-
37
- # template
38
- def _eval_template(self, node: TemplateNode):
39
- parts = []
40
- for child in node.children:
41
- parts.append(self._eval(child))
42
- out = ''.join(parts)
43
-
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
-
60
- return out
61
-
62
- # identifier
63
- def _eval_ident(self, node: IdentNode):
64
- name = node.name
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
71
- if name in self._seen:
72
- raise EvaluationError(f"Cycle detected in identifier '{name}'")
73
- self._seen.add(name)
74
-
75
- parts = name.split('.')
76
-
77
- # 1. ctx first
78
- val = self._lookup_hierarchical(self.ctx, parts)
79
- if val is not None:
80
- self._seen.remove(name)
81
- return str(val)
82
-
83
- # 2. syntax-bound maps
84
- for m in self.layerstack.all_maps():
85
- if getattr(m, '__syntax__', None) not in (None, node.syntax):
86
- continue
87
-
88
- d = self._map_to_dict(m)
89
- val = self._lookup_hierarchical(d, parts)
90
- if val is not None:
91
- self._seen.remove(name)
92
- return str(val)
93
-
94
- self._seen.remove(name)
95
- raise MissingKeyError(name)
96
-
97
- # call (nested token)
98
- def _eval_call(self, node: CallNode):
99
- outer = node.name
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
-
106
- if outer in self._seen:
107
- raise EvaluationError(f"Cycle detected in call '{outer}'")
108
- self._seen.add(outer)
109
-
110
- arg_value = self._eval(node.arg)
111
- parts = [outer, arg_value]
112
-
113
- # ctx first
114
- val = self._lookup_hierarchical(self.ctx, parts)
115
- if val is not None:
116
- self._seen.remove(outer)
117
- return str(val)
118
-
119
- # syntax-bound maps
120
- for m in self.layerstack.all_maps():
121
- if getattr(m, '__syntax__', None) not in (None, node.syntax):
122
- continue
123
-
124
- d = self._map_to_dict(m)
125
- val = self._lookup_hierarchical(d, parts)
126
- if val is not None:
127
- self._seen.remove(outer)
128
- return str(val)
129
-
130
- self._seen.remove(outer)
131
- raise MissingKeyError(f'{outer}({arg_value})')
132
-
133
- # helpers
134
- def _map_to_dict(self, m):
135
- if hasattr(m, 'as_map'):
136
- return m.as_map()
137
- if isinstance(m, dict):
138
- return m
139
- raise EvaluationError(f'Invalid map object: {m}')
140
-
141
- def _lookup_hierarchical(self, root, parts):
142
- cur = root
143
- for p in parts:
144
- if not isinstance(cur, dict):
145
- return None
146
- if p not in cur:
147
- return None
148
- cur = cur[p]
149
- return cur
150
-
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
155
- return False
156
-
157
- def _get_max_depth(self):
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
164
-
165
- def _re_resolve(self, text: str) -> str:
166
- tokens = Tokenizer(text).tokenize()
167
- ast = Parser(tokens).parse()
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes