omlish 0.0.0.dev295__py3-none-any.whl → 0.0.0.dev297__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omlish/__about__.py +4 -2
- omlish/collections/__init__.py +5 -0
- omlish/collections/persistent/persistent.py +26 -5
- omlish/collections/persistent/treapmap.py +35 -12
- omlish/collections/sorted/skiplist.py +27 -19
- omlish/collections/sorted/sorted.py +49 -12
- omlish/dataclasses/generation/processor.py +5 -2
- omlish/dataclasses/processing/base.py +7 -2
- omlish/dataclasses/processing/driving.py +2 -2
- omlish/dom/__init__.py +26 -0
- omlish/dom/building.py +54 -0
- omlish/dom/content.py +129 -0
- omlish/dom/rendering.py +184 -0
- omlish/math/__init__.py +83 -0
- omlish/math/bits.py +0 -153
- omlish/math/c.py +13 -0
- omlish/math/fixed.py +390 -0
- omlish/math/floats.py +3 -0
- omlish/math/histogram.py +127 -0
- omlish/math/stats.py +0 -124
- omlish/sql/__init__.py +16 -0
- omlish/sql/api/__init__.py +7 -0
- omlish/text/indent.py +6 -2
- omlish/typedvalues/values.py +0 -4
- {omlish-0.0.0.dev295.dist-info → omlish-0.0.0.dev297.dist-info}/METADATA +3 -1
- {omlish-0.0.0.dev295.dist-info → omlish-0.0.0.dev297.dist-info}/RECORD +30 -23
- {omlish-0.0.0.dev295.dist-info → omlish-0.0.0.dev297.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev295.dist-info → omlish-0.0.0.dev297.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev295.dist-info → omlish-0.0.0.dev297.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev295.dist-info → omlish-0.0.0.dev297.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
__version__ = '0.0.0.
|
2
|
-
__revision__ = '
|
1
|
+
__version__ = '0.0.0.dev297'
|
2
|
+
__revision__ = '60d7844496d6c1b510da1f34cb05a64b5f8c1f6d'
|
3
3
|
|
4
4
|
|
5
5
|
#
|
@@ -107,6 +107,8 @@ class Project(ProjectBase):
|
|
107
107
|
],
|
108
108
|
|
109
109
|
'templates': [
|
110
|
+
'markupsafe ~= 3.0',
|
111
|
+
|
110
112
|
'jinja2 ~= 3.1',
|
111
113
|
],
|
112
114
|
|
omlish/collections/__init__.py
CHANGED
@@ -77,11 +77,14 @@ from .ordered import ( # noqa
|
|
77
77
|
|
78
78
|
from .persistent.persistent import ( # noqa
|
79
79
|
PersistentMap,
|
80
|
+
PersistentMapping,
|
80
81
|
)
|
81
82
|
|
82
83
|
if _ta.TYPE_CHECKING:
|
83
84
|
from .persistent.treapmap import ( # noqa
|
85
|
+
TreapDict,
|
84
86
|
TreapMap,
|
87
|
+
new_treap_dict,
|
85
88
|
new_treap_map,
|
86
89
|
)
|
87
90
|
else:
|
@@ -108,6 +111,8 @@ else:
|
|
108
111
|
|
109
112
|
from .sorted.sorted import ( # noqa
|
110
113
|
SortedCollection,
|
114
|
+
SortedItems,
|
115
|
+
SortedIter,
|
111
116
|
SortedListDict,
|
112
117
|
SortedMapping,
|
113
118
|
SortedMutableMapping,
|
@@ -1,6 +1,8 @@
|
|
1
1
|
import abc
|
2
2
|
import typing as ta
|
3
3
|
|
4
|
+
from ... import lang
|
5
|
+
|
4
6
|
|
5
7
|
K = ta.TypeVar('K')
|
6
8
|
V = ta.TypeVar('V')
|
@@ -9,7 +11,7 @@ V = ta.TypeVar('V')
|
|
9
11
|
##
|
10
12
|
|
11
13
|
|
12
|
-
class PersistentMap(ta.Generic[K, V]
|
14
|
+
class PersistentMap(lang.Abstract, ta.Generic[K, V]):
|
13
15
|
@abc.abstractmethod
|
14
16
|
def __len__(self) -> int:
|
15
17
|
raise NotImplementedError
|
@@ -23,17 +25,36 @@ class PersistentMap(ta.Generic[K, V], abc.ABC):
|
|
23
25
|
raise NotImplementedError
|
24
26
|
|
25
27
|
@abc.abstractmethod
|
26
|
-
def __iter__(self) -> ta.Iterator[
|
28
|
+
def __iter__(self) -> ta.Iterator[K]:
|
29
|
+
raise NotImplementedError
|
30
|
+
|
31
|
+
@abc.abstractmethod
|
32
|
+
def items(self) -> ta.Iterator[tuple[K, V]]:
|
27
33
|
raise NotImplementedError
|
28
34
|
|
29
35
|
@abc.abstractmethod
|
30
|
-
def with_(self, k: K, v: V) ->
|
36
|
+
def with_(self, k: K, v: V) -> ta.Self:
|
31
37
|
raise NotImplementedError
|
32
38
|
|
33
39
|
@abc.abstractmethod
|
34
|
-
def without(self, k: K) ->
|
40
|
+
def without(self, k: K) -> ta.Self:
|
41
|
+
raise NotImplementedError
|
42
|
+
|
43
|
+
@abc.abstractmethod
|
44
|
+
def default(self, k: K, v: V) -> ta.Self:
|
45
|
+
raise NotImplementedError
|
46
|
+
|
47
|
+
|
48
|
+
class PersistentMapping(
|
49
|
+
PersistentMap[K, V],
|
50
|
+
ta.Mapping[K, V],
|
51
|
+
lang.Abstract,
|
52
|
+
ta.Generic[K, V],
|
53
|
+
):
|
54
|
+
@abc.abstractmethod
|
55
|
+
def __contains__(self, item: K) -> bool: # type: ignore[override]
|
35
56
|
raise NotImplementedError
|
36
57
|
|
37
58
|
@abc.abstractmethod
|
38
|
-
def
|
59
|
+
def items(self) -> ta.Iterator[tuple[K, V]]: # type: ignore[override] # FIXME: ItemsView
|
39
60
|
raise NotImplementedError
|
@@ -20,8 +20,10 @@ import abc
|
|
20
20
|
import random
|
21
21
|
import typing as ta
|
22
22
|
|
23
|
+
from ..sorted.sorted import SortedItems
|
23
24
|
from . import treap
|
24
25
|
from .persistent import PersistentMap
|
26
|
+
from .persistent import PersistentMapping
|
25
27
|
|
26
28
|
|
27
29
|
K = ta.TypeVar('K')
|
@@ -31,7 +33,7 @@ V = ta.TypeVar('V')
|
|
31
33
|
##
|
32
34
|
|
33
35
|
|
34
|
-
class TreapMap(PersistentMap[K, V]):
|
36
|
+
class TreapMap(PersistentMap[K, V], SortedItems[K, V]):
|
35
37
|
__slots__ = ('_n', '_c')
|
36
38
|
|
37
39
|
def __init__(
|
@@ -69,10 +71,10 @@ class TreapMap(PersistentMap[K, V]):
|
|
69
71
|
raise KeyError(item)
|
70
72
|
return n.value
|
71
73
|
|
72
|
-
def __iter__(self) -> ta.Iterator[
|
74
|
+
def __iter__(self) -> ta.Iterator[K]:
|
73
75
|
i = self.items()
|
74
76
|
while i.has_next():
|
75
|
-
yield i.next()
|
77
|
+
yield i.next()[0]
|
76
78
|
|
77
79
|
def items(self) -> 'TreapMapIterator[K, V]':
|
78
80
|
i = TreapMapIterator(
|
@@ -84,14 +86,6 @@ class TreapMap(PersistentMap[K, V]):
|
|
84
86
|
i._n = n.left # noqa
|
85
87
|
return i
|
86
88
|
|
87
|
-
def items_from(self, k: K) -> 'TreapMapIterator[K, V]':
|
88
|
-
lst = treap.place(self._n, (k, None), self._c) # type: ignore
|
89
|
-
i = TreapMapIterator(
|
90
|
-
_st=lst,
|
91
|
-
_n=lst.pop(),
|
92
|
-
)
|
93
|
-
return i
|
94
|
-
|
95
89
|
def items_desc(self) -> 'TreapMapReverseIterator[K, V]':
|
96
90
|
i = TreapMapReverseIterator(
|
97
91
|
_st=[],
|
@@ -102,6 +96,14 @@ class TreapMap(PersistentMap[K, V]):
|
|
102
96
|
i._n = n.right # noqa
|
103
97
|
return i
|
104
98
|
|
99
|
+
def items_from(self, k: K) -> 'TreapMapIterator[K, V]':
|
100
|
+
lst = treap.place(self._n, (k, None), self._c) # type: ignore
|
101
|
+
i = TreapMapIterator(
|
102
|
+
_st=lst,
|
103
|
+
_n=lst.pop(),
|
104
|
+
)
|
105
|
+
return i
|
106
|
+
|
105
107
|
def items_from_desc(self, k: K) -> 'TreapMapReverseIterator[K, V]':
|
106
108
|
lst = treap.place(self._n, (k, None), self._c, desc=True) # type: ignore
|
107
109
|
i = TreapMapReverseIterator(
|
@@ -137,7 +139,22 @@ def new_treap_map(cmp: ta.Callable[[tuple[K, V], tuple[K, V]], int]) -> Persiste
|
|
137
139
|
return TreapMap(_n=None, _c=cmp)
|
138
140
|
|
139
141
|
|
140
|
-
|
142
|
+
#
|
143
|
+
|
144
|
+
|
145
|
+
class TreapDict(TreapMap[K, V], PersistentMapping[K, V]):
|
146
|
+
__contains__ = TreapMap.__contains__
|
147
|
+
items = TreapMap.items
|
148
|
+
|
149
|
+
|
150
|
+
def new_treap_dict(cmp: ta.Callable[[tuple[K, V], tuple[K, V]], int]) -> PersistentMapping[K, V]:
|
151
|
+
return TreapDict(_n=None, _c=cmp)
|
152
|
+
|
153
|
+
|
154
|
+
##
|
155
|
+
|
156
|
+
|
157
|
+
class BaseTreapMapIterator(abc.ABC, ta.Iterator[tuple[K, V]], ta.Generic[K, V]):
|
141
158
|
__slots__ = ('_st', '_n')
|
142
159
|
|
143
160
|
def __init__(
|
@@ -151,6 +168,12 @@ class BaseTreapMapIterator(abc.ABC, ta.Generic[K, V]):
|
|
151
168
|
self._st = _st
|
152
169
|
self._n = _n
|
153
170
|
|
171
|
+
def __iter__(self) -> ta.Self:
|
172
|
+
return self
|
173
|
+
|
174
|
+
def __next__(self) -> tuple[K, V]:
|
175
|
+
return self.next()
|
176
|
+
|
154
177
|
def has_next(self) -> bool:
|
155
178
|
return self._n is not None
|
156
179
|
|
@@ -170,30 +170,38 @@ class SkipList(SortedCollection[T]):
|
|
170
170
|
|
171
171
|
#
|
172
172
|
|
173
|
-
def iter(self
|
174
|
-
|
175
|
-
cur = self._find(base)
|
176
|
-
while cur is not None and self._compare(base, cur.value) > 0: # type: ignore
|
177
|
-
cur = cur.next[0]
|
178
|
-
else:
|
179
|
-
cur = self._head.next[0]
|
173
|
+
def iter(self) -> ta.Iterator[T]:
|
174
|
+
cur = self._head.next[0]
|
180
175
|
|
181
176
|
while cur is not None:
|
182
177
|
yield cur.value # type: ignore
|
183
178
|
cur = cur.next[0]
|
184
179
|
|
185
|
-
def iter_desc(self
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
cur =
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
180
|
+
def iter_desc(self) -> ta.Iterator[T]:
|
181
|
+
cur = self._head.next[self._height - 1]
|
182
|
+
while True:
|
183
|
+
next = cur.next[cur.next.index(None) - 1 if None in cur.next else -1] # type: ignore # noqa
|
184
|
+
if next is None:
|
185
|
+
break
|
186
|
+
cur = next
|
187
|
+
|
188
|
+
while cur is not self._head:
|
189
|
+
yield cur.value # type: ignore
|
190
|
+
cur = cur.prev # type: ignore
|
191
|
+
|
192
|
+
def iter_from(self, base: T) -> ta.Iterator[T]:
|
193
|
+
cur = self._find(base)
|
194
|
+
while cur is not None and self._compare(base, cur.value) > 0: # type: ignore
|
195
|
+
cur = cur.next[0]
|
196
|
+
|
197
|
+
while cur is not None:
|
198
|
+
yield cur.value # type: ignore
|
199
|
+
cur = cur.next[0]
|
200
|
+
|
201
|
+
def iter_from_desc(self, base: T) -> ta.Iterator[T]:
|
202
|
+
cur = self._find(base)
|
203
|
+
while cur is not self._head and self._compare(base, cur.value) < 0: # type: ignore
|
204
|
+
cur = cur.prev # type: ignore
|
197
205
|
|
198
206
|
while cur is not self._head:
|
199
207
|
yield cur.value # type: ignore
|
@@ -13,7 +13,30 @@ V = ta.TypeVar('V')
|
|
13
13
|
##
|
14
14
|
|
15
15
|
|
16
|
-
class
|
16
|
+
class SortedIter(lang.Abstract, ta.Generic[T]):
|
17
|
+
@abc.abstractmethod
|
18
|
+
def iter(self) -> ta.Iterator[T]:
|
19
|
+
raise NotImplementedError
|
20
|
+
|
21
|
+
@abc.abstractmethod
|
22
|
+
def iter_desc(self) -> ta.Iterator[T]:
|
23
|
+
raise NotImplementedError
|
24
|
+
|
25
|
+
@abc.abstractmethod
|
26
|
+
def iter_from(self, base: T) -> ta.Iterator[T]:
|
27
|
+
raise NotImplementedError
|
28
|
+
|
29
|
+
@abc.abstractmethod
|
30
|
+
def iter_from_desc(self, base: T) -> ta.Iterator[T]:
|
31
|
+
raise NotImplementedError
|
32
|
+
|
33
|
+
|
34
|
+
class SortedCollection(
|
35
|
+
SortedIter[T],
|
36
|
+
ta.Collection[T],
|
37
|
+
lang.Abstract,
|
38
|
+
ta.Generic[T],
|
39
|
+
):
|
17
40
|
Comparator = ta.Callable[[U, U], int]
|
18
41
|
|
19
42
|
@staticmethod
|
@@ -46,18 +69,13 @@ class SortedCollection(lang.Abstract, ta.Collection[T]):
|
|
46
69
|
def remove(self, value: T) -> bool:
|
47
70
|
raise NotImplementedError
|
48
71
|
|
49
|
-
@abc.abstractmethod
|
50
|
-
def iter(self, base: T | None = None) -> ta.Iterable[T]:
|
51
|
-
raise NotImplementedError
|
52
72
|
|
53
|
-
|
54
|
-
def iter_desc(self, base: T | None = None) -> ta.Iterable[T]:
|
55
|
-
raise NotImplementedError
|
73
|
+
#
|
56
74
|
|
57
75
|
|
58
|
-
class
|
76
|
+
class SortedItems(lang.Abstract, ta.Generic[K, V]):
|
59
77
|
@abc.abstractmethod
|
60
|
-
def items(self) -> ta.Iterator[tuple[K, V]]:
|
78
|
+
def items(self) -> ta.Iterator[tuple[K, V]]:
|
61
79
|
raise NotImplementedError
|
62
80
|
|
63
81
|
@abc.abstractmethod
|
@@ -73,10 +91,29 @@ class SortedMapping(ta.Mapping[K, V]):
|
|
73
91
|
raise NotImplementedError
|
74
92
|
|
75
93
|
|
76
|
-
class
|
94
|
+
class SortedMapping(
|
95
|
+
SortedItems[K, V],
|
96
|
+
ta.Mapping[K, V],
|
97
|
+
lang.Abstract,
|
98
|
+
ta.Generic[K, V],
|
99
|
+
):
|
100
|
+
@abc.abstractmethod
|
101
|
+
def items(self) -> ta.Iterator[tuple[K, V]]: # type: ignore[override] # FIXME: ItemsView
|
102
|
+
raise NotImplementedError
|
103
|
+
|
104
|
+
|
105
|
+
class SortedMutableMapping(
|
106
|
+
ta.MutableMapping[K, V],
|
107
|
+
SortedMapping[K, V],
|
108
|
+
lang.Abstract,
|
109
|
+
ta.Generic[K, V],
|
110
|
+
):
|
77
111
|
pass
|
78
112
|
|
79
113
|
|
114
|
+
##
|
115
|
+
|
116
|
+
|
80
117
|
class SortedListDict(SortedMutableMapping[K, V]):
|
81
118
|
@staticmethod
|
82
119
|
def _item_comparator(a: tuple[K, V], b: tuple[K, V]) -> int:
|
@@ -119,7 +156,7 @@ class SortedListDict(SortedMutableMapping[K, V]):
|
|
119
156
|
yield from self._impl.iter_desc()
|
120
157
|
|
121
158
|
def items_from(self, key: K) -> ta.Iterator[tuple[K, V]]:
|
122
|
-
yield from self._impl.
|
159
|
+
yield from self._impl.iter_from((key, None))
|
123
160
|
|
124
161
|
def items_from_desc(self, key: K) -> ta.Iterator[tuple[K, V]]:
|
125
|
-
yield from self._impl.
|
162
|
+
yield from self._impl.iter_from_desc((key, None))
|
@@ -11,6 +11,7 @@ import typing as ta
|
|
11
11
|
from ... import check
|
12
12
|
from ... import lang
|
13
13
|
from ..processing.base import ProcessingContext
|
14
|
+
from ..processing.base import ProcessingOption
|
14
15
|
from ..processing.base import Processor
|
15
16
|
from ..processing.priority import ProcessorPriority
|
16
17
|
from ..processing.registry import register_processor_type
|
@@ -32,11 +33,13 @@ from .registry import generator_type_for_plan_type
|
|
32
33
|
##
|
33
34
|
|
34
35
|
|
35
|
-
|
36
|
+
@dc.dataclass(frozen=True)
|
37
|
+
class PlanOnly(ProcessingOption):
|
36
38
|
b: bool
|
37
39
|
|
38
40
|
|
39
|
-
|
41
|
+
@dc.dataclass(frozen=True)
|
42
|
+
class Verbose(ProcessingOption):
|
40
43
|
b: bool
|
41
44
|
|
42
45
|
|
@@ -13,6 +13,7 @@ from ..specs import ClassSpec
|
|
13
13
|
|
14
14
|
|
15
15
|
T = ta.TypeVar('T')
|
16
|
+
ProcessingOptionT = ta.TypeVar('ProcessingOptionT', bound='ProcessingOption')
|
16
17
|
|
17
18
|
|
18
19
|
##
|
@@ -21,6 +22,10 @@ T = ta.TypeVar('T')
|
|
21
22
|
ProcessingContextItemFactory: ta.TypeAlias = ta.Callable[['ProcessingContext'], ta.Any]
|
22
23
|
|
23
24
|
|
25
|
+
class ProcessingOption(lang.Abstract):
|
26
|
+
pass
|
27
|
+
|
28
|
+
|
24
29
|
class ProcessingContext:
|
25
30
|
def __init__(
|
26
31
|
self,
|
@@ -28,7 +33,7 @@ class ProcessingContext:
|
|
28
33
|
cs: ClassSpec,
|
29
34
|
item_factories: ta.Mapping[type, ProcessingContextItemFactory],
|
30
35
|
*,
|
31
|
-
options: ta.Sequence[
|
36
|
+
options: ta.Sequence[ProcessingOption] | None = None,
|
32
37
|
) -> None:
|
33
38
|
super().__init__()
|
34
39
|
|
@@ -63,7 +68,7 @@ class ProcessingContext:
|
|
63
68
|
self._items[ty] = ret
|
64
69
|
return ret
|
65
70
|
|
66
|
-
def option(self, ty: type[
|
71
|
+
def option(self, ty: type[ProcessingOptionT]) -> ProcessingOptionT | None:
|
67
72
|
return self._options_dct.get(ty)
|
68
73
|
|
69
74
|
|
@@ -1,9 +1,9 @@
|
|
1
|
-
import typing as ta
|
2
1
|
|
3
2
|
from .. import concerns as _concerns # noqa # imported for registration
|
4
3
|
from ..generation import processor as gp
|
5
4
|
from ..specs import ClassSpec
|
6
5
|
from .base import ProcessingContext
|
6
|
+
from .base import ProcessingOption
|
7
7
|
from .base import Processor
|
8
8
|
from .registry import all_processing_context_item_factories
|
9
9
|
from .registry import ordered_processor_types
|
@@ -19,7 +19,7 @@ def drive_cls_processing(
|
|
19
19
|
plan_only: bool = False,
|
20
20
|
verbose: bool = False,
|
21
21
|
) -> type:
|
22
|
-
options: list[
|
22
|
+
options: list[ProcessingOption] = []
|
23
23
|
if plan_only:
|
24
24
|
options.append(gp.PlanOnly(True))
|
25
25
|
if verbose:
|
omlish/dom/__init__.py
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
from .building import ( # noqa
|
2
|
+
d,
|
3
|
+
D,
|
4
|
+
)
|
5
|
+
|
6
|
+
from .content import ( # noqa
|
7
|
+
String,
|
8
|
+
Content,
|
9
|
+
|
10
|
+
Dom,
|
11
|
+
|
12
|
+
STRING_TYPES,
|
13
|
+
CONTENT_TYPES,
|
14
|
+
|
15
|
+
check_content,
|
16
|
+
iter_content,
|
17
|
+
)
|
18
|
+
|
19
|
+
from .rendering import ( # noqa
|
20
|
+
InvalidTagError,
|
21
|
+
StrForbiddenError,
|
22
|
+
|
23
|
+
Renderer,
|
24
|
+
|
25
|
+
render,
|
26
|
+
)
|
omlish/dom/building.py
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
import typing as ta
|
2
|
+
|
3
|
+
from .. import dataclasses as dc
|
4
|
+
from .content import Content
|
5
|
+
from .content import Dom
|
6
|
+
from .content import kwargs_to_attrs
|
7
|
+
|
8
|
+
|
9
|
+
##
|
10
|
+
|
11
|
+
|
12
|
+
def d(
|
13
|
+
tag: str,
|
14
|
+
*attrs_and_contents: tuple[str, ta.Any] | Content,
|
15
|
+
**kwargs: ta.Any,
|
16
|
+
) -> Dom:
|
17
|
+
c = []
|
18
|
+
for a in attrs_and_contents:
|
19
|
+
if isinstance(a, tuple):
|
20
|
+
k, v = a
|
21
|
+
if k in kwargs:
|
22
|
+
raise KeyError(f'Attribute {k} already set')
|
23
|
+
kwargs[k] = v
|
24
|
+
else:
|
25
|
+
c.append(a)
|
26
|
+
|
27
|
+
return Dom(
|
28
|
+
tag,
|
29
|
+
attrs=kwargs_to_attrs(**kwargs) or None,
|
30
|
+
body=c or None,
|
31
|
+
)
|
32
|
+
|
33
|
+
|
34
|
+
##
|
35
|
+
|
36
|
+
|
37
|
+
@dc.dataclass(frozen=True)
|
38
|
+
class DomBuilder:
|
39
|
+
tag: str
|
40
|
+
|
41
|
+
def __call__(
|
42
|
+
self,
|
43
|
+
*attrs_and_contents: tuple[str, ta.Any] | Content,
|
44
|
+
**kwargs: ta.Any,
|
45
|
+
) -> Dom:
|
46
|
+
return d(self.tag, *attrs_and_contents, **kwargs)
|
47
|
+
|
48
|
+
|
49
|
+
class DomAccessor:
|
50
|
+
def __getattr__(self, tag: str) -> DomBuilder:
|
51
|
+
return DomBuilder(tag)
|
52
|
+
|
53
|
+
|
54
|
+
D = DomAccessor()
|
omlish/dom/content.py
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
import keyword
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from .. import check
|
5
|
+
from .. import dataclasses as dc
|
6
|
+
from .. import lang
|
7
|
+
|
8
|
+
|
9
|
+
if ta.TYPE_CHECKING:
|
10
|
+
import markupsafe as ms
|
11
|
+
|
12
|
+
_HAS_MARKUPSAFE = True
|
13
|
+
|
14
|
+
else:
|
15
|
+
ms = lang.proxy_import('markupsafe')
|
16
|
+
|
17
|
+
_HAS_MARKUPSAFE = lang.can_import('markupsafe')
|
18
|
+
|
19
|
+
|
20
|
+
String: ta.TypeAlias = ta.Union[
|
21
|
+
str,
|
22
|
+
'ms.Markup',
|
23
|
+
]
|
24
|
+
|
25
|
+
Content: ta.TypeAlias = ta.Union[
|
26
|
+
list['Content'],
|
27
|
+
'Dom',
|
28
|
+
String,
|
29
|
+
None,
|
30
|
+
]
|
31
|
+
|
32
|
+
ContentT = ta.TypeVar('ContentT', bound=Content)
|
33
|
+
|
34
|
+
|
35
|
+
##
|
36
|
+
|
37
|
+
|
38
|
+
ATTR_NAMES_BY_KWARG: ta.Mapping[str, str] = {
|
39
|
+
**{f'{k}_': k for k in keyword.kwlist if k == k.lower()},
|
40
|
+
}
|
41
|
+
|
42
|
+
ATTR_KWARGS_BY_NAME: ta.Mapping[str, str] = {v: k for k, v in ATTR_NAMES_BY_KWARG.items()}
|
43
|
+
|
44
|
+
|
45
|
+
def kwargs_to_attrs(**kwargs: ta.Any) -> dict[str, ta.Any]:
|
46
|
+
return {
|
47
|
+
ATTR_NAMES_BY_KWARG.get(k, k).replace('_', '-'): v
|
48
|
+
for k, v in kwargs.items()
|
49
|
+
}
|
50
|
+
|
51
|
+
|
52
|
+
##
|
53
|
+
|
54
|
+
|
55
|
+
@dc.dataclass()
|
56
|
+
class Dom:
|
57
|
+
tag: str
|
58
|
+
attrs: dict[str, ta.Any | None] | None = dc.xfield(None, repr_fn=lang.opt_repr)
|
59
|
+
body: list[Content] | None = dc.xfield(None, repr_fn=lang.opt_repr)
|
60
|
+
|
61
|
+
def set(self, **kwargs: ta.Any) -> 'Dom':
|
62
|
+
if self.attrs is None:
|
63
|
+
self.attrs = {}
|
64
|
+
self.attrs.update(**kwargs_to_attrs(**kwargs))
|
65
|
+
return self
|
66
|
+
|
67
|
+
def unset(self, *keys: str) -> 'Dom':
|
68
|
+
if self.attrs is not None:
|
69
|
+
for k in keys:
|
70
|
+
self.attrs.pop(k, None)
|
71
|
+
return self
|
72
|
+
|
73
|
+
def add(self, *contents: Content) -> 'Dom':
|
74
|
+
if self.body is None:
|
75
|
+
self.body = []
|
76
|
+
for c in contents:
|
77
|
+
check_content(c)
|
78
|
+
self.body.extend(contents)
|
79
|
+
return self
|
80
|
+
|
81
|
+
def remove(self, *contents: Content, strict: bool = False) -> 'Dom':
|
82
|
+
if self.body is not None:
|
83
|
+
i = 0
|
84
|
+
while i < len(self.body):
|
85
|
+
e = self.body[i]
|
86
|
+
if any(c is e for c in contents):
|
87
|
+
del self.body[i]
|
88
|
+
elif strict:
|
89
|
+
raise ValueError(f'Content {e} not in body')
|
90
|
+
else:
|
91
|
+
i += 1
|
92
|
+
return self
|
93
|
+
|
94
|
+
|
95
|
+
##
|
96
|
+
|
97
|
+
|
98
|
+
STRING_TYPES: tuple[type, ...] = (
|
99
|
+
str,
|
100
|
+
*([ms.Markup] if _HAS_MARKUPSAFE else []),
|
101
|
+
)
|
102
|
+
|
103
|
+
CONTENT_TYPES: tuple[type, ...] = (
|
104
|
+
list,
|
105
|
+
Dom,
|
106
|
+
*STRING_TYPES,
|
107
|
+
type(None),
|
108
|
+
)
|
109
|
+
|
110
|
+
|
111
|
+
def check_content(c: ContentT) -> ContentT:
|
112
|
+
if isinstance(c, list):
|
113
|
+
for e in c:
|
114
|
+
check_content(e)
|
115
|
+
else:
|
116
|
+
check.isinstance(c, CONTENT_TYPES)
|
117
|
+
return c
|
118
|
+
|
119
|
+
|
120
|
+
def iter_content(c: Content) -> ta.Iterator[Dom | String]:
|
121
|
+
if isinstance(c, list):
|
122
|
+
for e in c:
|
123
|
+
yield from iter_content(e)
|
124
|
+
elif isinstance(c, (Dom, *STRING_TYPES)):
|
125
|
+
yield c # type: ignore[misc]
|
126
|
+
elif c is None:
|
127
|
+
pass
|
128
|
+
else:
|
129
|
+
raise TypeError(c)
|