omlish 0.0.0.dev1__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.
Potentially problematic release.
This version of omlish might be problematic. Click here for more details.
- omlish/__about__.py +7 -0
- omlish/__init__.py +0 -0
- omlish/argparse.py +223 -0
- omlish/asyncs/__init__.py +17 -0
- omlish/asyncs/anyio.py +23 -0
- omlish/asyncs/asyncio.py +19 -0
- omlish/asyncs/asyncs.py +76 -0
- omlish/asyncs/futures.py +179 -0
- omlish/asyncs/trio.py +11 -0
- omlish/c3.py +173 -0
- omlish/cached.py +9 -0
- omlish/check.py +231 -0
- omlish/collections/__init__.py +63 -0
- omlish/collections/_abc.py +156 -0
- omlish/collections/_io_abc.py +78 -0
- omlish/collections/cache/__init__.py +11 -0
- omlish/collections/cache/descriptor.py +188 -0
- omlish/collections/cache/impl.py +485 -0
- omlish/collections/cache/types.py +37 -0
- omlish/collections/coerce.py +337 -0
- omlish/collections/frozen.py +148 -0
- omlish/collections/identity.py +106 -0
- omlish/collections/indexed.py +75 -0
- omlish/collections/mappings.py +127 -0
- omlish/collections/ordered.py +81 -0
- omlish/collections/persistent.py +36 -0
- omlish/collections/skiplist.py +193 -0
- omlish/collections/sorted.py +126 -0
- omlish/collections/treap.py +228 -0
- omlish/collections/treapmap.py +144 -0
- omlish/collections/unmodifiable.py +174 -0
- omlish/collections/utils.py +110 -0
- omlish/configs/__init__.py +0 -0
- omlish/configs/flattening.py +147 -0
- omlish/configs/props.py +64 -0
- omlish/dataclasses/__init__.py +83 -0
- omlish/dataclasses/impl/__init__.py +6 -0
- omlish/dataclasses/impl/api.py +260 -0
- omlish/dataclasses/impl/as_.py +76 -0
- omlish/dataclasses/impl/exceptions.py +2 -0
- omlish/dataclasses/impl/fields.py +148 -0
- omlish/dataclasses/impl/frozen.py +55 -0
- omlish/dataclasses/impl/hashing.py +85 -0
- omlish/dataclasses/impl/init.py +173 -0
- omlish/dataclasses/impl/internals.py +118 -0
- omlish/dataclasses/impl/main.py +150 -0
- omlish/dataclasses/impl/metaclass.py +126 -0
- omlish/dataclasses/impl/metadata.py +74 -0
- omlish/dataclasses/impl/order.py +47 -0
- omlish/dataclasses/impl/params.py +150 -0
- omlish/dataclasses/impl/processing.py +16 -0
- omlish/dataclasses/impl/reflect.py +173 -0
- omlish/dataclasses/impl/replace.py +40 -0
- omlish/dataclasses/impl/repr.py +34 -0
- omlish/dataclasses/impl/simple.py +92 -0
- omlish/dataclasses/impl/slots.py +80 -0
- omlish/dataclasses/impl/utils.py +167 -0
- omlish/defs.py +193 -0
- omlish/dispatch/__init__.py +3 -0
- omlish/dispatch/dispatch.py +137 -0
- omlish/dispatch/functions.py +52 -0
- omlish/dispatch/methods.py +162 -0
- omlish/docker.py +149 -0
- omlish/dynamic.py +220 -0
- omlish/graphs/__init__.py +0 -0
- omlish/graphs/dot/__init__.py +19 -0
- omlish/graphs/dot/items.py +162 -0
- omlish/graphs/dot/rendering.py +147 -0
- omlish/graphs/dot/utils.py +30 -0
- omlish/graphs/trees.py +249 -0
- omlish/http/__init__.py +0 -0
- omlish/http/consts.py +20 -0
- omlish/http/wsgi.py +34 -0
- omlish/inject/__init__.py +85 -0
- omlish/inject/binder.py +12 -0
- omlish/inject/bindings.py +49 -0
- omlish/inject/eagers.py +21 -0
- omlish/inject/elements.py +43 -0
- omlish/inject/exceptions.py +49 -0
- omlish/inject/impl/__init__.py +0 -0
- omlish/inject/impl/bindings.py +19 -0
- omlish/inject/impl/elements.py +154 -0
- omlish/inject/impl/injector.py +182 -0
- omlish/inject/impl/inspect.py +98 -0
- omlish/inject/impl/private.py +109 -0
- omlish/inject/impl/providers.py +132 -0
- omlish/inject/impl/scopes.py +198 -0
- omlish/inject/injector.py +40 -0
- omlish/inject/inspect.py +14 -0
- omlish/inject/keys.py +43 -0
- omlish/inject/managed.py +24 -0
- omlish/inject/overrides.py +18 -0
- omlish/inject/private.py +29 -0
- omlish/inject/providers.py +111 -0
- omlish/inject/proxy.py +48 -0
- omlish/inject/scopes.py +84 -0
- omlish/inject/types.py +21 -0
- omlish/iterators.py +184 -0
- omlish/json.py +194 -0
- omlish/lang/__init__.py +112 -0
- omlish/lang/cached.py +267 -0
- omlish/lang/classes/__init__.py +24 -0
- omlish/lang/classes/abstract.py +74 -0
- omlish/lang/classes/restrict.py +137 -0
- omlish/lang/classes/simple.py +120 -0
- omlish/lang/classes/test/__init__.py +0 -0
- omlish/lang/classes/test/test_abstract.py +89 -0
- omlish/lang/classes/test/test_restrict.py +71 -0
- omlish/lang/classes/test/test_simple.py +58 -0
- omlish/lang/classes/test/test_virtual.py +72 -0
- omlish/lang/classes/virtual.py +130 -0
- omlish/lang/clsdct.py +67 -0
- omlish/lang/cmp.py +63 -0
- omlish/lang/contextmanagers.py +249 -0
- omlish/lang/datetimes.py +67 -0
- omlish/lang/descriptors.py +52 -0
- omlish/lang/functions.py +126 -0
- omlish/lang/imports.py +153 -0
- omlish/lang/iterables.py +54 -0
- omlish/lang/maybes.py +136 -0
- omlish/lang/objects.py +103 -0
- omlish/lang/resolving.py +50 -0
- omlish/lang/strings.py +128 -0
- omlish/lang/typing.py +92 -0
- omlish/libc.py +532 -0
- omlish/logs/__init__.py +9 -0
- omlish/logs/_abc.py +247 -0
- omlish/logs/configs.py +62 -0
- omlish/logs/filters.py +9 -0
- omlish/logs/formatters.py +67 -0
- omlish/logs/utils.py +20 -0
- omlish/marshal/__init__.py +52 -0
- omlish/marshal/any.py +25 -0
- omlish/marshal/base.py +201 -0
- omlish/marshal/base64.py +25 -0
- omlish/marshal/dataclasses.py +115 -0
- omlish/marshal/datetimes.py +90 -0
- omlish/marshal/enums.py +43 -0
- omlish/marshal/exceptions.py +7 -0
- omlish/marshal/factories.py +129 -0
- omlish/marshal/global_.py +33 -0
- omlish/marshal/iterables.py +57 -0
- omlish/marshal/mappings.py +66 -0
- omlish/marshal/naming.py +17 -0
- omlish/marshal/objects.py +106 -0
- omlish/marshal/optionals.py +49 -0
- omlish/marshal/polymorphism.py +147 -0
- omlish/marshal/primitives.py +43 -0
- omlish/marshal/registries.py +57 -0
- omlish/marshal/standard.py +80 -0
- omlish/marshal/utils.py +23 -0
- omlish/marshal/uuids.py +29 -0
- omlish/marshal/values.py +30 -0
- omlish/math.py +184 -0
- omlish/os.py +32 -0
- omlish/reflect.py +359 -0
- omlish/replserver/__init__.py +5 -0
- omlish/replserver/__main__.py +4 -0
- omlish/replserver/console.py +247 -0
- omlish/replserver/server.py +146 -0
- omlish/runmodule.py +28 -0
- omlish/stats.py +342 -0
- omlish/term.py +222 -0
- omlish/testing/__init__.py +7 -0
- omlish/testing/pydevd.py +225 -0
- omlish/testing/pytest/__init__.py +8 -0
- omlish/testing/pytest/helpers.py +35 -0
- omlish/testing/pytest/inject/__init__.py +1 -0
- omlish/testing/pytest/inject/harness.py +159 -0
- omlish/testing/pytest/plugins/__init__.py +20 -0
- omlish/testing/pytest/plugins/_registry.py +6 -0
- omlish/testing/pytest/plugins/logging.py +13 -0
- omlish/testing/pytest/plugins/pycharm.py +54 -0
- omlish/testing/pytest/plugins/repeat.py +19 -0
- omlish/testing/pytest/plugins/skips.py +32 -0
- omlish/testing/pytest/plugins/spacing.py +19 -0
- omlish/testing/pytest/plugins/switches.py +70 -0
- omlish/testing/testing.py +102 -0
- omlish/text/__init__.py +0 -0
- omlish/text/delimit.py +171 -0
- omlish/text/indent.py +50 -0
- omlish/text/parts.py +265 -0
- omlish-0.0.0.dev1.dist-info/LICENSE +21 -0
- omlish-0.0.0.dev1.dist-info/METADATA +17 -0
- omlish-0.0.0.dev1.dist-info/RECORD +187 -0
- omlish-0.0.0.dev1.dist-info/WHEEL +5 -0
- omlish-0.0.0.dev1.dist-info/top_level.txt +1 -0
omlish/dynamic.py
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dynamically scoped variables (implemented by stackwalking). Unlike threadlocals these are generator-correct both in
|
|
3
|
+
binding and retrieval, and unlike ContextVars they require no manual context management. They are however *slow* and
|
|
4
|
+
should be used sparingly (once per sql statement executed not once per inner function call).
|
|
5
|
+
|
|
6
|
+
TODO:
|
|
7
|
+
- clj-style binding conveyance
|
|
8
|
+
- contextvar/async interop
|
|
9
|
+
- 'partializer'
|
|
10
|
+
"""
|
|
11
|
+
import contextlib
|
|
12
|
+
import functools
|
|
13
|
+
import sys
|
|
14
|
+
import types
|
|
15
|
+
import typing as ta
|
|
16
|
+
import weakref
|
|
17
|
+
|
|
18
|
+
from . import lang
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
T = ta.TypeVar('T')
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
_HOISTED_CODE_DEPTH: ta.MutableMapping[types.CodeType, int] = weakref.WeakKeyDictionary()
|
|
25
|
+
_MAX_HOIST_DEPTH = 0
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def hoist(depth=0):
|
|
29
|
+
def inner(fn):
|
|
30
|
+
_HOISTED_CODE_DEPTH[fn.__code__] = depth
|
|
31
|
+
global _MAX_HOIST_DEPTH
|
|
32
|
+
_MAX_HOIST_DEPTH = max(_MAX_HOIST_DEPTH, depth)
|
|
33
|
+
return fn
|
|
34
|
+
return inner
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
hoist()(contextlib.ExitStack.enter_context) # noqa
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class MISSING(lang.Marker):
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class UnboundVarError(ValueError):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Var(ta.Generic[T]):
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
default: ta.Union[type[MISSING], T] = MISSING, # type: ignore
|
|
53
|
+
*,
|
|
54
|
+
new: ta.Union[ta.Callable[[], T], type[MISSING]] = MISSING,
|
|
55
|
+
validate: ta.Optional[ta.Callable[[T], None]] = None,
|
|
56
|
+
) -> None:
|
|
57
|
+
super().__init__()
|
|
58
|
+
|
|
59
|
+
if default is not MISSING and new is not MISSING:
|
|
60
|
+
raise TypeError('Cannot set both default and new')
|
|
61
|
+
elif default is not MISSING:
|
|
62
|
+
new = lambda: default # type: ignore
|
|
63
|
+
self._new: ta.Union[type[MISSING], ta.Callable[[], T]] = new
|
|
64
|
+
self._validate = validate
|
|
65
|
+
self._bindings_by_frame: ta.MutableMapping[types.FrameType, ta.MutableMapping[int, Binding]] = weakref.WeakValueDictionary() # noqa
|
|
66
|
+
|
|
67
|
+
@ta.overload
|
|
68
|
+
def __call__(self) -> T:
|
|
69
|
+
...
|
|
70
|
+
|
|
71
|
+
@ta.overload
|
|
72
|
+
def __call__(self, value: T, **kwargs: ta.Any) -> ta.ContextManager[T]:
|
|
73
|
+
...
|
|
74
|
+
|
|
75
|
+
def __call__(self, *args, **kwargs):
|
|
76
|
+
if not args:
|
|
77
|
+
if kwargs:
|
|
78
|
+
raise TypeError(kwargs)
|
|
79
|
+
return self.value
|
|
80
|
+
elif len(args) == 1:
|
|
81
|
+
return self.binding(*args, **kwargs)
|
|
82
|
+
else:
|
|
83
|
+
raise TypeError(args)
|
|
84
|
+
|
|
85
|
+
def binding(self, value: T, *, offset: int = 0) -> ta.ContextManager[T]:
|
|
86
|
+
if self._validate is not None:
|
|
87
|
+
self._validate(self.value)
|
|
88
|
+
return Binding(self, value, offset=offset)
|
|
89
|
+
|
|
90
|
+
def with_binding(self, value):
|
|
91
|
+
def outer(fn):
|
|
92
|
+
@functools.wraps(fn)
|
|
93
|
+
def inner(*args, **kwargs):
|
|
94
|
+
with self.binding(value):
|
|
95
|
+
return fn(*args, **kwargs)
|
|
96
|
+
return inner
|
|
97
|
+
return outer
|
|
98
|
+
|
|
99
|
+
def with_binding_fn(self, binding_fn):
|
|
100
|
+
this = self
|
|
101
|
+
|
|
102
|
+
def outer(fn):
|
|
103
|
+
class Descriptor:
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
@functools.wraps(fn)
|
|
107
|
+
def __call__(*args, **kwargs):
|
|
108
|
+
with this.binding(binding_fn(*args, **kwargs)):
|
|
109
|
+
return fn(*args, **kwargs)
|
|
110
|
+
|
|
111
|
+
def __get__(self, obj, cls=None):
|
|
112
|
+
bound_binding_fn = binding_fn.__get__(obj, cls)
|
|
113
|
+
bound_fn = fn.__get__(obj, cls)
|
|
114
|
+
|
|
115
|
+
@functools.wraps(fn)
|
|
116
|
+
def inner(*args, **kwargs):
|
|
117
|
+
with this.binding(bound_binding_fn(*args, **kwargs)):
|
|
118
|
+
return bound_fn(*args, **kwargs)
|
|
119
|
+
|
|
120
|
+
return inner
|
|
121
|
+
|
|
122
|
+
dct = dict((k, getattr(fn, k)) for k in functools.WRAPPER_ASSIGNMENTS)
|
|
123
|
+
return lang.new_type(fn.__name__, (Descriptor,), dct)()
|
|
124
|
+
|
|
125
|
+
return outer
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def values(self) -> ta.Iterator[T]:
|
|
129
|
+
frame = sys._getframe().f_back # noqa
|
|
130
|
+
while frame:
|
|
131
|
+
try:
|
|
132
|
+
frame_bindings = self._bindings_by_frame[frame]
|
|
133
|
+
except KeyError:
|
|
134
|
+
pass
|
|
135
|
+
else:
|
|
136
|
+
for level, frame_binding in sorted(frame_bindings.items()):
|
|
137
|
+
yield frame_binding._value # noqa
|
|
138
|
+
frame = frame.f_back
|
|
139
|
+
|
|
140
|
+
if self._new is not MISSING:
|
|
141
|
+
yield self._new()
|
|
142
|
+
|
|
143
|
+
def __iter__(self) -> ta.Iterator[T]:
|
|
144
|
+
return self.values
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def value(self) -> T:
|
|
148
|
+
try:
|
|
149
|
+
return next(self.values)
|
|
150
|
+
except StopIteration:
|
|
151
|
+
raise UnboundVarError
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class Binding(ta.Generic[T]):
|
|
155
|
+
|
|
156
|
+
_frame: types.FrameType
|
|
157
|
+
_frame_bindings: ta.MutableMapping[int, 'Binding']
|
|
158
|
+
_level: int
|
|
159
|
+
|
|
160
|
+
def __init__(self, var: Var[T], value: T, *, offset: int = 0) -> None:
|
|
161
|
+
super().__init__()
|
|
162
|
+
|
|
163
|
+
self._var = var
|
|
164
|
+
self._value = value
|
|
165
|
+
self._offset = offset
|
|
166
|
+
|
|
167
|
+
def __enter__(self) -> T:
|
|
168
|
+
frame = sys._getframe(self._offset).f_back # noqa
|
|
169
|
+
lag_frame: ta.Optional[types.FrameType] = frame
|
|
170
|
+
while lag_frame is not None:
|
|
171
|
+
for cur_depth in range(_MAX_HOIST_DEPTH + 1):
|
|
172
|
+
if lag_frame is None:
|
|
173
|
+
break # type: ignore
|
|
174
|
+
try:
|
|
175
|
+
lag_hoist = _HOISTED_CODE_DEPTH[lag_frame.f_code]
|
|
176
|
+
except KeyError:
|
|
177
|
+
pass
|
|
178
|
+
else:
|
|
179
|
+
if lag_hoist >= cur_depth:
|
|
180
|
+
frame = lag_frame = lag_frame.f_back
|
|
181
|
+
break
|
|
182
|
+
lag_frame = lag_frame.f_back
|
|
183
|
+
else:
|
|
184
|
+
break
|
|
185
|
+
|
|
186
|
+
if frame is None:
|
|
187
|
+
raise RuntimeError
|
|
188
|
+
self._frame = frame
|
|
189
|
+
try:
|
|
190
|
+
self._frame_bindings = self._var._bindings_by_frame[self._frame] # noqa
|
|
191
|
+
except KeyError:
|
|
192
|
+
self._frame_bindings = self._var._bindings_by_frame[self._frame] = weakref.WeakValueDictionary() # noqa
|
|
193
|
+
self._level = 0
|
|
194
|
+
else:
|
|
195
|
+
self._level = min(self._frame_bindings.keys() or [1]) - 1
|
|
196
|
+
|
|
197
|
+
self._frame_bindings[self._level] = self
|
|
198
|
+
return self._value
|
|
199
|
+
|
|
200
|
+
def __exit__(self, et, e, tb):
|
|
201
|
+
if self._frame_bindings[self._level] is not self:
|
|
202
|
+
raise TypeError
|
|
203
|
+
|
|
204
|
+
del self._frame_bindings[self._level]
|
|
205
|
+
del self._frame_bindings
|
|
206
|
+
del self._frame
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class _GeneratorContextManager(contextlib._GeneratorContextManager): # noqa
|
|
210
|
+
|
|
211
|
+
@hoist(2)
|
|
212
|
+
def __enter__(self):
|
|
213
|
+
return super().__enter__()
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def contextmanager(fn):
|
|
217
|
+
@functools.wraps(fn)
|
|
218
|
+
def helper(*args, **kwds):
|
|
219
|
+
return _GeneratorContextManager(fn, args, kwds)
|
|
220
|
+
return helper # noqa
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .items import Attrs # noqa
|
|
2
|
+
from .items import Cell # noqa
|
|
3
|
+
from .items import Edge # noqa
|
|
4
|
+
from .items import Graph # noqa
|
|
5
|
+
from .items import Id # noqa
|
|
6
|
+
from .items import Item # noqa
|
|
7
|
+
from .items import Node # noqa
|
|
8
|
+
from .items import Raw # noqa
|
|
9
|
+
from .items import RawStmt # noqa
|
|
10
|
+
from .items import Row # noqa
|
|
11
|
+
from .items import Stmt # noqa
|
|
12
|
+
from .items import Table # noqa
|
|
13
|
+
from .items import Text # noqa
|
|
14
|
+
from .rendering import Renderer # noqa
|
|
15
|
+
from .rendering import open_dot # noqa
|
|
16
|
+
from .rendering import render # noqa
|
|
17
|
+
from .utils import Color # noqa
|
|
18
|
+
from .utils import escape # noqa
|
|
19
|
+
from .utils import gen_rainbow # noqa
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""
|
|
2
|
+
https://github.com/chadbrewbaker/ReverseSnowflakeJoins/blob/master/dir.g lolwut
|
|
3
|
+
- https://github.com/chadbrewbaker/ReverseSnowflakeJoins/blob/5a1186843b47db0c94d976ca115efa6012b572ba/gui.py#L37
|
|
4
|
+
- * https://linux.die.net/man/1/gvpr *
|
|
5
|
+
- https://github.com/rodw/gvpr-lib
|
|
6
|
+
"""
|
|
7
|
+
import typing as ta
|
|
8
|
+
|
|
9
|
+
from ... import check
|
|
10
|
+
from ... import collections as col
|
|
11
|
+
from ... import dataclasses as dc
|
|
12
|
+
from ... import lang
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Item(dc.Frozen, lang.Abstract, lang.Sealed):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Value(Item, lang.Abstract):
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def of(cls, obj: ta.Union['Value', str, ta.Sequence]) -> 'Value':
|
|
23
|
+
if isinstance(obj, Value):
|
|
24
|
+
return obj
|
|
25
|
+
elif isinstance(obj, str):
|
|
26
|
+
return Text(obj)
|
|
27
|
+
elif isinstance(obj, ta.Sequence):
|
|
28
|
+
return Table.of(obj)
|
|
29
|
+
else:
|
|
30
|
+
raise TypeError(obj)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Raw(Value):
|
|
34
|
+
raw: str
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def of(cls, obj: ta.Union['Raw', str]) -> 'Raw': # type: ignore
|
|
38
|
+
if isinstance(obj, Raw):
|
|
39
|
+
return obj
|
|
40
|
+
elif isinstance(obj, str):
|
|
41
|
+
return Raw(obj)
|
|
42
|
+
else:
|
|
43
|
+
raise TypeError(obj)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Text(Value):
|
|
47
|
+
text: str
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def of(cls, obj: ta.Union['Text', str]) -> 'Text': # type: ignore
|
|
51
|
+
if isinstance(obj, Text):
|
|
52
|
+
return obj
|
|
53
|
+
elif isinstance(obj, str):
|
|
54
|
+
return Text(obj)
|
|
55
|
+
else:
|
|
56
|
+
raise TypeError(obj)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Cell(Item):
|
|
60
|
+
value: Value
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def of(cls, obj: ta.Union['Cell', ta.Any]) -> 'Cell':
|
|
64
|
+
if isinstance(obj, Cell):
|
|
65
|
+
return obj
|
|
66
|
+
else:
|
|
67
|
+
return Cell(Value.of(obj))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Row(Item):
|
|
71
|
+
cells: ta.Sequence[Cell] = dc.xfield(coerce=col.seq)
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def of(cls, obj: ta.Union['Row', ta.Sequence[ta.Any]]) -> 'Row':
|
|
75
|
+
if isinstance(obj, Row):
|
|
76
|
+
return obj
|
|
77
|
+
elif isinstance(obj, str):
|
|
78
|
+
raise TypeError(obj)
|
|
79
|
+
elif isinstance(obj, ta.Sequence):
|
|
80
|
+
return Row([Cell.of(e) for e in obj])
|
|
81
|
+
else:
|
|
82
|
+
raise TypeError(obj)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Table(Value):
|
|
86
|
+
rows: ta.Sequence[Row] = dc.xfield(coerce=col.seq)
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def of(cls, obj: ta.Union['Table', ta.Sequence[ta.Any]]) -> 'Table': # type: ignore
|
|
90
|
+
if isinstance(obj, Table):
|
|
91
|
+
return obj
|
|
92
|
+
elif isinstance(obj, str):
|
|
93
|
+
raise TypeError(obj)
|
|
94
|
+
elif isinstance(obj, ta.Sequence):
|
|
95
|
+
return Table([Row.of(e) for e in obj])
|
|
96
|
+
else:
|
|
97
|
+
raise TypeError(obj)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class Id(Item):
|
|
101
|
+
id: str
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def of(cls, obj: ta.Union['Id', str]) -> 'Id':
|
|
105
|
+
if isinstance(obj, Id):
|
|
106
|
+
return obj
|
|
107
|
+
elif isinstance(obj, str):
|
|
108
|
+
return Id(obj)
|
|
109
|
+
else:
|
|
110
|
+
raise TypeError(obj)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class Attrs(Item):
|
|
114
|
+
attrs: ta.Mapping[str, Value] = dc.field(
|
|
115
|
+
coerce=lambda o: col.frozendict(
|
|
116
|
+
(check.not_empty(check.isinstance(k, str)), Value.of(v)) # type: ignore
|
|
117
|
+
for k, v in check.isinstance(o, ta.Mapping).items()
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def of(cls, obj: ta.Union['Attrs', ta.Mapping[str, ta.Any]]) -> 'Attrs':
|
|
123
|
+
if isinstance(obj, Attrs):
|
|
124
|
+
return obj
|
|
125
|
+
elif isinstance(obj, ta.Mapping):
|
|
126
|
+
return Attrs(obj)
|
|
127
|
+
else:
|
|
128
|
+
raise TypeError(obj)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class Stmt(Item, lang.Abstract):
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class RawStmt(Stmt):
|
|
136
|
+
raw: str
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
def of(cls, obj: ta.Union['RawStmt', str]) -> 'RawStmt':
|
|
140
|
+
if isinstance(obj, RawStmt):
|
|
141
|
+
return obj
|
|
142
|
+
elif isinstance(obj, str):
|
|
143
|
+
return RawStmt(obj)
|
|
144
|
+
else:
|
|
145
|
+
raise TypeError(obj)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class Edge(Stmt):
|
|
149
|
+
left: Id = dc.xfield(coerce=Id.of)
|
|
150
|
+
right: Id = dc.xfield(coerce=Id.of)
|
|
151
|
+
attrs: Attrs = dc.xfield(default=Attrs({}), coerce=Attrs.of)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class Node(Stmt):
|
|
155
|
+
id: Id = dc.xfield(coerce=Id.of)
|
|
156
|
+
attrs: Attrs = dc.xfield(default=Attrs({}), coerce=Attrs.of)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class Graph(Item):
|
|
160
|
+
stmts: ta.Sequence[Stmt] = dc.xfield(coerce=col.seq)
|
|
161
|
+
|
|
162
|
+
id: Id = dc.xfield(default=Id('G'), kw_only=True)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import html
|
|
2
|
+
import io
|
|
3
|
+
import subprocess
|
|
4
|
+
import tempfile
|
|
5
|
+
import time
|
|
6
|
+
import typing as ta
|
|
7
|
+
|
|
8
|
+
from ... import dispatch
|
|
9
|
+
from .items import Attrs
|
|
10
|
+
from .items import Cell
|
|
11
|
+
from .items import Edge
|
|
12
|
+
from .items import Graph
|
|
13
|
+
from .items import Id
|
|
14
|
+
from .items import Item
|
|
15
|
+
from .items import Node
|
|
16
|
+
from .items import Raw
|
|
17
|
+
from .items import RawStmt
|
|
18
|
+
from .items import Row
|
|
19
|
+
from .items import Table
|
|
20
|
+
from .items import Text
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Renderer:
|
|
24
|
+
|
|
25
|
+
def __init__(self, out: ta.TextIO) -> None:
|
|
26
|
+
super().__init__()
|
|
27
|
+
|
|
28
|
+
self._out = out
|
|
29
|
+
|
|
30
|
+
@dispatch.method
|
|
31
|
+
def render(self, item: Item) -> None:
|
|
32
|
+
raise TypeError(item)
|
|
33
|
+
|
|
34
|
+
@render.register
|
|
35
|
+
def render_raw(self, item: Raw) -> None:
|
|
36
|
+
self._out.write(item.raw)
|
|
37
|
+
|
|
38
|
+
@render.register
|
|
39
|
+
def render_text(self, item: Text) -> None:
|
|
40
|
+
self._out.write(html.escape(item.text))
|
|
41
|
+
|
|
42
|
+
@render.register
|
|
43
|
+
def render_cell(self, item: Cell) -> None:
|
|
44
|
+
self._out.write('<td>')
|
|
45
|
+
self.render(item.value)
|
|
46
|
+
self._out.write('</td>')
|
|
47
|
+
|
|
48
|
+
@render.register
|
|
49
|
+
def render_row(self, item: Row) -> None:
|
|
50
|
+
self._out.write('<tr>')
|
|
51
|
+
for cell in item.cells:
|
|
52
|
+
self.render(cell)
|
|
53
|
+
self._out.write('</tr>')
|
|
54
|
+
|
|
55
|
+
@render.register
|
|
56
|
+
def render_table(self, item: Table) -> None:
|
|
57
|
+
self._out.write('<table>')
|
|
58
|
+
for row in item.rows:
|
|
59
|
+
self.render(row)
|
|
60
|
+
self._out.write('</table>')
|
|
61
|
+
|
|
62
|
+
@render.register
|
|
63
|
+
def render_id(self, item: Id) -> None:
|
|
64
|
+
self._out.write(f'"{item.id}"')
|
|
65
|
+
|
|
66
|
+
@render.register
|
|
67
|
+
def render_attrs(self, item: Attrs) -> None:
|
|
68
|
+
if item.attrs:
|
|
69
|
+
self._out.write('[')
|
|
70
|
+
for i, (k, v) in enumerate(item.attrs.items()):
|
|
71
|
+
if i:
|
|
72
|
+
self._out.write(', ')
|
|
73
|
+
self._out.write(k)
|
|
74
|
+
self._out.write('=<')
|
|
75
|
+
self.render(v)
|
|
76
|
+
self._out.write('>')
|
|
77
|
+
self._out.write(']')
|
|
78
|
+
|
|
79
|
+
@render.register
|
|
80
|
+
def render_raw_stmt(self, item: RawStmt) -> None:
|
|
81
|
+
self._out.write(item.raw)
|
|
82
|
+
self._out.write('\n')
|
|
83
|
+
|
|
84
|
+
@render.register
|
|
85
|
+
def render_edge(self, item: Edge) -> None:
|
|
86
|
+
self.render(item.left)
|
|
87
|
+
self._out.write(' -> ')
|
|
88
|
+
self.render(item.right)
|
|
89
|
+
if item.attrs.attrs:
|
|
90
|
+
self._out.write(' ')
|
|
91
|
+
self.render(item.attrs)
|
|
92
|
+
self._out.write(';\n')
|
|
93
|
+
|
|
94
|
+
@render.register
|
|
95
|
+
def render_node(self, item: Node) -> None:
|
|
96
|
+
self.render(item.id)
|
|
97
|
+
if item.attrs.attrs:
|
|
98
|
+
self._out.write(' ')
|
|
99
|
+
self.render(item.attrs)
|
|
100
|
+
self._out.write(';\n')
|
|
101
|
+
|
|
102
|
+
@render.register
|
|
103
|
+
def render_graph(self, item: Graph) -> None:
|
|
104
|
+
self._out.write('digraph ')
|
|
105
|
+
self.render(item.id)
|
|
106
|
+
self._out.write(' {\n')
|
|
107
|
+
for stmt in item.stmts:
|
|
108
|
+
self.render(stmt)
|
|
109
|
+
self._out.write('}\n')
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def render(item: Item) -> str:
|
|
113
|
+
out = io.StringIO()
|
|
114
|
+
Renderer(out).render(item)
|
|
115
|
+
return out.getvalue()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def open_dot(
|
|
119
|
+
gv: str,
|
|
120
|
+
*,
|
|
121
|
+
timeout_s: float = 1.,
|
|
122
|
+
sleep_s: float = 0.,
|
|
123
|
+
) -> None:
|
|
124
|
+
stdout, _ = subprocess.Popen(
|
|
125
|
+
['dot', '-Tpdf'],
|
|
126
|
+
stdin=subprocess.PIPE,
|
|
127
|
+
stdout=subprocess.PIPE,
|
|
128
|
+
).communicate(
|
|
129
|
+
input=gv.encode('utf-8'),
|
|
130
|
+
timeout=timeout_s,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
with tempfile.NamedTemporaryFile(
|
|
134
|
+
suffix='.pdf',
|
|
135
|
+
delete=True,
|
|
136
|
+
) as pdf:
|
|
137
|
+
pdf.file.write(stdout)
|
|
138
|
+
pdf.file.flush()
|
|
139
|
+
|
|
140
|
+
_, _ = subprocess.Popen(
|
|
141
|
+
['open', pdf.name],
|
|
142
|
+
).communicate(
|
|
143
|
+
timeout=timeout_s,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if sleep_s > 0.:
|
|
147
|
+
time.sleep(sleep_s)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import html
|
|
2
|
+
import typing as ta
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def escape(s: str) -> str:
|
|
6
|
+
return html.escape(s).replace('@', '@')
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Color(ta.NamedTuple):
|
|
10
|
+
r: int
|
|
11
|
+
g: int
|
|
12
|
+
b: int
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def gen_rainbow(steps: int) -> list[Color]:
|
|
16
|
+
colors = []
|
|
17
|
+
for r in range(steps):
|
|
18
|
+
colors.append(Color(r * 255 // steps, 255, 0))
|
|
19
|
+
for g in range(steps, 0, -1):
|
|
20
|
+
colors.append(Color(255, g * 255 // steps, 0))
|
|
21
|
+
for b in range(steps):
|
|
22
|
+
colors.append(Color(255, 0, b * 255 // steps))
|
|
23
|
+
for r in range(steps, 0, -1):
|
|
24
|
+
colors.append(Color(r * 255 // steps, 0, 255))
|
|
25
|
+
for g in range(steps):
|
|
26
|
+
colors.append(Color(0, g * 255 // steps, 255))
|
|
27
|
+
for b in range(steps, 0, -1):
|
|
28
|
+
colors.append(Color(0, 255, b * 255 // steps))
|
|
29
|
+
colors.append(Color(0, 255, 0))
|
|
30
|
+
return colors
|