1#!/usr/bin/env python3
2"""
3The Interface for Strang.
4
5Strang Enums:
6- StrangMarkAbstract_e
7
8Describes the internal data structs:
9- Sec_d : A Single section spec
10- Sections_d : Collects the sec_d's. ClassVar
11- Strang_d : Instance data of a strang beyond the normal str's
12
13"""
14# Imports:
15from __future__ import annotations
16
17# ##-- stdlib imports
18import datetime
19import enum
20import functools as ftz
21import itertools as itz
22import logging as logmod
23import pathlib as pl
24import re
25import time
26import collections
27import contextlib
28import hashlib
29from copy import deepcopy
30from uuid import UUID, uuid1
31from weakref import ref
32# ##-- end stdlib imports
33
34from jgdv._abstract.protocols.str import String_p
35
36# ##-- types
37# isort: off
38import abc
39import collections.abc
40from typing import TYPE_CHECKING, cast, assert_type, assert_never
41from typing import Generic, NewType, TypeVar
42# Protocols:
43from typing import Protocol, runtime_checkable
44# Typing Decorators:
45from typing import no_type_check, final, override, overload
46from collections.abc import Sized
47from collections import UserString
48from types import UnionType
49
50if TYPE_CHECKING:
51 from jgdv._abstract.protocols.pre_processable import PreProcessor_p
52 from jgdv import Maybe, Rx
53 from typing import Final
54 from typing import ClassVar, Any, LiteralString
55 from typing import Never, Self, Literal
56 from typing import TypeGuard, SupportsIndex
57 from collections.abc import Iterable, Iterator, Callable, Generator
58 from collections.abc import Sequence, Mapping, MutableMapping, Hashable
59 import string
60 from enum import Enum
61
62##--|
63
64# isort: on
65# ##-- end types
66
67# ##-- Generated Exports
68__all__ = ( # noqa: RUF022
69# -- Types
70"FindSlice", "FullSlice", "ItemIndex", "MSlice", "MarkIndex", "PushVal", "SectionIndex",
71"WordIndex",
72# -- Classes
73"CodeRefHeadMarks_e", "DefaultBodyMarks_e", "DefaultHeadMarks_e",
74"Importable_p", "Sec_d", "Sections_d", "StrangFormatter_p", "StrangMarkAbstract_e",
75"StrangMod_p", "StrangUUIDs_p", "Strang_d", "Strang_p",
76
77)
78# ##-- end Generated Exports
79
80##-- logging
81logging = logmod.getLogger(__name__)
82##-- end logging
83
84##--| Vars
85FMT_PATTERN : Final[Rx] = re.compile("^(h?)(t?)(p?)")
86TYPE_RE : Final[Rx] = re.compile(r"<(.+?)(?::(.+?))?>") # TODO name the groups
87TYPE_ITER_RE : Final[Rx] = re.compile(r"(<)(.+?)(?::(.+?))?(>)")
88MARK_RE : Final[Rx] = re.compile(r"(\$.+?\$)")
89MARK_ITER_RE : Final[Rx] = re.compile(r"(\$)(.+?)(\$)")
90ARGS_RE : Final[Rx] = re.compile(r"\[(.+?)\]$")
91ARGS_CHARS : Final[tuple[str, str, str]] = "[", ",", "]"
92CASE_DEFAULT : Final[str] = "."
93END_DEFAULT : Final[str] = "::"
94INST_K : Final[str] = "instanced"
95GEN_K : Final[str] = "gen_uuid"
96STRGET : Final[Callable] = str.__getitem__
97STRCON : Final[Callable[[str,str], bool]] = str.__contains__
98UUID_WORD : Final[str] = "<uuid>"
99
100SEC_END_MSG : Final[str] = "Only the last section has no end marker"
101
102##--| Enums
103
[docs]
104class StrangMarkAbstract_e(enum.StrEnum):
105
[docs]
106 @classmethod
107 def default(cls) -> Maybe:
108 return None
109
[docs]
110 @classmethod
111 def implicit(cls) -> set:
112 return set()
113
[docs]
114 @classmethod
115 def skip[T](cls:type[T]) -> Maybe[T]:
116 return None
117
[docs]
118 @classmethod
119 def idempotent(cls) -> set[str]:
120 return set()
121
[docs]
122class DefaultHeadMarks_e(StrangMarkAbstract_e):
123 """ Markers used in a Strang's head """
124 basic = "$basic$"
125
[docs]
126class DefaultBodyMarks_e(StrangMarkAbstract_e):
127 """ Markers Used in a base Strang's body """
128
129 head = "$head$"
130 gen = "$gen$"
131 empty = ""
132 hide = "_"
133 extend = "+"
134
[docs]
135 @override
136 @classmethod
137 def default(cls) -> str:
138 return cls.head
139
[docs]
140 @override
141 @classmethod
142 def implicit(cls) -> set[str]:
143 return {cls.hide, cls.empty}
144
[docs]
145 @override
146 @classmethod
147 def skip(cls) -> Maybe[DefaultBodyMarks_e]:
148 return cls.empty
149
[docs]
150 @override
151 @classmethod
152 def idempotent(cls) -> set[str]:
153 return {cls.head, cls.gen}
154
[docs]
155class CodeRefHeadMarks_e(StrangMarkAbstract_e):
156 """ Available Group values of CodeRef strang's """
157 module = "module"
158 cls = "cls"
159 value = "value"
160 fn = "fn"
161
162 val = "value"
163
[docs]
164 @override
165 @classmethod
166 def default(cls) -> str:
167 return cls.fn
168
[docs]
169 @override
170 @classmethod
171 def idempotent(cls) -> set[str]:
172 return {cls.module, cls.cls, cls.value, cls.fn}
173
174
175##--|
176type FullSlice = slice[None, None, None]
177type MSlice = slice[Maybe[int], Maybe[int], Maybe[int]]
178type SectionIndex = str|int
179type WordIndex = tuple[SectionIndex, int]
180type MarkIndex = tuple[SectionIndex, StrangMarkAbstract_e]
181type FindSlice = str|StrangMarkAbstract_e|WordIndex|MarkIndex
182type ItemIndex = SectionIndex | FullSlice | MSlice | tuple[ItemIndex, ...]
183type PushVal = Maybe[str | StrangMarkAbstract_e | UUID]
184
185HEAD_TYPES = str|UUID|DefaultBodyMarks_e
186BODY_TYPES = str|UUID|DefaultBodyMarks_e
187##--| Data
188
[docs]
189class Sec_d:
190 """ Data of a named Strang section
191
192 for an example section 'a.2.c.+::d'
193 - case : the word boundary. = '.'
194 - end : the rhs end str. = '::'
195 - types : allowed types. = str|int
196 - marks : StrangMarkAbstract_e of words with a meta meaning. = '+'
197 - required : a strang errors if a required section isnt found
198
199 - idx : the index of the section
200
201 TODO Maybe 'type_re' and 'mark_re'
202
203 """
204 __slots__ = ("case", "end", "idx", "marks", "name", "required", "types")
205 idx : int
206 name : Final[str]
207 case : Final[Maybe[str]]
208 end : Final[Maybe[str]]
209 types : Final[type|UnionType]
210 marks : Final[Maybe[type[StrangMarkAbstract_e]]]
211 required : Final[bool]
212
213 def __init__(self, name:str, case:Maybe[str], end:Maybe[str], types:type|UnionType, marks:Maybe[type[StrangMarkAbstract_e]], required:bool=True, *, idx:int=-1) -> None: # noqa: FBT001, FBT002, PLR0913
214 assert(case is None or bool(case))
215 assert(end is None or bool(end))
216 self.idx = idx
217 self.name = name.lower()
218 self.case = case
219 self.end = end
220 self.types = types
221 self.marks = marks
222 self.required = required
223
224 def __contains__(self, other:type|StrangMarkAbstract_e) -> bool:
225 match other:
226 case type() as x:
227 return issubclass(x, self.types)
228 case UnionType() as xs:
229 # Check its contained using its removal of duplicates
230 return (xs | self.types) == self.types
231 case StrangMarkAbstract_e() as x if self.marks:
232 return x in self.marks
233 case _:
234 return False
235
[docs]
236class Sections_d:
237 """
238 An object to hold information about word separation and sections,
239 a strang type is structured into these
240
241 Each Section is a Sec_d
242 TODO add format conversion specs
243 """
244 __slots__ = ("named", "order", "types")
245 named : Final[dict[str, int]]
246 order : Final[tuple[Sec_d, ...]]
247 types : type|UnionType
248
249 def __init__(self, *sections:tuple|Sec_d) -> None:
250 order : list[Sec_d] = []
251 for i, sec in enumerate(sections):
252 match sec:
253 case Sec_d() as obj:
254 obj.idx = i
255 order.append(obj)
256 case xs:
257 obj = Sec_d(*xs, idx=i)
258 order.append(obj)
259 else:
260 assert(all(x.end is not None for x in order[:-1])), SEC_END_MSG
261 self.order = tuple(order)
262 self.named = {x.name:i for i,x in enumerate(self.order)}
263
264 def __contains__(self, val:str) -> bool:
265 return val in self.named
266
267 def __getitem__(self, val:int|str) -> Sec_d:
268 match val:
269 case int() as i:
270 return self.order[i]
271 case str() as k if k in self.named:
272 return self.order[self.named[k]]
273 case x:
274 raise KeyError(x)
275
276 def __iter__(self) -> Iterator[Sec_d]:
277 return iter(self.order)
278
279 def __len__(self) -> int:
280 return len(self.order)
281
[docs]
282class Strang_d:
283 """ Extra Data of a Strang.
284 Sections are accessed by their index, so use cls._sections.named[name] to get the index
285
286 - sections : tuple[slice, ...] - Section boundaries
287 - sec_words : tuple[tuple[int, ...]] - lookup of (sec, word) -> WordIndex
288 - words : tuple[slice, ...] - Word Slices
289 - meta : tuple[Maybe, ...] - Flat word level meta data
290
291 """
292 __slots__ = ("args", "args_start", "flat_idx", "meta", "sec_words", "sections", "uuid", "words")
293 args_start : Maybe[int]
294 args : Maybe[tuple]
295 sections : tuple[slice, ...]
296 sec_words : tuple[tuple[int, ...], ...]
297 flat_idx : tuple[tuple[int, int], ...]
298 words : tuple[slice, ...]
299 meta : tuple[Maybe, ...]
300 uuid : Maybe[UUID]
301
302 def __init__(self, uuid:Maybe[UUID]=None) -> None:
303 self.args_start = None
304 self.args = None
305 self.sections = ()
306 self.sec_words = ()
307 self.words = ()
308 self.meta = ()
309 self.uuid = uuid
310
311
312##--| Default Section Specs
313HEAD_SEC : Final[Sec_d] = Sec_d("head", CASE_DEFAULT, END_DEFAULT, BODY_TYPES, DefaultHeadMarks_e, True) # noqa: FBT003
314BODY_SEC : Final[Sec_d] = Sec_d("body", CASE_DEFAULT, None, HEAD_TYPES, DefaultBodyMarks_e, True) # noqa: FBT003
315
316CODEREF_HEAD_SEC : Final[Sec_d] = Sec_d("head", CASE_DEFAULT, END_DEFAULT, HEAD_TYPES, CodeRefHeadMarks_e, False) # noqa: FBT003
317CODEREF_MODULE_SEC : Final[Sec_d] = Sec_d("module", CASE_DEFAULT, ":", HEAD_TYPES, DefaultBodyMarks_e, True) # noqa: FBT003
318CODEREF_VAL_SEC : Final[Sec_d] = Sec_d("value", CASE_DEFAULT, None, HEAD_TYPES, CodeRefHeadMarks_e, True) # noqa: FBT003
319
320STRANG_DEFAULT_SECS : Final[Sections_d] = Sections_d(HEAD_SEC, BODY_SEC)
321STRANG_ALT_SECS : Final[Sections_d] = Sections_d(
322 Sec_d("head", CASE_DEFAULT, END_DEFAULT, HEAD_TYPES, DefaultHeadMarks_e, True), # noqa: FBT003
323 Sec_d("body", CASE_DEFAULT, None, BODY_TYPES, DefaultBodyMarks_e, True), # noqa: FBT003
324)
325CODEREF_DEFAULT_SECS : Final[Sections_d] = Sections_d(CODEREF_HEAD_SEC, CODEREF_MODULE_SEC, CODEREF_VAL_SEC)
326##--| Protocols
327
[docs]
328@runtime_checkable
329class Importable_p(Protocol):
330 """ Marks a class as able to import code. Userd for CodeRef's."""
331
[docs]
332 def _does_imports(self) -> Literal[True]: ...
333
334
[docs]
335class StrangUUIDs_p(Protocol):
336
[docs]
337 def to_uniq(self, *args:str) -> Self: ...
338
[docs]
339 def de_uniq(self) -> Self: ...
340
[docs]
341class StrangMod_p(Protocol):
342
[docs]
343 def pop(self, *, top:bool=False) -> Strang_p: ...
344
[docs]
345 def push(self, *vals:PushVal) -> Strang_p: ...
346
357
[docs]
358@runtime_checkable
359class Strang_p(StrangUUIDs_p, StrangMod_p, String_p, Protocol):
360 """ The Main protocol describing a Strang. """
361 _processor : ClassVar[PreProcessor_p]
362 _formatter : ClassVar[string.Formatter]
363 _sections : ClassVar[Sections_d]
364 data : Strang_d
365
366 ##--| classmethods
[docs]
367 @classmethod
368 def sections(cls) -> Sections_d: ...
369
[docs]
370 @classmethod
371 def section(cls, arg:int|str) -> Sec_d: ...
372
373 ##--| dunders
374 @override
375 def __getitem__(self, i:ItemIndex) -> str: ... # type: ignore[override]
376
377 @override
378 def __lt__(self, other:object) -> bool: ...
379 @override
380 def __le__(self, other:object) -> bool: ...
381 ##--| properties
[docs]
382 @property
383 def base(self) -> Self: ...
384
[docs]
385 @property
386 def shape(self) -> tuple[int, ...]: ...
387
388 ##--| methods
[docs]
389 @override
390 def index(self, *sub:FindSlice, start:Maybe[int]=None, end:Maybe[int]=None) -> int: # type: ignore[override]
391 """index
392
393 Extended str.index, to handle marks and word slices
394
395 :param sub: The indices to slice
396 :param start: The start of the slice to cover.
397 :param end: The end of the slice to cover.
398
399 :returns: The index of the char
400 """
401 pass
402
[docs]
403 @override
404 def rindex(self, *sub:FindSlice, start:Maybe[int]=None, end:Maybe[int]=None) -> int: ... # type: ignore[override]
405
[docs]
406 def words(self, idx:SectionIndex, *, case:bool=False) -> list: ...
407
[docs]
408 def get(self, *args:SectionIndex|WordIndex) -> Any: ... # noqa: ANN401
409
[docs]
410 def args(self) -> Maybe[tuple]: ...
[docs]
411 def uuid(self) -> Maybe[UUID]: ...