Source code for jgdv.structs.dkey.keys

  1#!/usr/bin/env python3
  2"""
  3
  4"""
  5
  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 re
 16import time
 17import types
 18import weakref
 19from uuid import UUID, uuid1
 20
 21# ##-- end stdlib imports
 22
 23from . import _interface as API # noqa: N812
 24from ._interface import INDIRECT_SUFFIX, RAWKEY_ID, DKeyMark_e, Key_p, NonKey_p
 25from ._util._interface import (ExpInst_d, ExpInstChain_d, InstructionFactory_p,
 26                               SourceChain_d)
 27from .dkey import DKey
 28
 29# ##-- types
 30# isort: off
 31import abc
 32import collections.abc
 33from typing import TYPE_CHECKING, Generic, cast, assert_type, assert_never, Self
 34# Protocols:
 35from typing import Protocol, runtime_checkable
 36# Typing Decorators:
 37from typing import no_type_check, final, override, overload
 38from typing import Never
 39from typing import Any
 40from collections.abc import Mapping
 41
 42if TYPE_CHECKING:
 43   import pathlib as pl
 44   from jgdv import Maybe
 45   from typing import Final
 46   from typing import ClassVar, LiteralString
 47   from typing import Literal
 48   from typing import TypeGuard
 49   from collections.abc import Iterable, Iterator, Callable, Generator
 50   from collections.abc import Sequence, MutableMapping, Hashable
 51   from jgdv._abstract.protocols.general import SpecStruct_p
 52
 53# isort: on
 54# ##-- end types
 55
 56##-- logging
 57logging = logmod.getLogger(__name__)
 58##-- end logging
 59
[docs] 60class SingleDKey(DKey, mark=Any, default=True): 61 """ 62 A Single key with no extras. 63 ie: {x}. not {x}{y}, or {x}.blah. 64 """ 65 __slots__ = () 66 67 def __init__(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 68 super().__init__(*args, **kwargs) 69 match self.data.raw: 70 case [x] if self.data.convert is None: 71 self.data.convert = x.convert 72 self.data.format = x.format 73 case [_]: 74 pass 75 case None | []: 76 msg = "A Single Key has no raw key data" 77 raise ValueError(msg) 78 case [*xs]: 79 msg = "A Single Key got multiple raw key data" 80 raise ValueError(msg, xs) 81
[docs] 82 def _post_process_h(self, data:Maybe[dict]=None) -> None: # noqa: ARG002 83 fmt : str 84 match self.data.raw[0].format: 85 case None: 86 return 87 case str() as fmt : 88 pass 89 90 match API.EXPANSION_LIMIT_PATTERN.search(fmt): 91 case re.Match() as m: 92 self.data.max_expansions = int(m[1]) 93 case None: 94 pass
95
[docs] 96class MultiDKey(DKey, mark=list): 97 """ Multi keys allow 1+ explicit subkeys. 98 99 They have additional fields: 100 101 _subkeys : parsed information about explicit subkeys 102 103 """ 104 __slots__ = ("anon",) 105 anon : str 106 107 def __init__(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 108 super().__init__(*args, **kwargs) 109 match self.data.raw: 110 case [] | None: 111 msg = "Tried to build a multi key with no subkeys" 112 raise ValueError(msg, self.data.raw, kwargs) 113 case [*xs]: 114 self.anon = "".join(x.anon() for x in xs) 115 116 @override 117 def __str__(self) -> str: 118 return self[:] 119 120 @override 121 def __contains__(self, other:object) -> bool: 122 return other in self.keys() 123 124 @override 125 def __format__(self, spec:str, **kwargs:Any) -> str: 126 """ Just does normal str formatting """ 127 rem, _, _= self._processor.consume_format_params(spec) 128 return super().__format__(rem, **kwargs) 129
[docs] 130 def _multi(self) -> Literal[True]: 131 return True
132
[docs] 133 def keys(self) -> list[DKey]: 134 return [DKey(x, implicit=True) for x in self.data.meta if bool(x)]
135
[docs] 136 def exp_generate_chains_h(self, root:ExpInst_d, factory:InstructionFactory_p, opts:dict) -> list[ExpInstChain_d|ExpInst_d]: 137 """ Lift subkeys to expansion instructions """ 138 targets : list[ExpInstChain_d|ExpInst_d]= [] 139 keys = self.keys() 140 targets.append(ExpInstChain_d(root=self, merge=len(keys))) # type: ignore[arg-type] 141 for key in keys: 142 match factory.build_inst(key, root, opts, decrement=False): 143 case None: 144 targets.append(factory.null_inst()) 145 case ExpInst_d() as inst if inst.literal is True: 146 targets.append(inst) 147 case ExpInst_d() as inst if inst.value != self: 148 assert(inst.value != root.value) 149 targets += factory.build_chains(inst, opts) 150 case ExpInst_d() as inst: 151 vals = [ 152 inst, 153 factory.null_inst(), 154 ] 155 targets.append(factory.build_single_chain(vals, self)) 156 else: 157 return targets
158
[docs] 159 def exp_flatten_h(self, vals:list[ExpInst_d], factory:InstructionFactory_p, opts:dict) -> Maybe[ExpInst_d]: # noqa: ARG002 160 """ Flatten the multi-key expansion into a single string, 161 by using the anon-format str 162 """ 163 flat : list[str] = [] 164 key_meta = [x for x in self.data.meta if bool(x)] 165 if bool(vals) and not bool(key_meta): 166 return vals[0] 167 if len(vals) != len([x for x in self.data.meta if bool(x)]): 168 return None 169 170 for x in vals: 171 match x: 172 case ExpInst_d(value=IndirectDKey() as k): 173 flat.append(f"{k:wi}") 174 case ExpInst_d(value=API.Key_p() as k): 175 flat.append(f"{k:w}") 176 case ExpInst_d(value=x): 177 flat.append(str(x)) 178 else: 179 return ExpInst_d(value=self.anon.format(*flat), literal=True)
180
[docs] 181class NonDKey(DKey, mark=False): 182 """ Just a string, not a key. 183 184 :: 185 186 But this lets you call no-ops for key specific methods. 187 It can coerce itself though 188 """ 189 __slots__ = () 190 191 def __init__(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 192 super().__init__(*args, **kwargs) 193 if (fb:=kwargs.get('fallback', None)) is not None and fb != self: 194 msg = "NonKeys can't have a fallback, did you mean to use an explicit key?" 195 raise ValueError(msg, self) 196 197 @override 198 def __format__(self, spec:str, **kwargs:Any) -> str: 199 """ Just does normal str formatting """ 200 rem, _, _= self._processor.consume_format_params(spec) 201 return super().__format__(rem, **kwargs) 202
[docs] 203 def _nonkey(self) -> Literal[True]: 204 return True
205
[docs] 206 @override 207 def expand(self, *args, **kwargs) -> Maybe: # noqa: ANN002, ANN003 208 """ A Non-key just needs to be coerced into the correct str format """ 209 assert(isinstance(self, API.Key_p)) 210 val = ExpInst_d(value=self[:]) 211 match self._expander.coerce_result(val, self, kwargs): 212 case None if (fallback:=kwargs.get("fallback")) is not None: 213 return ExpInst_d(value=fallback, literal=True) 214 case None: 215 return self.data.fallback 216 case ExpInst_d() as x: 217 return x.value 218 case x: 219 msg = "Nonkey coercion didn't return an ExpInst_d" 220 raise TypeError(msg, x)
221
[docs] 222class IndirectDKey(DKey, mark=Mapping, convert="I"): 223 """ 224 A Key for getting a redirected key. 225 eg: RedirectionDKey(key) -> SingleDKey(value) 226 227 re_mark : 228 """ 229 __slots__ = ("multi_redir", "re_mark") 230 __hash__ = SingleDKey.__hash__ 231 232 def __init__(self, *, multi:bool=False, re_mark:Maybe[API.KeyMark]=None, **kwargs) -> None: # noqa: ANN003 233 assert(not self.endswith(INDIRECT_SUFFIX)), self[:] 234 super().__init__(**kwargs) 235 self.multi_redir = multi 236 self.re_mark = re_mark 237 238 @override 239 def __str__(self) -> str: 240 return f"{self:i}" 241 242 @override 243 def __eq__(self, other:object) -> bool: 244 match other: 245 case str() if other.endswith(INDIRECT_SUFFIX): 246 return f"{self:i}" == other 247 case _: 248 return super().__eq__(other) 249
[docs] 250 def _indirect(self) -> Literal[True]: 251 return True
252
[docs] 253 def exp_generate_chains_h(self, root:ExpInst_d, factory:InstructionFactory_p, opts:dict) -> list[ExpInstChain_d|ExpInst_d]: 254 """ Lookup the indirect version, the direct version, then use the fallback """ 255 targets : list = [ 256 factory.build_inst(self, root, opts, decrement=False), 257 factory.lift_inst(f"{self:d}", root, opts, decrement=False, implicit=True), 258 factory.null_inst(), 259 ] 260 261 return [ExpInstChain_d(*targets, root=root.value)]