Source code for jgdv.structs.strang._interface

  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
[docs] 347class StrangFormatter_p(Protocol): 348 """ A string.Formatter with some Strang-specific methods """ 349
[docs] 350 def format(self, format_string:str, /, *args:Any, **kwargs:Any) -> str: ... # noqa: ANN401
351
[docs] 352 def get_value(self, key:str, args:Any, kwargs:Any) -> str: ... # noqa: ANN401
353
[docs] 354 def convert_field(self, value:Any, conversion:Any) -> str: ... # noqa: ANN401
355
[docs] 356 def expanded_str(self, data:Strang_p, *, stop:Maybe[int]=None) -> str: ...
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]: ...