mapres 1.1.2__tar.gz → 2.0__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.
- {mapres-1.1.2/src/mapres.egg-info → mapres-2.0}/PKG-INFO +1 -1
- {mapres-1.1.2 → mapres-2.0}/pyproject.toml +1 -1
- {mapres-1.1.2 → mapres-2.0}/src/mapres/cache.py +1 -1
- {mapres-1.1.2 → mapres-2.0}/src/mapres/datamap.py +1 -14
- {mapres-1.1.2 → mapres-2.0}/src/mapres/exceptions.py +1 -1
- {mapres-1.1.2 → mapres-2.0}/src/mapres/layers.py +3 -2
- mapres-2.0/src/mapres/resolver.py +174 -0
- {mapres-1.1.2 → mapres-2.0/src/mapres.egg-info}/PKG-INFO +1 -1
- {mapres-1.1.2 → mapres-2.0}/src/mapres.egg-info/SOURCES.txt +0 -1
- mapres-1.1.2/src/mapres/resolver.py +0 -110
- mapres-1.1.2/src/mapres/utils.py +0 -2
- {mapres-1.1.2 → mapres-2.0}/LICENSE +0 -0
- {mapres-1.1.2 → mapres-2.0}/README.md +0 -0
- {mapres-1.1.2 → mapres-2.0}/setup.cfg +0 -0
- {mapres-1.1.2 → mapres-2.0}/src/mapres/__init__.py +0 -0
- {mapres-1.1.2 → mapres-2.0}/src/mapres/maps/__init__.py +0 -0
- {mapres-1.1.2 → mapres-2.0}/src/mapres/maps/color.py +0 -0
- {mapres-1.1.2 → mapres-2.0}/src/mapres/maps/time.py +0 -0
- {mapres-1.1.2 → mapres-2.0}/src/mapres.egg-info/dependency_links.txt +0 -0
- {mapres-1.1.2 → mapres-2.0}/src/mapres.egg-info/requires.txt +0 -0
- {mapres-1.1.2 → mapres-2.0}/src/mapres.egg-info/top_level.txt +0 -0
|
@@ -12,31 +12,21 @@ class syntax:
|
|
|
12
12
|
class DataMap:
|
|
13
13
|
'''Core datamap class. Contains logic used to make datamaps'''
|
|
14
14
|
__syntax__: str = syntax.braces
|
|
15
|
-
__mode__ = None
|
|
15
|
+
__mode__: str | None = None
|
|
16
16
|
|
|
17
17
|
def as_map(self):
|
|
18
18
|
result = {}
|
|
19
|
-
|
|
20
19
|
for f in fields(self):
|
|
21
20
|
if not f.init or f.name in ("__syntax__", "__mode__"):
|
|
22
21
|
continue
|
|
23
|
-
|
|
24
22
|
val = getattr(self, f.name)
|
|
25
|
-
|
|
26
|
-
# dynamic mode
|
|
27
23
|
if self.__mode__ == "dynamic":
|
|
28
24
|
providers = getattr(self, "providers", None)
|
|
29
|
-
|
|
30
|
-
# explicit provider dict only
|
|
31
25
|
if isinstance(providers, dict) and f.name in providers:
|
|
32
26
|
val = providers[f.name]
|
|
33
|
-
|
|
34
|
-
# callables are executed
|
|
35
27
|
if callable(val):
|
|
36
28
|
val = val()
|
|
37
|
-
|
|
38
29
|
result[f.name] = val
|
|
39
|
-
|
|
40
30
|
return result
|
|
41
31
|
|
|
42
32
|
@classmethod
|
|
@@ -54,13 +44,10 @@ def datamap(
|
|
|
54
44
|
def wrap(cls):
|
|
55
45
|
cls.__syntax__ = syntax
|
|
56
46
|
cls.__mode__ = mode
|
|
57
|
-
|
|
58
47
|
namespace = dict(cls.__dict__)
|
|
59
48
|
namespace["__dict__"] = {}
|
|
60
|
-
|
|
61
49
|
cls = type(cls.__name__, (DataMap,), namespace)
|
|
62
50
|
return dataclass(frozen=False)(cls)
|
|
63
|
-
|
|
64
51
|
return wrap if _cls is None else wrap(_cls)
|
|
65
52
|
|
|
66
53
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
# layer holds maps with a priority
|
|
2
1
|
class Layer:
|
|
2
|
+
'''Holds a map with a priority'''
|
|
3
3
|
def __init__(self, name, maps=None, priority=0):
|
|
4
4
|
self.name = name
|
|
5
5
|
self.maps = maps or []
|
|
@@ -11,8 +11,9 @@ class Layer:
|
|
|
11
11
|
def __iter__(self):
|
|
12
12
|
return iter(self.maps)
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
class LayerStack:
|
|
16
|
+
'''Manages ordered layers'''
|
|
16
17
|
def __init__(self, layers=None):
|
|
17
18
|
self.layers = {}
|
|
18
19
|
if layers:
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from .datamap import DataMap, syntax
|
|
3
|
+
from .cache import LRUCache
|
|
4
|
+
from .layers import Layer, LayerStack
|
|
5
|
+
from .exceptions import MapResError, MissingKeyError, MapSyntaxError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MapResolver:
|
|
9
|
+
"""
|
|
10
|
+
Core resolution engine for mapres.
|
|
11
|
+
|
|
12
|
+
Handles:
|
|
13
|
+
- pipeline stages
|
|
14
|
+
- syntax providers
|
|
15
|
+
- layered maps
|
|
16
|
+
- context substitution
|
|
17
|
+
- recursive multi-pass resolution
|
|
18
|
+
- optional caching
|
|
19
|
+
"""
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
layers: list | None = None,
|
|
23
|
+
syntax: list | None = None,
|
|
24
|
+
pipeline: list | None = None,
|
|
25
|
+
cache: bool = False,
|
|
26
|
+
cache_size: int = 1024,
|
|
27
|
+
max_depth: int = 5
|
|
28
|
+
):
|
|
29
|
+
self.layers = layers or LayerStack()
|
|
30
|
+
self.syntax = syntax or []
|
|
31
|
+
self.pipeline = pipeline or []
|
|
32
|
+
self.cache_enabled = cache
|
|
33
|
+
self.cache = LRUCache(cache_size) if cache else None
|
|
34
|
+
self.max_depth = max_depth
|
|
35
|
+
|
|
36
|
+
# pipeline
|
|
37
|
+
def _apply_pipeline(self, text: str, ctx: dict) -> str:
|
|
38
|
+
try:
|
|
39
|
+
for stage in self.pipeline:
|
|
40
|
+
text = stage(text, ctx, self)
|
|
41
|
+
return text
|
|
42
|
+
except Exception as exc:
|
|
43
|
+
raise MapResError(f"Pipeline stage failed: {exc}") from exc
|
|
44
|
+
|
|
45
|
+
# syntax providers
|
|
46
|
+
def _apply_syntax(self, text: str, ctx: dict) -> str:
|
|
47
|
+
for provider in self.syntax:
|
|
48
|
+
pattern = provider.pattern
|
|
49
|
+
d = provider.get_map(ctx, self)
|
|
50
|
+
def repl(match: re.Match) -> str:
|
|
51
|
+
k = match.group(1)
|
|
52
|
+
if k not in d:
|
|
53
|
+
raise MissingKeyError(f"Missing key '{k}' in syntax provider {provider}")
|
|
54
|
+
return str(d[k])
|
|
55
|
+
try:
|
|
56
|
+
text = re.sub(pattern, repl, text)
|
|
57
|
+
except re.error as exc:
|
|
58
|
+
raise MapSyntaxError(
|
|
59
|
+
f"Regex error in syntax provider {provider}: {exc}"
|
|
60
|
+
) from exc
|
|
61
|
+
return text
|
|
62
|
+
|
|
63
|
+
# layered maps
|
|
64
|
+
def _apply_maps(self, text: str, ctx: dict, layerstack: LayerStack) -> str:
|
|
65
|
+
for m in layerstack:
|
|
66
|
+
|
|
67
|
+
# class-based layer
|
|
68
|
+
if isinstance(m, type) and hasattr(m, "as_map"):
|
|
69
|
+
m = m()
|
|
70
|
+
if hasattr(m, "as_map"):
|
|
71
|
+
pattern = m.get_syntax()
|
|
72
|
+
d = m.as_map()
|
|
73
|
+
elif isinstance(m, dict):
|
|
74
|
+
pattern = ctx.get("syntax")
|
|
75
|
+
if not pattern:
|
|
76
|
+
continue
|
|
77
|
+
d = m
|
|
78
|
+
else:
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
def repl(match: re.Match) -> str:
|
|
82
|
+
k = match.group(1)
|
|
83
|
+
if k not in d:
|
|
84
|
+
raise MissingKeyError(f"Missing key '{k}' in layer {m}")
|
|
85
|
+
return str(d[k])
|
|
86
|
+
try:
|
|
87
|
+
text = re.sub(pattern, repl, text)
|
|
88
|
+
except re.error as exc:
|
|
89
|
+
raise MapSyntaxError(f"Regex error in layer {m}: {exc}") from exc
|
|
90
|
+
return text
|
|
91
|
+
|
|
92
|
+
# context
|
|
93
|
+
def _apply_ctx(self, text: str, ctx: dict) -> str:
|
|
94
|
+
if not ctx:
|
|
95
|
+
return text
|
|
96
|
+
pattern = syntax.braces
|
|
97
|
+
d = ctx
|
|
98
|
+
def repl(match: re.Match) -> str:
|
|
99
|
+
k = match.group(1)
|
|
100
|
+
if k not in d:
|
|
101
|
+
raise MissingKeyError(f"Missing ctx key '{k}'")
|
|
102
|
+
return str(d[k])
|
|
103
|
+
try:
|
|
104
|
+
return re.sub(pattern, repl, text)
|
|
105
|
+
except re.error as exc:
|
|
106
|
+
raise MapSyntaxError(f"Regex error in ctx syntax: {exc}") from exc
|
|
107
|
+
|
|
108
|
+
# one pass
|
|
109
|
+
def _resolve_once(self, text: str, ctx: dict, layerstack: LayerStack) -> str:
|
|
110
|
+
text = self._apply_pipeline(text, ctx)
|
|
111
|
+
text = self._apply_syntax(text, ctx)
|
|
112
|
+
text = self._apply_maps(text, ctx, layerstack)
|
|
113
|
+
text = self._apply_ctx(text, ctx)
|
|
114
|
+
return text
|
|
115
|
+
|
|
116
|
+
# recursion
|
|
117
|
+
def _recursive(self, text: str, ctx: dict, layerstack: LayerStack) -> str:
|
|
118
|
+
current = text
|
|
119
|
+
for _ in range(self.max_depth):
|
|
120
|
+
result = self._resolve_once(current, ctx, layerstack)
|
|
121
|
+
if result == current:
|
|
122
|
+
return result
|
|
123
|
+
current = result
|
|
124
|
+
return current
|
|
125
|
+
|
|
126
|
+
# public api
|
|
127
|
+
def res(
|
|
128
|
+
self,
|
|
129
|
+
text: str,
|
|
130
|
+
*,
|
|
131
|
+
extra_maps: dict | None = None,
|
|
132
|
+
override_maps: dict | None = None,
|
|
133
|
+
**ctx
|
|
134
|
+
) -> str:
|
|
135
|
+
"""
|
|
136
|
+
Resolve a string using maps, syntax providers, pipeline, and context.
|
|
137
|
+
"""
|
|
138
|
+
try:
|
|
139
|
+
if override_maps is not None:
|
|
140
|
+
temp = LayerStack([Layer("override", override_maps, priority=0)])
|
|
141
|
+
return self._recursive(text, ctx, temp)
|
|
142
|
+
|
|
143
|
+
if extra_maps:
|
|
144
|
+
temp = self.layers.clone()
|
|
145
|
+
temp.add_layer(Layer("extra", extra_maps, priority=999))
|
|
146
|
+
return self._recursive(text, ctx, temp)
|
|
147
|
+
|
|
148
|
+
if self.cache_enabled:
|
|
149
|
+
key = (text, tuple(sorted(ctx.items())))
|
|
150
|
+
cached = self.cache.get(key)
|
|
151
|
+
if cached is not None:
|
|
152
|
+
return cached
|
|
153
|
+
|
|
154
|
+
result = self._recursive(text, ctx, self.layers)
|
|
155
|
+
|
|
156
|
+
if self.cache_enabled:
|
|
157
|
+
self.cache.set(key, result)
|
|
158
|
+
|
|
159
|
+
return result
|
|
160
|
+
|
|
161
|
+
except MapResError:
|
|
162
|
+
raise
|
|
163
|
+
except Exception as exc:
|
|
164
|
+
raise MapResError(f"Unhandled resolver error: {exc}") from exc
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# global resolver
|
|
168
|
+
_DEFAULT = MapResolver()
|
|
169
|
+
|
|
170
|
+
def res(text: str, **ctx) -> str:
|
|
171
|
+
"""
|
|
172
|
+
Resolve using the global default resolver.
|
|
173
|
+
"""
|
|
174
|
+
return _DEFAULT.res(text, **ctx)
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
from .datamap import DataMap, syntax
|
|
3
|
-
from .cache import LRUCache
|
|
4
|
-
from .layers import Layer, LayerStack
|
|
5
|
-
|
|
6
|
-
class MapResolver:
|
|
7
|
-
def __init__(self, layers=None, syntax=None, pipeline=None, cache=False, cache_size=1024, max_depth=5):
|
|
8
|
-
self.layers = layers or LayerStack()
|
|
9
|
-
self.syntax = syntax or []
|
|
10
|
-
self.pipeline = pipeline or []
|
|
11
|
-
self.cache_enabled = cache
|
|
12
|
-
self.cache = LRUCache(cache_size) if cache else None
|
|
13
|
-
self.max_depth = max_depth
|
|
14
|
-
|
|
15
|
-
# pipeline
|
|
16
|
-
def _apply_pipeline(self, text, ctx):
|
|
17
|
-
for stage in self.pipeline:
|
|
18
|
-
text = stage(text, ctx, self)
|
|
19
|
-
return text
|
|
20
|
-
|
|
21
|
-
# syntax providers
|
|
22
|
-
def _apply_syntax(self, text, ctx):
|
|
23
|
-
for provider in self.syntax:
|
|
24
|
-
pattern = provider.pattern
|
|
25
|
-
d = provider.get_map(ctx, self)
|
|
26
|
-
def repl(match):
|
|
27
|
-
k = match.group(1)
|
|
28
|
-
return str(d.get(k, match.group(0)))
|
|
29
|
-
text = re.sub(pattern, repl, text)
|
|
30
|
-
return text
|
|
31
|
-
|
|
32
|
-
# layered maps
|
|
33
|
-
def _apply_maps(self, text, ctx, layerstack):
|
|
34
|
-
for m in layerstack:
|
|
35
|
-
if isinstance(m, type) and hasattr(m, "as_map"):
|
|
36
|
-
m = m()
|
|
37
|
-
if hasattr(m, "as_map"):
|
|
38
|
-
pattern = m.get_syntax()
|
|
39
|
-
d = m.as_map()
|
|
40
|
-
elif isinstance(m, dict):
|
|
41
|
-
pattern = ctx.get("syntax")
|
|
42
|
-
if not pattern:
|
|
43
|
-
continue
|
|
44
|
-
d = m
|
|
45
|
-
else:
|
|
46
|
-
continue
|
|
47
|
-
def repl(match):
|
|
48
|
-
k = match.group(1)
|
|
49
|
-
return str(d.get(k, match.group(0)))
|
|
50
|
-
text = re.sub(pattern, repl, text)
|
|
51
|
-
return text
|
|
52
|
-
|
|
53
|
-
# ctx
|
|
54
|
-
def _apply_ctx(self, text, ctx):
|
|
55
|
-
if not ctx:
|
|
56
|
-
return text
|
|
57
|
-
pattern = syntax.braces
|
|
58
|
-
d = ctx
|
|
59
|
-
def repl(match):
|
|
60
|
-
k = match.group(1)
|
|
61
|
-
return str(d.get(k, match.group(0)))
|
|
62
|
-
return re.sub(pattern, repl, text)
|
|
63
|
-
|
|
64
|
-
# one pass
|
|
65
|
-
def _resolve_once(self, text, ctx, layerstack):
|
|
66
|
-
text = self._apply_pipeline(text, ctx)
|
|
67
|
-
text = self._apply_syntax(text, ctx)
|
|
68
|
-
text = self._apply_maps(text, ctx, layerstack)
|
|
69
|
-
text = self._apply_ctx(text, ctx)
|
|
70
|
-
return text
|
|
71
|
-
|
|
72
|
-
# recursion
|
|
73
|
-
def _recursive(self, text, ctx, layerstack):
|
|
74
|
-
current = text
|
|
75
|
-
for _ in range(self.max_depth):
|
|
76
|
-
result = self._resolve_once(current, ctx, layerstack)
|
|
77
|
-
if result == current:
|
|
78
|
-
return result
|
|
79
|
-
current = result
|
|
80
|
-
return current
|
|
81
|
-
|
|
82
|
-
# public api
|
|
83
|
-
def res(self, text, *, extra_maps=None, override_maps=None, **ctx):
|
|
84
|
-
if override_maps is not None:
|
|
85
|
-
temp = LayerStack([Layer("override", override_maps, priority=0)])
|
|
86
|
-
return self._recursive(text, ctx, temp)
|
|
87
|
-
|
|
88
|
-
if extra_maps:
|
|
89
|
-
temp = self.layers.clone()
|
|
90
|
-
temp.add_layer(Layer("extra", extra_maps, priority=999))
|
|
91
|
-
return self._recursive(text, ctx, temp)
|
|
92
|
-
|
|
93
|
-
if self.cache_enabled:
|
|
94
|
-
key = (text, tuple(sorted(ctx.items())))
|
|
95
|
-
cached = self.cache.get(key)
|
|
96
|
-
if cached is not None:
|
|
97
|
-
return cached
|
|
98
|
-
|
|
99
|
-
result = self._recursive(text, ctx, self.layers)
|
|
100
|
-
|
|
101
|
-
if self.cache_enabled:
|
|
102
|
-
self.cache.set(key, result)
|
|
103
|
-
|
|
104
|
-
return result
|
|
105
|
-
|
|
106
|
-
# global resolver
|
|
107
|
-
_DEFAULT = MapResolver()
|
|
108
|
-
|
|
109
|
-
def res(text, **ctx):
|
|
110
|
-
return _DEFAULT.res(text, **ctx)
|
mapres-1.1.2/src/mapres/utils.py
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|