mapres 2.4.dev4__tar.gz → 2.4.dev6__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mapres
3
- Version: 2.4.dev4
3
+ Version: 2.4.dev6
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 = "2.4-dev.4"
10
+ version = "2.4-dev.6"
11
11
  description = "A powerfull mapping utility"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.9"
@@ -7,7 +7,7 @@ try:
7
7
  except PackageNotFoundError:
8
8
  __version__ = '0.0.0'
9
9
 
10
- from .resolver import MapResolver, res
10
+ from .resolver import MapResolver, res, setGlobalMaps
11
11
  from .datamap import DataMap, datamap, syntax
12
12
  from .layers import Layer, LayerStack
13
13
 
@@ -37,6 +37,7 @@ __all__ = [
37
37
  'syntax',
38
38
  'Layer',
39
39
  'LayerStack',
40
+ 'setGlobalMaps',
40
41
 
41
42
  # maps
42
43
  'ColorMap',
@@ -14,7 +14,7 @@ class MapResolver:
14
14
  - syntax providers
15
15
  - layered maps
16
16
  - context substitution
17
- - recursive multi-pass resolution
17
+ - multi-pass resolution (controlled by `passes`)
18
18
  - optional caching
19
19
  '''
20
20
  def __init__(
@@ -24,14 +24,16 @@ class MapResolver:
24
24
  pipeline: list | None = None,
25
25
  cache: bool = False,
26
26
  cache_size: int = 1024,
27
- max_depth: int = 5
27
+ passes_default: int = 5
28
28
  ):
29
29
  self.layers = layers or LayerStack()
30
30
  self.syntax = syntax or []
31
31
  self.pipeline = pipeline or []
32
32
  self.cache_enabled = cache
33
33
  self.cache = LRUCache(cache_size) if cache else None
34
- self.max_depth = max_depth
34
+
35
+ # unified recursion control
36
+ self.passes_default = passes_default
35
37
 
36
38
  # pipeline
37
39
  def _apply_pipeline(self, text: str, ctx: dict) -> str:
@@ -47,22 +49,26 @@ class MapResolver:
47
49
  for provider in self.syntax:
48
50
  pattern = provider.pattern
49
51
  d = provider.get_map(ctx, self)
52
+
50
53
  def repl(match: re.Match) -> str:
51
54
  k = match.group(1)
52
55
  if k not in d:
53
56
  raise MissingKeyError(f'Missing key "{k}" in syntax provider {provider}')
54
57
  return str(d[k])
58
+
55
59
  try:
56
60
  text = re.sub(pattern, repl, text)
57
61
  except re.error as exc:
58
62
  raise MapSyntaxError(
59
63
  f'Regex error in syntax provider {provider}: {exc}'
60
64
  ) from exc
65
+
61
66
  return text
62
67
 
63
68
  # layered maps
64
69
  def _apply_maps(self, text: str, ctx: dict, layerstack: LayerStack):
65
70
  syntax_patterns = []
71
+
66
72
  for m in layerstack:
67
73
  if isinstance(m, type) and hasattr(m, 'as_map'):
68
74
  m = m()
@@ -72,10 +78,13 @@ class MapResolver:
72
78
  pattern = ctx.get('syntax')
73
79
  if pattern:
74
80
  syntax_patterns.append(pattern)
81
+
75
82
  syntax_patterns = list(dict.fromkeys(syntax_patterns))
83
+
76
84
  for pattern in syntax_patterns:
77
85
  def repl(match: re.Match):
78
86
  k = match.group(1)
87
+
79
88
  for m in layerstack:
80
89
  layer = m
81
90
  if isinstance(layer, type) and hasattr(layer, 'as_map'):
@@ -86,6 +95,7 @@ class MapResolver:
86
95
  d = layer
87
96
  else:
88
97
  continue
98
+
89
99
  if k in d:
90
100
  return str(d[k])
91
101
 
@@ -98,19 +108,23 @@ class MapResolver:
98
108
  text = re.sub(pattern, repl, text)
99
109
  except re.error as exc:
100
110
  raise MapSyntaxError(f'Regex error in syntax pattern {pattern}: {exc}') from exc
111
+
101
112
  return text
102
113
 
103
114
  # context
104
115
  def _apply_ctx(self, text: str, ctx: dict) -> str:
105
116
  if not ctx:
106
117
  return text
118
+
107
119
  pattern = syntax.braces
108
120
  d = ctx
121
+
109
122
  def repl(match: re.Match) -> str:
110
123
  k = match.group(1)
111
124
  if k not in d:
112
125
  raise MissingKeyError(f'Missing ctx key "{k}"')
113
126
  return str(d[k])
127
+
114
128
  try:
115
129
  return re.sub(pattern, repl, text)
116
130
  except re.error as exc:
@@ -124,21 +138,12 @@ class MapResolver:
124
138
  text = self._apply_ctx(text, ctx)
125
139
  return text
126
140
 
127
- # recursion
128
- def _recursive(self, text: str, ctx: dict, layerstack: LayerStack) -> str:
129
- current = text
130
- for _ in range(self.max_depth):
131
- result = self._resolve_once(current, ctx, layerstack)
132
- if result == current:
133
- return result
134
- current = result
135
- return current
136
-
137
141
  # public api
138
142
  def res(
139
143
  self,
140
144
  text: str,
141
145
  *,
146
+ passes: int | None = None,
142
147
  extra_maps: dict | None = None,
143
148
  override_maps: dict | None = None,
144
149
  **ctx
@@ -147,22 +152,39 @@ class MapResolver:
147
152
  Resolve a string using maps, syntax providers, pipeline, and context.
148
153
  '''
149
154
  try:
155
+ # determine number of passes
156
+ depth = passes if passes is not None else self.passes_default
157
+
158
+ if depth <= 0:
159
+ raise MapResError("passes must be >= 1")
160
+
161
+ # override maps
150
162
  if override_maps is not None:
151
163
  temp = LayerStack([Layer('override', override_maps, priority=0)])
152
- return self._recursive(text, ctx, temp)
164
+ return self._resolve_once(text, ctx, temp)
153
165
 
166
+ # extra maps
154
167
  if extra_maps:
155
168
  temp = self.layers.clone()
156
169
  temp.add_layer(Layer('extra', extra_maps, priority=999))
157
- return self._recursive(text, ctx, temp)
170
+ return self._resolve_once(text, ctx, temp)
158
171
 
172
+ # caching
159
173
  if self.cache_enabled:
160
174
  key = (text, tuple(sorted(ctx.items())))
161
175
  cached = self.cache.get(key)
162
176
  if cached is not None:
163
177
  return cached
164
178
 
165
- result = self._recursive(text, ctx, self.layers)
179
+ # multi-pass resolution
180
+ current = text
181
+ for _ in range(depth):
182
+ new = self._resolve_once(current, ctx, self.layers)
183
+ if new == current:
184
+ break
185
+ current = new
186
+
187
+ result = current
166
188
 
167
189
  if self.cache_enabled:
168
190
  self.cache.set(key, result)
@@ -178,8 +200,20 @@ class MapResolver:
178
200
  # global resolver
179
201
  _DEFAULT = MapResolver()
180
202
 
181
- def res(text: str, **ctx) -> str:
203
+ def res(text: str, passes=None, **ctx) -> str:
182
204
  '''
183
205
  Resolve using the global default resolver.
184
206
  '''
185
- return _DEFAULT.res(text, **ctx)
207
+ return _DEFAULT.res(text, passes=passes, **ctx)
208
+
209
+ def setGlobalMaps(maps, *, name=None, priority=0):
210
+ """
211
+ Register a global map or datamap class into the default resolver.
212
+
213
+ maps: datamap class OR dict
214
+ name: optional layer name (default = class name or 'global')
215
+ priority: layer priority (lower = earlier)
216
+ """
217
+ layer_name = name or getattr(maps, "__name__", "global")
218
+ layer = Layer(layer_name, maps=[maps], priority=priority)
219
+ _DEFAULT.layers.add_layer(layer)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mapres
3
- Version: 2.4.dev4
3
+ Version: 2.4.dev6
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