1#!/usr/bin/env python3
2"""
3
4"""
5# ruff: noqa: N801, ANN001, ANN002, ANN003
6# Imports:
7from __future__ import annotations
8
9# ##-- stdlib imports
10import datetime
11import enum
12import functools as ftz
13import itertools as itz
14import logging as logmod
15import pathlib as pl
16import re
17import time
18import types
19import collections
20import contextlib
21import hashlib
22from copy import deepcopy
23from uuid import UUID, uuid1
24from weakref import ref
25import atexit # for @atexit.register
26import faulthandler
27# ##-- end stdlib imports
28
29from jgdv import identity_fn
30from jgdv.structs.strang import _interface as StrangAPI # noqa: N812
31
32# ##-- types
33# isort: off
34import abc
35import collections.abc
36from typing import TYPE_CHECKING, cast, assert_type, assert_never
37from typing import Generic, NewType, Any
38# Protocols:
39from typing import Protocol, runtime_checkable
40# Typing Decorators:
41from typing import no_type_check, final, override, overload
42from collections.abc import Mapping
43
44if TYPE_CHECKING:
45 from typing import Final
46 from typing import ClassVar, LiteralString
47 from typing import Never, Self, Literal
48 from typing import TypeGuard
49 from collections.abc import Iterable, Iterator, Callable, Generator
50 from collections.abc import Sequence, MutableMapping, Hashable
51
52 from jgdv import Maybe, Rx, Ident, RxStr, Ctor, CHECKTYPE, FmtStr
53 from ._util._interface import Expander_p, ExpInst_d
54
55 type LitFalse = Literal[False]
56 type KeyMark = DKeyMarkAbstract_e|LitFalse|str|type|tuple[KeyMark, ...]
57##--|
58
59# isort: on
60# ##-- end types
61
62##-- logging
63logging = logmod.getLogger(__name__)
64##-- end logging
65
66# Vars:
67DEFAULT_COUNT : Final[int] = 0
68LIFT_EXPANSION_PATTERN : Final[str] = "L"
69FMT_PATTERN : Final[Rx] = re.compile(r"[wdi]+")
70EXPANSION_LIMIT_PATTERN : Final[Rx] = re.compile(r"e(\d+)")
71INDIRECT_SUFFIX : Final[Ident] = "_"
72KEY_PATTERN : Final[RxStr] = "{(.+?)}"
73OBRACE : Final[str] = "{"
74MAX_DEPTH : Final[int] = 10
75MAX_KEY_EXPANSIONS : Final[int] = 200
76PAUSE_COUNT : Final[int] = 0
77RECURSION_GUARD : Final[int] = 10
78PARAM_IGNORES : Final[tuple[str, str]] = ("_", "_ex")
79
80RAWKEY_ID : Final[str] = "_rawkeys"
81FORCE_ID : Final[str] = "force"
82ARGS_K : Final[Ident] = "args"
83KWARGS_K : Final[Ident] = "kwargs"
84
85DEFAULT_DKEY_KWARGS : Final[list[str]] = [
86 "ctor", "check", "mark", "fallback",
87 "max_exp", "fmt", "help", "implicit", "conv",
88 "named",
89 RAWKEY_ID, FORCE_ID,
90 ]
91
92##--| Error Messages
93UnknownDKeyCtorType : Final[str] = "Unknown type passed to construct dkey"
94InsistentKeyFailure : Final[str] = "An insistent key was not built"
95KeyBuildFailure : Final[str] = "No key was built"
96NoMark : Final[str] = "Mark has to be a value"
97MarkConflictsWithMulti : Final[str] = "Mark is MULTI but multi=False"
98MarkLacksACtor : Final[str] = "Couldn't find a ctor for mark"
99MarkConversionConflict : Final[str] = "Kwd Mark/Conversion Conflict"
100UnexpectedKwargs : Final[str] = "Key got unexpected kwargs"
101RegistryLacksMark : Final[str] = "Can't register when the mark is None"
102RegistryConflict : Final[str] = "API.Key_p Registry conflict"
103ConvParamTooLong : Final[str] = "Conversion Parameters For Dkey's Can't Be More Than A Single Char"
104ConvParamConflict : Final[str] = "Conversion Param Conflict"
105# Enums:
106
[docs]
107class DKeyMarkAbstract_e(StrangAPI.StrangMarkAbstract_e):
108
[docs]
109 @classmethod
110 def default(cls) -> Maybe: ...
111
[docs]
112 @classmethod
113 def null(cls) -> Maybe: ...
114
[docs]
115 @classmethod
116 def multi(cls) -> Maybe: ...
117
[docs]
118class DKeyMark_e(DKeyMarkAbstract_e):
119 """
120 Enums for how to use/build a dkey
121
122 """
123 ARGS = enum.auto() # -> list
124 KWARGS = enum.auto() # -> dict
125 POSTBOX = enum.auto() # -> list
126
[docs]
127 @classmethod
128 def default(cls) -> Any: # noqa: ANN401
129 return Any
130
[docs]
131 @classmethod
132 def null(cls) -> Maybe:
133 return False
134
[docs]
135 @classmethod
136 def indirect(cls) -> type:
137 return Mapping
138
[docs]
139 @classmethod
140 def multi(cls) -> type:
141 return list
142
143##--| Data
144
[docs]
145class RawKey_d:
146 """ Utility class for parsed {}-format string parameters.
147
148 ::
149
150 see: https://peps.python.org/pep-3101/
151 and: https://docs.python.org/3/library/string.html#format-string-syntax
152
153 Provides the data from string.Formatter.parse, but in a structure
154 instead of a tuple.
155 """
156 __slots__ = ("convert", "format", "key", "prefix")
157 prefix : str
158 key : Maybe[str]
159 format : Maybe[str]
160 convert : Maybe[str]
161
162 def __init__(self, **kwargs) -> None:
163 self.prefix = kwargs.pop("prefix")
164 self.key = kwargs.pop("key", None)
165 self.format = kwargs.pop("format", None)
166 self.convert = kwargs.pop("convert", None)
167 assert(not bool(kwargs)), kwargs
168
169 def __getitem__(self, i) -> Maybe[str]:
170 match i:
171 case 0:
172 return self.prefix
173 case 1:
174 return self.key
175 case 2:
176 return self.format
177 case 3:
178 return self.convert
179 case _:
180 msg = "Tried to access a bad element of DKeyParams"
181 raise ValueError(msg, i)
182
183 def __bool__(self) -> bool:
184 return bool(self.key)
185
186 def __repr__(self) -> str:
187 return f"<RawkKey: {self.joined()}>"
188
[docs]
189 def joined(self) -> str:
190 """ Returns the key and params as one string
191
192 eg: blah, fmt=5, conv=p -> blah:5!p
193 """
194 args : list[str]
195 if not bool(self.key):
196 return ""
197
198 assert(self.key is not None)
199 args = [self.key]
200 if bool(self.format):
201 assert(self.format is not None)
202 args += [":", self.format]
203 if bool(self.convert):
204 assert(self.convert is not None)
205 args += ["!", self.convert]
206
207 return "".join(args)
208
[docs]
209 def wrapped(self) -> str:
210 """ Returns this key in simple wrapped form
211
212 (it ignores format, conv params and prefix)
213
214 eg: blah -> {blah}
215 """
216 return "{%s}" % self.key # noqa: UP031
217
[docs]
218 def anon(self) -> str:
219 """ Make a format str of this key, with anon variables.
220
221 eg: blah {key:f!p} -> blah {}
222 """
223 if bool(self.key):
224 return "%s{}" % self.prefix # noqa: UP031
225
226 return self.prefix or ""
227
[docs]
228 def direct(self) -> str:
229 """ Returns this key in direct form
230
231 ::
232
233 eg: blah -> blah
234 blah_ -> blah
235 """
236 return (self.key or "").removesuffix(INDIRECT_SUFFIX)
237
[docs]
238 def indirect(self) -> str:
239 """ Returns this key in indirect form
240
241 ::
242
243 eg: blah -> blah_
244 blah_ -> blah_
245 """
246 match self.key:
247 case str() as k if k.endswith(INDIRECT_SUFFIX):
248 return k
249 case str() as k:
250 return f"{k}{INDIRECT_SUFFIX}"
251 case _:
252 return ""
253
[docs]
254 def is_indirect(self) -> bool:
255 match self.key:
256 case str() as k if k.endswith(INDIRECT_SUFFIX):
257 return True
258 case _:
259 return False
260
[docs]
261class DKey_d(StrangAPI.Strang_d):
262 """ Data of a DKey """
263 __slots__ = ("convert", "expansion_type", "fallback", "format", "help", "max_expansions", "multi", "name", "raw", "typecheck")
264 name : Maybe[str]
265 raw : tuple[RawKey_d, ...]
266 expansion_type : Ctor
267 typecheck : CHECKTYPE
268 fallback : Maybe[Any]
269 format : Maybe[FmtStr]
270 convert : Maybe[FmtStr]
271 help : Maybe[str]
272 max_expansions : Maybe[int]
273 multi : bool
274
275 def __init__(self, **kwargs) -> None:
276 super().__init__()
277 self.name = kwargs.pop("name", None)
278 self.raw = tuple(kwargs.pop(RAWKEY_ID, ()))
279 self.expansion_type = kwargs.pop("ctor", identity_fn)
280 self.typecheck = kwargs.pop("check", Any)
281 self.fallback = kwargs.pop("fallback", None)
282 self.format = kwargs.pop("format", None)
283 self.convert = kwargs.pop("convert", None)
284 self.help = kwargs.pop("help", None)
285 self.max_expansions = kwargs.pop("max_exp", None)
286 self.multi = kwargs.pop("multi", False)
287
288##--| Section Specs
289DKEY_SECTIONS : Final[StrangAPI.Sections_d] = StrangAPI.Sections_d(
290 StrangAPI.Sec_d("body", None, None, str, None, True), # noqa: FBT003
291)
292##--| Protocols
293
[docs]
294@runtime_checkable
295class Key_p(StrangAPI.Strang_p, Protocol):
296 """ The protocol for a Key, something that used in a template system"""
297 _extra_kwargs : ClassVar[set[str]]
298 _processor : ClassVar
299 _expander : ClassVar[Expander_p]
300 data : DKey_d
301
[docs]
302 @staticmethod
303 def MarkOf[T:Key_p](cls:type[T]|T) -> KeyMark: ... # noqa: N802, PLW0211
304
[docs]
305 def redirect(self, spec=None) -> Key_p: ...
306
[docs]
307 def expand(self, *sources, rec=False, insist=False, chain:Maybe[list[Key_p]]=None, on_fail=Any, locs:Maybe[Mapping]=None, **kwargs) -> str: ...
308
[docs]
309 def var_name(self) -> str: ...
310
[docs]
311@runtime_checkable
312class MultiKey_p(Protocol):
313
[docs]
314 def keys(self) -> list[Key_p]: ...
315
[docs]
316 def _multi(self) -> Literal[True]: ...
317
[docs]
318@runtime_checkable
319class IndirectKey_p(Protocol):
320
[docs]
321 def _indirect(self) -> Literal[True]: ...
322##--| Combined Interfaces
323
[docs]
324@runtime_checkable
325class NonKey_p(Protocol):
326
[docs]
327 def _nonkey(self) -> Literal[True]: ...