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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mapres
3
- Version: 1.1.2
3
+ Version: 2.0
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 = "1.1.2"
10
+ version = "2.0"
11
11
  description = "A powerfull mapping utility"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.9"
@@ -1,5 +1,5 @@
1
1
  class LRUCache:
2
- def __init__(self, max_size=1024):
2
+ def __init__(self, max_size: int = 1024):
3
3
  self.max_size = max_size
4
4
  self.data = {}
5
5
 
@@ -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
 
@@ -4,5 +4,5 @@ class MapResError(Exception):
4
4
  class MissingKeyError(MapResError):
5
5
  pass
6
6
 
7
- class SyntaxError(MapResError):
7
+ class MapSyntaxError(MapResError):
8
8
  pass
@@ -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
- # layerstack manages ordered layers
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mapres
3
- Version: 1.1.2
3
+ Version: 2.0
4
4
  Summary: A powerfull mapping utility
5
5
  Author: iFamished
6
6
  License: MIT
@@ -7,7 +7,6 @@ src/mapres/datamap.py
7
7
  src/mapres/exceptions.py
8
8
  src/mapres/layers.py
9
9
  src/mapres/resolver.py
10
- src/mapres/utils.py
11
10
  src/mapres.egg-info/PKG-INFO
12
11
  src/mapres.egg-info/SOURCES.txt
13
12
  src/mapres.egg-info/dependency_links.txt
@@ -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)
@@ -1,2 +0,0 @@
1
- def is_datamap(obj):
2
- return hasattr(obj, "as_map")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes