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 importlib
14import itertools as itz
15import logging as logmod
16import pathlib as pl
17import re
18import time
19import weakref
20from uuid import UUID, uuid1
21
22# ##-- end stdlib imports
23
24# ##-- 3rd party imports
25from pydantic import field_validator, model_validator
26from jgdv import Maybe, Proto
27from jgdv.structs.strang import Strang
28from jgdv.structs.strang import _interface as StrangAPI # noqa: N812
29from jgdv.structs.strang.processor import StrangBasicProcessor
30
31# ##-- end 3rd party imports
32
33# ##-- 1st party imports
34import doot
35import doot.errors
36
37# ##-- end 1st party imports
38
39from .. import _interface as API # noqa: N812
40
41# ##-- types
42# isort: off
43import abc
44import collections.abc
45from typing import TYPE_CHECKING, cast, assert_type, assert_never
46from typing import Generic, NewType, Never
47# Protocols:
48from typing import Protocol, runtime_checkable
49# Typing Decorators:
50from typing import no_type_check, final, override, overload
51
52if TYPE_CHECKING:
53 from jgdv._abstract.pre_processable import PreProcessResult, PostInstanceData, InstanceData
54 from jgdv import Maybe, VerStr
55 from jgdv.structs.strang import Strang_p
56 from typing import Final
57 from typing import ClassVar, Any, LiteralString
58 from typing import Self, Literal
59 from typing import TypeGuard
60 from collections.abc import Iterable, Iterator, Callable, Generator
61 from collections.abc import Sequence, Mapping, MutableMapping, Hashable
62
63 from .._interface import TaskName_p
64##--|
65
66# isort: on
67# ##-- end types
68
69##-- logging
70logging = logmod.getLogger(__name__)
71##-- end logging
72DEFAULT_SEP : Final[str] = doot.constants.patterns.TASK_SEP # type: ignore[attr-defined]
73TASKS_PREFIX : Final[str] = "tasks."
74
75##--|
76
[docs]
77class TaskNameHeadMarks_e(StrangAPI.StrangMarkAbstract_e):
78 """ Markers used in a Strang's head """
79 basic = "$basic$"
80
[docs]
81class TaskNameBodyMarks_e(StrangAPI.StrangMarkAbstract_e):
82 """ Markers Used in a base Strang's body """
83
84 head = "$head$"
85 cleanup = "$cleanup$"
86 partial = "$partial$"
87 data = "$data$"
88 empty = ""
89 hide = "_"
90 extend = "+"
91 customised = "$+$"
92
[docs]
93 @override
94 @classmethod
95 def default(cls) -> Maybe[str]:
96 """ The mark used if no mark is found"""
97 return None
98
[docs]
99 @override
100 @classmethod
101 def implicit(cls) -> set[str]:
102 """ Marks that arent in the form $mark$ """
103 return {cls.hide, cls.empty}
104
[docs]
105 @override
106 @classmethod
107 def skip(cls) -> Maybe[str]:
108 """ The mark placed in empty words """
109 return cls.empty
110
[docs]
111 @override
112 @classmethod
113 def idempotent(cls) -> set[str]:
114 """ marks you can't have more than one of """
115 return {cls.head, cls.hide}
116
117
[docs]
118 @classmethod
119 def generated(cls) -> set[str]:
120 return { cls.cleanup, cls.head }
121##--|
122TASKSECTIONS : Final[StrangAPI.Sections_d] = StrangAPI.Sections_d(
123 StrangAPI.Sec_d("group", ".", "::", str, TaskNameHeadMarks_e, True), # noqa: FBT003
124 StrangAPI.Sec_d("body", ".", None, str, TaskNameBodyMarks_e, True), # noqa: FBT003
125)
126##--|
127
[docs]
128class TaskNameProcessor[T:API.TaskName_p](StrangBasicProcessor):
129
[docs]
130 @override
131 def pre_process(self, cls:type[T], input:Any, *args:Any, strict:bool=False, **kwargs:Any) -> PreProcessResult:
132 """ Remove 'tasks' as a prefix, and strip quotes """
133 match input:
134 case Strang():
135 cleaned = str(input).removeprefix(TASKS_PREFIX).replace('"', "")
136 case str():
137 cleaned = input.removeprefix(TASKS_PREFIX).replace('"', "")
138 case x if not strict:
139 cleaned = str(x)
140 case x:
141 raise TypeError(type(x))
142
143 return super().pre_process(cls, cleaned, *args, strict=strict, **kwargs)
144
[docs]
145 @override
146 def _implicit_mark(self, val:str, *, sec:StrangAPI.Sec_d, data:dict, index:int, maxcount:int) -> Maybe[StrangAPI.StrangMarkAbstract_e]:
147 """ Builds certain marks that are not in the form $mark$.
148
149 In particular, pass marks that are empty words between two case chars: group::a.b..c
150 And meta marks for tasks like job and hide: group::+._.a.b.c
151 """
152 match sec.marks:
153 case None:
154 return None
155 case x:
156 marks = x
157 match marks.skip():
158 case None:
159 pass
160 case x if val == x.value:
161 return x
162
163 if val not in marks:
164 return None
165 return marks(val)
166
[docs]
167@Proto(API.TaskName_p, StrangAPI.Strang_p)
168class TaskName(Strang):
169 """
170 A Task Name.
171 """
172 __slots__ = ()
173 Marks : ClassVar = TaskNameBodyMarks_e
174 _processor : ClassVar = TaskNameProcessor()
175 _sections : ClassVar = TASKSECTIONS
176
[docs]
177 def with_cleanup(self) -> Self:
178 """
179 Generate a $cleanup$ task name. the UUID of the source is carried with it
180 """
181 if self.is_cleanup():
182 return self
183 # if not self.uuid():
184 # raise ValueError("adding $cleanup$ to a task name requires a uuid in the base", self[:])
185
186 return self.push(TaskNameBodyMarks_e.cleanup, uuid=self.uuid())
187
[docs]
188 def with_head(self) -> Self:
189 """ generate a $head$ task name, carrying the uuid along with it """
190 if self.is_head():
191 return self
192 # if not self.uuid():
193 # raise ValueError("Adding $head$ to a task name requires a uuid in the base", self[:])
194
195 return self.push(TaskNameBodyMarks_e.head, uuid=self.uuid())
196
[docs]
197 def is_cleanup(self) -> bool:
198 return TaskNameBodyMarks_e.cleanup in self
199
[docs]
200 def is_head(self) -> bool:
201 return TaskNameBodyMarks_e.head in self
202
[docs]
203 def pop_generated(self) -> Self:
204 if not (self.is_head() or self.is_cleanup()):
205 return self
206
207 assert(self.uuid())
208 base = self.pop(top=False)
209 return type(self)(f"{base}[<uuid>]", uuid=self.uuid())