mapres 2.3__tar.gz → 2.4a1__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.3
3
+ Version: 2.4a1
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.3"
10
+ version = "2.4-alpha.1"
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',
@@ -2,10 +2,10 @@ import re
2
2
  from dataclasses import dataclass, fields, is_dataclass
3
3
 
4
4
  class syntax:
5
- braces = r"\{([^{}]+)\}"
6
- dollars = r"\$\{([^{}]+)\}"
7
- angles = r"<([^<>]+)>"
8
- percents = r"%([^%]+)%"
5
+ braces = r'\{([^{}]+)\}'
6
+ dollars = r'\$\{([^{}]+)\}'
7
+ angles = r'<([^<>]+)>'
8
+ percents = r'%([^%]+)%'
9
9
 
10
10
 
11
11
  @dataclass
@@ -17,11 +17,11 @@ class DataMap:
17
17
  def as_map(self):
18
18
  result = {}
19
19
  for f in fields(self):
20
- if not f.init or f.name in ("__syntax__", "__mode__"):
20
+ if not f.init or f.name in ('__syntax__', '__mode__'):
21
21
  continue
22
22
  val = getattr(self, f.name)
23
- if self.__mode__ == "dynamic":
24
- providers = getattr(self, "providers", None)
23
+ if self.__mode__ == 'dynamic':
24
+ providers = getattr(self, 'providers', None)
25
25
  if isinstance(providers, dict) and f.name in providers:
26
26
  val = providers[f.name]
27
27
  if callable(val):
@@ -31,7 +31,7 @@ class DataMap:
31
31
 
32
32
  @classmethod
33
33
  def get_syntax(cls):
34
- return getattr(cls, "__syntax__", syntax.braces)
34
+ return getattr(cls, '__syntax__', syntax.braces)
35
35
 
36
36
 
37
37
  # decorator
@@ -39,20 +39,20 @@ def datamap(
39
39
  _cls = None,
40
40
  *,
41
41
  syntax: str = syntax.braces,
42
- mode: bool | None = None,
42
+ mode: bool | str | None = None,
43
43
  ):
44
44
  '''@datamap decorator with optional values'''
45
45
  def wrap(cls):
46
46
  cls.__syntax__ = syntax
47
47
  cls.__mode__ = mode
48
48
  namespace = dict(cls.__dict__)
49
- namespace["__dict__"] = {}
49
+ namespace['__dict__'] = {}
50
50
  # preserve any rules class defined in the original namespace by attaching it to the generated class
51
- rules_obj = namespace.get("rules", None)
51
+ rules_obj = namespace.get('rules', None)
52
52
  cls = type(cls.__name__, (DataMap,), namespace)
53
53
  # attach rules (if present) to the new class so validators can read them via the datamap class
54
54
  if rules_obj is not None:
55
- setattr(cls, "rules", rules_obj)
55
+ setattr(cls, 'rules', rules_obj)
56
56
  return dataclass(frozen=False)(cls)
57
57
  return wrap if _cls is None else wrap(_cls)
58
58
 
@@ -0,0 +1,75 @@
1
+ from mapres.datamap import datamap, syntax
2
+
3
+ @datamap(syntax=syntax.angles) # produces <>
4
+ class ColorMap:
5
+ black: str = '\033[30m'
6
+ dark_blue: str = '\033[34m'
7
+ dark_green: str = '\033[32m'
8
+ dark_aqua: str = '\033[36m'
9
+ dark_red: str = '\033[31m'
10
+ dark_purple: str = '\033[35m'
11
+ gold: str = '\033[33m'
12
+ gray: str = '\033[37m'
13
+ dark_gray: str = '\033[90m'
14
+ blue: str = '\033[94m'
15
+ green: str = '\033[92m'
16
+ aqua: str = '\033[96m'
17
+ red: str = '\033[91m'
18
+ light_purple: str = '\033[95m'
19
+ yellow: str = '\033[93m'
20
+ white: str = '\033[97m'
21
+ reset: str = '\033[0m'
22
+ bold: str = '\033[1m'
23
+ italic: str = '\033[3m'
24
+
25
+ def as_dict(self):
26
+ return {f'<{k}>': getattr(self, k) for k in self.__dataclass_fields__}
27
+
28
+ # ASCII color map
29
+ ascii_colors = ColorMap()
30
+
31
+ # Minecraft color map
32
+ mc_colors = ColorMap(
33
+ black='§0',
34
+ dark_blue='§1',
35
+ dark_green='§2',
36
+ dark_aqua='§3',
37
+ dark_red='§4',
38
+ dark_purple='§5',
39
+ gold='§6',
40
+ gray='§7',
41
+ dark_gray='§8',
42
+ blue='§9',
43
+ green='§a',
44
+ aqua='§b',
45
+ red='§c',
46
+ light_purple='§d',
47
+ yellow='§e',
48
+ white='§f',
49
+ reset='§r',
50
+ bold='§l',
51
+ italic='§o'
52
+ )
53
+
54
+ # Color strip map
55
+ strip_colors = ColorMap(
56
+ black='',
57
+ dark_blue='',
58
+ dark_green='',
59
+ dark_aqua='',
60
+ dark_red='',
61
+ dark_purple='',
62
+ gold='',
63
+ gray='',
64
+ dark_gray='',
65
+ blue='',
66
+ green='',
67
+ aqua='',
68
+ red='',
69
+ light_purple='',
70
+ yellow='',
71
+ white='',
72
+ reset='',
73
+ bold='',
74
+ italic=''
75
+ )
@@ -1,31 +1,36 @@
1
- from datetime import datetime
1
+ from datetime import datetime, timezone
2
2
  from zoneinfo import ZoneInfo
3
3
  from mapres.datamap import datamap, syntax
4
4
 
5
5
 
6
+ def safe_zoneinfo(tz: str):
7
+ try:
8
+ return ZoneInfo(tz)
9
+ except Exception:
10
+ return timezone.utc
11
+
12
+
6
13
  @datamap(syntax=syntax.percents, mode='dynamic')
7
14
  class TimeMap:
8
- # default timezone
9
- _default_tz = ZoneInfo('UTC')
10
-
11
- # dynamic fields
12
- hh: str = None
13
- h: str = None
14
- h12: str = None
15
- hh12: str = None
16
- ampm: str = None
17
- mm: str = None
18
- m: str = None
19
- ss: str = None
20
- s: str = None
21
- ms: str = None
22
- YYYY: str = None
23
- MM: str = None
24
- DD: str = None
25
- weekday: str = None
15
+ _default_tz = safe_zoneinfo("America/Chicago")
16
+
17
+ hh: str | None = None
18
+ h: str | None = None
19
+ h12: str | None = None
20
+ hh12: str | None = None
21
+ ampm: str | None = None
22
+ mm: str | None = None
23
+ m: str | None = None
24
+ ss: str | None = None
25
+ s: str | None = None
26
+ ms: str | None = None
27
+ YYYY: str | None = None
28
+ MM: str | None = None
29
+ DD: str | None = None
30
+ weekday: str | None = None
26
31
 
27
32
  def __init__(self, tz: str | None = None):
28
- self.TZ = ZoneInfo(tz) if tz else self._default_tz
33
+ self.TZ = safe_zoneinfo(tz) if tz else self._default_tz
29
34
 
30
35
  @property
31
36
  def providers(self):
@@ -6,7 +6,7 @@ from .exceptions import MapResError, MissingKeyError, MapSyntaxError
6
6
 
7
7
 
8
8
  class MapResolver:
9
- """
9
+ '''
10
10
  Core resolution engine for mapres.
11
11
 
12
12
  Handles:
@@ -14,9 +14,9 @@ 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__(
21
21
  self,
22
22
  layers: list | None = None,
@@ -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:
@@ -40,81 +42,93 @@ class MapResolver:
40
42
  text = stage(text, ctx, self)
41
43
  return text
42
44
  except Exception as exc:
43
- raise MapResError(f"Pipeline stage failed: {exc}") from exc
45
+ raise MapResError(f'Pipeline stage failed: {exc}') from exc
44
46
 
45
47
  # syntax providers
46
48
  def _apply_syntax(self, text: str, ctx: dict) -> str:
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
- raise MissingKeyError(f"Missing key '{k}' in syntax provider {provider}")
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
- f"Regex error in syntax provider {provider}: {exc}"
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
- if isinstance(m, type) and hasattr(m, "as_map"):
73
+ if isinstance(m, type) and hasattr(m, 'as_map'):
68
74
  m = m()
69
- if hasattr(m, "as_map"):
75
+ if hasattr(m, 'as_map'):
70
76
  syntax_patterns.append(m.get_syntax())
71
77
  elif isinstance(m, dict):
72
- pattern = ctx.get("syntax")
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
- if isinstance(layer, type) and hasattr(layer, "as_map"):
90
+ if isinstance(layer, type) and hasattr(layer, 'as_map'):
82
91
  layer = layer()
83
- if hasattr(layer, "as_map"):
92
+ if hasattr(layer, 'as_map'):
84
93
  d = layer.as_map()
85
94
  elif isinstance(layer, dict):
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
 
92
102
  if k in ctx:
93
103
  return str(ctx[k])
94
104
 
95
- raise MissingKeyError(f"Missing key '{k}' in any layer or context")
105
+ raise MissingKeyError(f'Missing key "{k}" in any layer or context')
96
106
 
97
107
  try:
98
108
  text = re.sub(pattern, repl, text)
99
109
  except re.error as exc:
100
- raise MapSyntaxError(f"Regex error in syntax pattern {pattern}: {exc}") from exc
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
- raise MissingKeyError(f"Missing ctx key '{k}'")
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:
117
- raise MapSyntaxError(f"Regex error in ctx syntax: {exc}") from exc
131
+ raise MapSyntaxError(f'Regex error in ctx syntax: {exc}') from exc
118
132
 
119
133
  # one pass
120
134
  def _resolve_once(self, text: str, ctx: dict, layerstack: LayerStack) -> str:
@@ -124,45 +138,53 @@ 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
145
150
  ) -> str:
146
- """
151
+ '''
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
- temp = LayerStack([Layer("override", override_maps, priority=0)])
152
- return self._recursive(text, ctx, temp)
163
+ temp = LayerStack([Layer('override', override_maps, priority=0)])
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
- temp.add_layer(Layer("extra", extra_maps, priority=999))
157
- return self._recursive(text, ctx, temp)
169
+ temp.add_layer(Layer('extra', extra_maps, priority=999))
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)
@@ -172,14 +194,33 @@ class MapResolver:
172
194
  except MapResError:
173
195
  raise
174
196
  except Exception as exc:
175
- raise MapResError(f"Unhandled resolver error: {exc}") from exc
197
+ raise MapResError(f'Unhandled resolver error: {exc}') from exc
176
198
 
177
199
 
178
- # global resolver
179
- _DEFAULT = MapResolver()
200
+ # ----------------------------------
201
+ # ---------- SIMPLE USAGE ----------
202
+ # ----------------------------------
203
+ _DEFAULT_RESOLVER = MapResolver() # global resolver
180
204
 
181
- def res(text: str, **ctx) -> str:
205
+ def setGlobalMaps(maps, *, name=None, priority=0):
182
206
  """
183
- Resolve using the global default resolver.
207
+ Register a global map or datamap class into the default resolver.
208
+
209
+ maps: datamap class OR dict
210
+ name: optional layer name (default = class name or 'global')
211
+ priority: layer priority (lower = earlier)
184
212
  """
185
- return _DEFAULT.res(text, **ctx)
213
+ layer_name = name or getattr(maps, "__name__", "global")
214
+ layer = Layer(layer_name, maps=[maps], priority=priority)
215
+ _DEFAULT_RESOLVER.layers.add_layer(layer)
216
+
217
+ def setDefaultPasses(passes: int):
218
+ if passes < 1:
219
+ raise MapResError("Passes cannot be less than 1")
220
+ _DEFAULT_RESOLVER.passes_default = passes
221
+
222
+ def res(text: str, passes=_DEFAULT_PASSES, **ctx) -> str:
223
+ '''
224
+ Simple resolution using the global default resolver.
225
+ '''
226
+ return _DEFAULT_RESOLVER.res(text, passes=passes, **ctx)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mapres
3
- Version: 2.3
3
+ Version: 2.4a1
4
4
  Summary: A powerfull mapping utility
5
5
  Author: iFamished
6
6
  License: MIT
@@ -1,75 +0,0 @@
1
- from mapres.datamap import datamap, syntax
2
-
3
- @datamap(syntax=syntax.angles) # produces <>
4
- class ColorMap:
5
- black: str = "\033[30m"
6
- dark_blue: str = "\033[34m"
7
- dark_green: str = "\033[32m"
8
- dark_aqua: str = "\033[36m"
9
- dark_red: str = "\033[31m"
10
- dark_purple: str = "\033[35m"
11
- gold: str = "\033[33m"
12
- gray: str = "\033[37m"
13
- dark_gray: str = "\033[90m"
14
- blue: str = "\033[94m"
15
- green: str = "\033[92m"
16
- aqua: str = "\033[96m"
17
- red: str = "\033[91m"
18
- light_purple: str = "\033[95m"
19
- yellow: str = "\033[93m"
20
- white: str = "\033[97m"
21
- reset: str = "\033[0m"
22
- bold: str = "\033[1m"
23
- italic: str = "\033[3m"
24
-
25
- def as_dict(self):
26
- return {f"<{k}>": getattr(self, k) for k in self.__dataclass_fields__}
27
-
28
- # ASCII color map
29
- ascii_colors = ColorMap()
30
-
31
- # Minecraft color map
32
- mc_colors = ColorMap(
33
- black="§0",
34
- dark_blue="§1",
35
- dark_green="§2",
36
- dark_aqua="§3",
37
- dark_red="§4",
38
- dark_purple="§5",
39
- gold="§6",
40
- gray="§7",
41
- dark_gray="§8",
42
- blue="§9",
43
- green="§a",
44
- aqua="§b",
45
- red="§c",
46
- light_purple="§d",
47
- yellow="§e",
48
- white="§f",
49
- reset="§r",
50
- bold="§l",
51
- italic="§o"
52
- )
53
-
54
- # Color strip map
55
- strip_colors = ColorMap(
56
- black="",
57
- dark_blue="",
58
- dark_green="",
59
- dark_aqua="",
60
- dark_red="",
61
- dark_purple="",
62
- gold="",
63
- gray="",
64
- dark_gray="",
65
- blue="",
66
- green="",
67
- aqua="",
68
- red="",
69
- light_purple="",
70
- yellow="",
71
- white="",
72
- reset="",
73
- bold="",
74
- italic=""
75
- )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes